From f2d7fa8f88c5e59a7485bba816c28cd2a0505190 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 7 Jun 2020 23:33:55 +0200 Subject: [PATCH 01/14] _lots_ of changes - add collection.h for general purpose, concurrent data structures - add compositor with support for DRM hardware planes - add omxplayer_video_player plugin - add texture_registry (not used right now) *compositor* The first backing store created by flutter will be the native window surface (libgbm). All other backing stores created after that use some dirty, but awesome hacks. These backing stores are also only single-buffered right now, so they look absolutely terrible on screen. *omxplayer_video_player* A video_player_platform_interface implementation based on PlatformViews and omxplayer. Rendering works, but it's not that stable right now. Uses libsystemd's sd_bus API to communicate with omxplayer. --- CMakeLists.txt | 2 + Makefile | 24 +- include/collection.h | 470 ++++++++++ include/compositor.h | 56 ++ include/event_loop.h | 1 + include/flutter-pi.h | 98 ++- include/platformchannel.h | 22 + include/plugins/video_player.h | 444 ++++++++++ include/texture_registry.h | 50 ++ src/compositor.c | 858 ++++++++++++++++++ src/flutter-pi.c | 465 ++++++---- src/platformchannel.c | 101 ++- src/pluginregistry.c | 10 +- src/plugins/services.c | 19 + src/plugins/video_player.c | 1487 ++++++++++++++++++++++++++++++++ src/texture_registry.c | 240 ++++++ 16 files changed, 4156 insertions(+), 191 deletions(-) create mode 100644 include/collection.h create mode 100644 include/compositor.h create mode 100644 include/event_loop.h create mode 100644 include/plugins/video_player.h create mode 100644 include/texture_registry.h create mode 100644 src/compositor.c create mode 100644 src/plugins/video_player.c create mode 100644 src/texture_registry.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bdfb0f4..77c510c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ set(FLUTTER_PI_SRC src/plugins/text_input.c src/plugins/raw_keyboard.c src/plugins/spidev.c + src/plugins/video_player.c ) if(GPIOD_FOUND) @@ -145,6 +146,7 @@ target_compile_options(flutter-pi PRIVATE -DBUILD_ELM327_PLUGIN -DBUILD_SPIDEV_PLUGIN -DBUILD_TEST_PLUGIN + -DBUILD_VIDEO_PLAYER_PLUGIN ) install(TARGETS flutter-pi RUNTIME DESTINATION bin) diff --git a/Makefile b/Makefile index e0ddb566..358eec07 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,27 @@ CC = cc LD = cc -REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl) -DBUILD_TEXT_INPUT_PLUGIN -DBUILD_ELM327_PLUGIN -DBUILD_GPIOD_PLUGIN -DBUILD_SPIDEV_PLUGIN -DBUILD_TEST_PLUGIN -ggdb $(CFLAGS) -REAL_LDFLAGS = $(shell pkg-config --libs gbm libdrm glesv2 egl) -lrt -lflutter_engine -lpthread -ldl $(LDFLAGS) +REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd) \ + -DBUILD_TEXT_INPUT_PLUGIN \ + -DBUILD_ELM327_PLUGIN \ + -DBUILD_GPIOD_PLUGIN \ + -DBUILD_SPIDEV_PLUGIN \ + -DBUILD_TEST_PLUGIN \ + -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ + -ggdb -fsanitize=undefined \ + $(CFLAGS) -SOURCES = src/flutter-pi.c src/platformchannel.c src/pluginregistry.c src/console_keyboard.c \ +REAL_LDFLAGS = $(shell pkg-config --libs gbm libdrm glesv2 egl) \ + -lrt \ + -lflutter_engine \ + -lpthread \ + -ldl \ + -lm \ + $(LDFLAGS) + +SOURCES = src/flutter-pi.c src/platformchannel.c src/pluginregistry.c src/console_keyboard.c src/texture_registry.c \ + src/compositor.c \ src/plugins/elm327plugin.c src/plugins/services.c src/plugins/testplugin.c src/plugins/text_input.c \ - src/plugins/raw_keyboard.c src/plugins/gpiod.c src/plugins/spidev.c + src/plugins/raw_keyboard.c src/plugins/gpiod.c src/plugins/spidev.c src/plugins/video_player.c OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) all: out/flutter-pi diff --git a/include/collection.h b/include/collection.h new file mode 100644 index 00000000..a08cebdc --- /dev/null +++ b/include/collection.h @@ -0,0 +1,470 @@ +#ifndef _COLLECTION_H +#define _COLLECTION_H + +#include +#include +#include + +#include + +#define CQUEUE_DEFAULT_MAX_QUEUE_SIZE 64 + +struct concurrent_queue { + pthread_mutex_t mutex; + pthread_cond_t is_dequeueable; + pthread_cond_t is_enqueueable; + size_t start_index; + size_t length; + size_t size; + void *elements; + + size_t max_queue_size; + size_t element_size; +}; + +#define CQUEUE_INITIALIZER(element_type, _max_queue_size) \ + ((struct concurrent_queue) { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .is_dequeueable = PTHREAD_COND_INITIALIZER, \ + .is_enqueueable = PTHREAD_COND_INITIALIZER, \ + .start_index = 0, \ + .length = 0, \ + .size = 0, \ + .elements = NULL, \ + .max_queue_size = _max_queue_size, \ + .element_size = sizeof(element_type) \ + }) + +static inline int cqueue_init(struct concurrent_queue *queue, size_t element_size, size_t max_queue_size) { + memset(queue, 0, sizeof(*queue)); + + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->is_dequeueable, NULL); + pthread_cond_init(&queue->is_enqueueable, NULL); + + queue->start_index = 0; + queue->length = 0; + queue->size = 2; + queue->elements = calloc(2, element_size); + + queue->max_queue_size = max_queue_size; + queue->element_size = element_size; + + if (queue->elements == NULL) { + queue->size = 0; + return ENOMEM; + } + + return 0; +} + +static inline int cqueue_deinit(struct concurrent_queue *queue) { + pthread_mutex_destroy(&queue->mutex); + pthread_cond_destroy(&queue->is_dequeueable); + pthread_cond_destroy(&queue->is_enqueueable); + + if (queue->elements != NULL) { + free(queue->elements); + } + + queue->start_index = 0; + queue->length = 0; + queue->size = 0; + queue->elements = NULL; + + queue->max_queue_size = 0; + queue->element_size = 0; + + return 0; +} + +static inline int cqueue_lock(struct concurrent_queue * const queue) { + return pthread_mutex_lock(&queue->mutex); +} + +static inline int cqueue_unlock(struct concurrent_queue * const queue) { + return pthread_mutex_unlock(&queue->mutex); +} + +static inline int cqueue_try_enqueue( + struct concurrent_queue * const queue, + const void const *p_element +) { + cqueue_lock(queue); + + if (queue->size == queue->length) { + // expand the queue. + + size_t new_size = queue->size ? queue->size << 1 : 1; + + if (new_size > queue->max_queue_size) { + cqueue_unlock(queue); + return EAGAIN; + } + + void *new_elements = realloc(queue->elements, new_size * queue->element_size); + + if (new_elements == NULL) { + cqueue_unlock(queue); + return ENOMEM; + } + + if (queue->size) { + memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); + } + + queue->elements = new_elements; + queue->size = new_size; + } + + memcpy( + ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), + p_element, + queue->element_size + ); + + queue->length++; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + +static inline int cqueue_try_dequeue( + struct concurrent_queue * const queue, + void const *element_out +) { + cqueue_lock(queue); + + if (queue->length == 0) { + cqueue_unlock(queue); + return EAGAIN; + } + + memcpy( + ((char*) queue->elements) + queue->element_size*queue->start_index, + element_out, + queue->element_size + ); + + queue->start_index = (queue->start_index + 1) & (queue->size - 1); + queue->length--; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_enqueueable); + + return 0; +} + +static inline int cqueue_enqueue( + struct concurrent_queue * const queue, + const void const *p_element +) { + size_t new_size; + cqueue_lock(queue); + + if (queue->size == queue->length) { + // expand the queue or wait for an element to be dequeued. + + new_size = queue->size ? (queue->size << 1) : 1; + + if (new_size < queue->max_queue_size) { + void *new_elements = realloc(queue->elements, new_size * queue->element_size); + + if (new_elements == NULL) { + cqueue_unlock(queue); + return ENOMEM; + } + + if (queue->size) { + memcpy(((char*)new_elements) + (queue->element_size*queue->size), new_elements, queue->element_size*queue->size); + } + + queue->elements = new_elements; + queue->size = new_size; + } else { + do { + pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); + } while (queue->size == queue->length); + } + } + + memcpy( + ((char*) queue->elements) + (queue->element_size*((queue->start_index + queue->length) & (queue->size - 1))), + p_element, + queue->element_size + ); + + queue->length++; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + +static inline int cqueue_dequeue( + struct concurrent_queue *const queue, + void *const element_out +) { + cqueue_lock(queue); + + while (queue->length == 0) + pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); + + memcpy( + element_out, + ((char*) queue->elements) + (queue->element_size*queue->start_index), + queue->element_size + ); + + queue->start_index = (queue->start_index + 1) & (queue->size - 1); + queue->length--; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_enqueueable); + + return 0; +} + + +struct concurrent_pointer_set { + pthread_mutex_t mutex; + size_t count_pointers; + size_t size; + void **pointers; + + size_t max_size; +}; + +#define CPSET_DEFAULT_MAX_SIZE 64 + +#define CPSET_INITIALIZER(_max_size) \ + ((struct concurrent_pointer_set) { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .count_pointers = 0, \ + .size = 0, \ + .pointers = NULL, \ + .max_size = _max_size \ + }) + +static inline int cpset_init( + struct concurrent_pointer_set *const set, + const size_t max_size +) { + memset(set, 0, sizeof(*set)); + + pthread_mutex_init(&set->mutex, NULL); + + set->count_pointers = 0; + set->size = 2; + set->pointers = (void**) calloc(2, sizeof(void*)); + + set->max_size = max_size; + + if (set->pointers == NULL) { + set->size = 0; + return ENOMEM; + } + + return 0; +} + +static inline int cpset_deinit(struct concurrent_pointer_set *const set) { + pthread_mutex_destroy(&set->mutex); + + if (set->pointers != NULL) { + free(set->pointers); + } + + set->count_pointers = 0; + set->size = 0; + set->pointers = NULL; + + set->max_size = 0; + + return 0; +} + +static inline int cpset_lock(struct concurrent_pointer_set *const set) { + return pthread_mutex_lock(&set->mutex); +} + +static inline int cpset_unlock(struct concurrent_pointer_set *const set) { + return pthread_mutex_unlock(&set->mutex); +} + +static inline int cpset_put_locked( + struct concurrent_pointer_set *const set, + void *pointer +) { + size_t new_size; + int index; + + index = -1; + for (int i = 0; i < set->size; i++) { + if (set->pointers[i] && (pointer == set->pointers[i])) { + index = i; + break; + } + } + + if ((index == -1) && (set->size == set->count_pointers)) { + new_size = set->size ? set->size << 1 : 1; + if (new_size < set->max_size) { + void **new_pointers = (void**) realloc(set->pointers, new_size * sizeof(void*)); + + if (new_pointers == NULL) { + return ENOMEM; + } + + memset(new_pointers + set->size, 0, new_size - set->size); + + index = set->size; + + set->pointers = new_pointers; + set->size = new_size; + } else { + return EBUSY; + } + } + + if (index == -1) { + while (set->pointers[++index]); + } + + set->pointers[index] = pointer; + + set->count_pointers++; + + return 0; +} + +static inline int cpset_put( + struct concurrent_pointer_set *const set, + void *pointer +) { + int ok; + + cpset_lock(set); + ok = cpset_put_locked(set, pointer); + cpset_unlock(set); + + return ok; +} + +static inline bool cpset_contains( + struct concurrent_pointer_set *const set, + const void const *pointer +) { + cpset_lock(set); + + for (int i = 0; i < set->size; i++) { + if (set->pointers[i] && (set->pointers[i] == pointer)) { + return true; + } + } + + cpset_unlock(set); + return false; +} + +static inline bool cpset_contains_locked( + struct concurrent_pointer_set *const set, + const void const *pointer +) { + for (int i = 0; i < set->size; i++) { + if (set->pointers[i] && (set->pointers[i] == pointer)) { + return true; + } + } + + return false; +} + +static inline void *cpset_remove( + struct concurrent_pointer_set *const set, + const void const *pointer +) { + void *result; + size_t new_size; + int index; + + result = NULL; + + cpset_lock(set); + + for (index = 0; index < set->size; index++) { + if (set->pointers[index] && (set->pointers[index] == pointer)) { + result = set->pointers[index]; + + set->pointers[index] = NULL; + set->count_pointers--; + + cpset_unlock(set); + + return result; + } + } + + cpset_unlock(set); + return NULL; +} + +static inline void *cpset_remove_locked( + struct concurrent_pointer_set *const set, + const void const *pointer +) { + void *result; + size_t new_size; + int index; + + result = NULL; + + for (index = 0; index < set->size; index++) { + if (set->pointers[index] && (set->pointers[index] == pointer)) { + result = set->pointers[index]; + + set->pointers[index] = NULL; + set->count_pointers--; + + return result; + } + } + + return NULL; +} + +static inline void *__cpset_next_pointer( + struct concurrent_pointer_set *const set, + const void const *pointer +) { + int i = -1; + + if (pointer != NULL) { + for (i = 0; i < set->size; i++) { + if (set->pointers[i] == pointer) { + break; + } + } + + if (i == set->size) return NULL; + } + + for (i = i+1; i < set->size; i++) { + if (set->pointers[i]) { + return set->pointers[i]; + } + } + + return NULL; +} + +#define for_each_pointer_in_cpset(set, pointer) for ((pointer) = __cpset_next_pointer(set, NULL); (pointer) != NULL; (pointer) = __cpset_next_pointer(set, (pointer))) + +#endif \ No newline at end of file diff --git a/include/compositor.h b/include/compositor.h new file mode 100644 index 00000000..43fa74e8 --- /dev/null +++ b/include/compositor.h @@ -0,0 +1,56 @@ +#ifndef _COMPOSITOR_H +#define _COMPOSITOR_H + +#include + +#include +#include + +typedef int (*platform_view_present_cb)( + int64_t view_id, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +); + +struct window_surface_backing_store { + struct gbm_surface *gbm_surface; + struct gbm_bo *current_front_bo; +}; + +struct drm_fb_backing_store { + EGLImage egl_image; + GLuint gl_fbo_id; + GLuint gl_rbo_id; + uint32_t gem_handle; + uint32_t gem_stride; + uint32_t drm_fb_id; + uint32_t drm_plane_id; + int64_t current_zpos; +}; + +enum backing_store_type { + kWindowSurface, + kDrmFb +}; + +struct backing_store_metadata { + enum backing_store_type type; + union { + struct window_surface_backing_store window_surface; + struct drm_fb_backing_store drm_fb; + }; +}; + +extern const FlutterCompositor flutter_compositor; + +uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo); + +int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_present_cb cb, void *userdata); + +#endif \ No newline at end of file diff --git a/include/event_loop.h b/include/event_loop.h new file mode 100644 index 00000000..4289c357 --- /dev/null +++ b/include/event_loop.h @@ -0,0 +1 @@ +struct evloop; \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 318e5ead..63d860b6 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -5,14 +5,21 @@ #include #include #include -#include -#include #include -#include #include #include +#include +#include -#define EGL_PLATFORM_GBM_KHR 0x31D7 +#include +#include +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include +#define GL_GLEXT_PROTOTYPES +#include +#include enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight @@ -38,7 +45,11 @@ typedef enum { kUpdateOrientation, kSendPlatformMessage, kRespondToPlatformMessage, - kFlutterTask + kFlutterTask, + kRegisterExternalTexture, + kUnregisterExternalTexture, + kMarkExternalTextureFrameAvailable, + kGeneric } flutterpi_task_type; struct flutterpi_task { @@ -57,6 +68,11 @@ struct flutterpi_task { size_t message_size; uint8_t *message; }; + int64_t texture_id; + struct { + void (*callback)(void *userdata); + void *userdata; + }; }; uint64_t target_time; }; @@ -93,6 +109,74 @@ struct mousepointer_mtslot { FlutterPointerPhase phase; }; +extern struct drm { + char device[PATH_MAX]; + bool has_device; + int fd; + uint32_t connector_id; + drmModeModeInfo *mode; + uint32_t crtc_id; + size_t crtc_index; + struct gbm_bo *current_bo; + drmEventContext evctx; + bool disable_vsync; +} drm; + +extern struct gbm { + struct gbm_device *device; + struct gbm_surface *surface; + uint32_t format; + uint64_t modifier; +} gbm; + +extern struct egl { + EGLDisplay display; + EGLConfig config; + EGLContext root_context; + EGLContext flutter_render_context; + EGLContext flutter_resource_uploading_context; + EGLContext vidpp_context; + EGLContext compositor_context; + EGLSurface surface; + + bool modifiers_supported; + char *renderer; + + PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface; + PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurface; + PFNEGLCREATEDRMIMAGEMESAPROC createDRMImageMESA; + PFNEGLEXPORTDRMIMAGEMESAPROC exportDRMImageMESA; +} egl; + +extern struct gl { + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES; + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC EGLImageTargetRenderbufferStorageOES; +} gl; + +#define LOAD_EGL_PROC(name) \ + do { \ + char proc[256]; \ + snprintf(proc, 256, "%s", "egl" #name); \ + proc[3] = toupper(proc[3]); \ + egl.name = (void*) eglGetProcAddress(proc); \ + if (!egl.name) { \ + fprintf(stderr, "could not resolve EGL procedure " #name "\n"); \ + return EINVAL; \ + } \ + } while (false) + +#define LOAD_GL_PROC(name) \ + do { \ + char proc_name[256]; \ + snprintf(proc_name, 256, "gl" #name); \ + proc_name[2] = toupper(proc_name[2]); \ + gl.name = (void*) eglGetProcAddress(proc_name); \ + if (!gl.name) { \ + fprintf(stderr, "could not resolve GL procedure " #name "\n"); \ + return EINVAL; \ + } \ + } while (false) #define INPUT_BUSTYPE_FRIENDLY_NAME(bustype) ( \ (bustype) == BUS_PCI ? "PCI/e" : \ @@ -164,6 +248,10 @@ extern struct mousepointer_mtslot mousepointer; extern FlutterEngine engine; +struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo); + +void *proc_resolver(void* userdata, const char* name); + void post_platform_task(struct flutterpi_task *task); int flutterpi_send_platform_message(const char *channel, diff --git a/include/platformchannel.h b/include/platformchannel.h index a1e465db..2b5bcc25 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -315,6 +315,28 @@ int platch_respond_illegal_arg_json(FlutterPlatformMessageResponseHandle *handle int platch_respond_native_error_json(FlutterPlatformMessageResponseHandle *handle, int _errno); +int platch_respond_success_pigeon( + FlutterPlatformMessageResponseHandle *handle, + struct std_value *return_value +); + +int platch_respond_error_pigeon( + FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details +); + +int platch_respond_illegal_arg_pigeon( + FlutterPlatformMessageResponseHandle *handle, + char *error_msg +); + +int platch_respond_native_error_pigeon( + FlutterPlatformMessageResponseHandle *handle, + int _errno +); + /// Sends a success event with value `event_value` to an event channel /// that uses the standard method codec. int platch_send_success_event_std(char *channel, diff --git a/include/plugins/video_player.h b/include/plugins/video_player.h new file mode 100644 index 00000000..00584d15 --- /dev/null +++ b/include/plugins/video_player.h @@ -0,0 +1,444 @@ +#ifndef _OMXPLAYER_VIDEO_PLAYER_PLUGIN_H +#define _OMXPLAYER_VIDEO_PLAYER_PLUGIN_H + +#include +#include +#include +#include + +#include +#include + +#include + +#define LOAD_LIBSYSTEMD_PROC(name) \ + do { \ + libsystemd.name = dlsym(libsystemd.handle, #name); \ + if (!libsystemd.name) {\ + perror("could not resolve dbus procedure " #name); \ + return errno; \ + } \ + } while (false) + +#define LOAD_LIBSYSTEMD_PROC_OPTIONAL(name) \ + libsystemd.name = dlsym(libsystemd.handle, #name) + +#define DBUS_OMXPLAYER_OBJECT "/org/mpris/MediaPlayer2" +#define DBUS_OMXPLAYER_PLAYER_FACE "org.mpris.MediaPlayer2.Player" +#define DBUS_OMXPLAYER_ROOT_FACE "org.mpris.MediaPlayer2.Root" +#define DBUS_PROPERTY_FACE "org.freedesktop.DBus.Properties" +#define DBUS_PROPERTY_GET "Get" +#define DBUS_PROPRETY_SET "Set" + +struct libsystemd { + void *handle; + + int (*sd_bus_default)(sd_bus **ret); + int (*sd_bus_default_user)(sd_bus **ret); + int (*sd_bus_default_system)(sd_bus **ret); + + int (*sd_bus_open)(sd_bus **ret); + int (*sd_bus_open_with_description)(sd_bus **ret, const char *description); + int (*sd_bus_open_user)(sd_bus **ret); + int (*sd_bus_open_user_with_description)(sd_bus **ret, const char *description); + int (*sd_bus_open_system)(sd_bus **ret); + int (*sd_bus_open_system_with_description)(sd_bus **ret, const char *description); + int (*sd_bus_open_system_remote)(sd_bus **ret, const char *host); + int (*sd_bus_open_system_machine)(sd_bus **ret, const char *machine); + + int (*sd_bus_new)(sd_bus **ret); + + int (*sd_bus_set_address)(sd_bus *bus, const char *address); + int (*sd_bus_set_fd)(sd_bus *bus, int input_fd, int output_fd); + int (*sd_bus_set_exec)(sd_bus *bus, const char *path, char *const argv[]); + int (*sd_bus_get_address)(sd_bus *bus, const char **address); + int (*sd_bus_set_bus_client)(sd_bus *bus, int b); + int (*sd_bus_is_bus_client)(sd_bus *bus); + int (*sd_bus_set_server)(sd_bus *bus, int b, sd_id128_t bus_id); + int (*sd_bus_is_server)(sd_bus *bus); + int (*sd_bus_set_anonymous)(sd_bus *bus, int b); + int (*sd_bus_is_anonymous)(sd_bus *bus); + int (*sd_bus_set_trusted)(sd_bus *bus, int b); + int (*sd_bus_is_trusted)(sd_bus *bus); + int (*sd_bus_set_monitor)(sd_bus *bus, int b); + int (*sd_bus_is_monitor)(sd_bus *bus); + int (*sd_bus_set_description)(sd_bus *bus, const char *description); + int (*sd_bus_get_description)(sd_bus *bus, const char **description); + int (*sd_bus_negotiate_creds)(sd_bus *bus, int b, uint64_t creds_mask); + int (*sd_bus_negotiate_timestamp)(sd_bus *bus, int b); + int (*sd_bus_negotiate_fds)(sd_bus *bus, int b); + int (*sd_bus_can_send)(sd_bus *bus, char type); + int (*sd_bus_get_creds_mask)(sd_bus *bus, uint64_t *creds_mask); + int (*sd_bus_set_allow_interactive_authorization)(sd_bus *bus, int b); + int (*sd_bus_get_allow_interactive_authorization)(sd_bus *bus); + int (*sd_bus_set_exit_on_disconnect)(sd_bus *bus, int b); + int (*sd_bus_get_exit_on_disconnect)(sd_bus *bus); + int (*sd_bus_set_close_on_exit)(sd_bus *bus, int b); + int (*sd_bus_get_close_on_exit)(sd_bus *bus); + int (*sd_bus_set_watch_bind)(sd_bus *bus, int b); + int (*sd_bus_get_watch_bind)(sd_bus *bus); + int (*sd_bus_set_connected_signal)(sd_bus *bus, int b); + int (*sd_bus_get_connected_signal)(sd_bus *bus); + int (*sd_bus_set_sender)(sd_bus *bus, const char *sender); + int (*sd_bus_get_sender)(sd_bus *bus, const char **ret); + + int (*sd_bus_start)(sd_bus *bus); + + int (*sd_bus_try_close)(sd_bus *bus); + void (*sd_bus_close)(sd_bus *bus); + + sd_bus *(*sd_bus_ref)(sd_bus *bus); + sd_bus *(*sd_bus_unref)(sd_bus *bus); + sd_bus *(*sd_bus_close_unref)(sd_bus *bus); + sd_bus *(*sd_bus_flush_close_unref)(sd_bus *bus); + + void (*sd_bus_default_flush_close)(void); + + int (*sd_bus_is_open)(sd_bus *bus); + int (*sd_bus_is_ready)(sd_bus *bus); + + int (*sd_bus_get_bus_id)(sd_bus *bus, sd_id128_t *id); + int (*sd_bus_get_scope)(sd_bus *bus, const char **scope); + int (*sd_bus_get_tid)(sd_bus *bus, pid_t *tid); + int (*sd_bus_get_owner_creds)(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); + + int (*sd_bus_send)(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); + int (*sd_bus_send_to)(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); + int (*sd_bus_call)(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); + int (*sd_bus_call_async)(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); + + int (*sd_bus_get_fd)(sd_bus *bus); + int (*sd_bus_get_events)(sd_bus *bus); + int (*sd_bus_get_timeout)(sd_bus *bus, uint64_t *timeout_usec); + int (*sd_bus_process)(sd_bus *bus, sd_bus_message **r); + int (*sd_bus_process_priority)(sd_bus *bus, int64_t max_priority, sd_bus_message **r); + int (*sd_bus_wait)(sd_bus *bus, uint64_t timeout_usec); + int (*sd_bus_flush)(sd_bus *bus); + + sd_bus_slot* (*sd_bus_get_current_slot)(sd_bus *bus); + sd_bus_message* (*sd_bus_get_current_message)(sd_bus *bus); + sd_bus_message_handler_t (*sd_bus_get_current_handler)(sd_bus *bus); + void* (*sd_bus_get_current_userdata)(sd_bus *bus); + + int (*sd_bus_attach_event)(sd_bus *bus, sd_event *e, int priority); + int (*sd_bus_detach_event)(sd_bus *bus); + sd_event *(*sd_bus_get_event)(sd_bus *bus); + + int (*sd_bus_get_n_queued_read)(sd_bus *bus, uint64_t *ret); + int (*sd_bus_get_n_queued_write)(sd_bus *bus, uint64_t *ret); + + int (*sd_bus_set_method_call_timeout)(sd_bus *bus, uint64_t usec); + int (*sd_bus_get_method_call_timeout)(sd_bus *bus, uint64_t *ret); + + int (*sd_bus_add_filter)(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_add_match)(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_add_match_async)(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata); + int (*sd_bus_add_object)(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_add_fallback)(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_add_object_vtable)(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); + int (*sd_bus_add_fallback_vtable)(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); + int (*sd_bus_add_node_enumerator)(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); + int (*sd_bus_add_object_manager)(sd_bus *bus, sd_bus_slot **slot, const char *path); + + sd_bus_slot* (*sd_bus_slot_ref)(sd_bus_slot *slot); + sd_bus_slot* (*sd_bus_slot_unref)(sd_bus_slot *slot); + + sd_bus* (*sd_bus_slot_get_bus)(sd_bus_slot *slot); + void *(*sd_bus_slot_get_userdata)(sd_bus_slot *slot); + void *(*sd_bus_slot_set_userdata)(sd_bus_slot *slot, void *userdata); + int (*sd_bus_slot_set_description)(sd_bus_slot *slot, const char *description); + int (*sd_bus_slot_get_description)(sd_bus_slot *slot, const char **description); + int (*sd_bus_slot_get_floating)(sd_bus_slot *slot); + int (*sd_bus_slot_set_floating)(sd_bus_slot *slot, int b); + int (*sd_bus_slot_set_destroy_callback)(sd_bus_slot *s, sd_bus_destroy_t callback); + int (*sd_bus_slot_get_destroy_callback)(sd_bus_slot *s, sd_bus_destroy_t *callback); + + sd_bus_message* (*sd_bus_slot_get_current_message)(sd_bus_slot *slot); + sd_bus_message_handler_t (*sd_bus_slot_get_current_handler)(sd_bus_slot *slot); + void *(*sd_bus_slot_get_current_userdata)(sd_bus_slot *slot); + + int (*sd_bus_message_new)(sd_bus *bus, sd_bus_message **m, uint8_t type); + int (*sd_bus_message_new_signal)(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); + int (*sd_bus_message_new_method_call)(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); + int (*sd_bus_message_new_method_return)(sd_bus_message *call, sd_bus_message **m); + int (*sd_bus_message_new_method_error)(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); + int (*sd_bus_message_new_method_errorf)(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); + int (*sd_bus_message_new_method_errno)(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); + int (*sd_bus_message_new_method_errnof)(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); + + sd_bus_message* (*sd_bus_message_ref)(sd_bus_message *m); + sd_bus_message* (*sd_bus_message_unref)(sd_bus_message *m); + + int (*sd_bus_message_seal)(sd_bus_message *m, uint64_t cookie, uint64_t timeout_usec); + + int (*sd_bus_message_get_type)(sd_bus_message *m, uint8_t *type); + int (*sd_bus_message_get_cookie)(sd_bus_message *m, uint64_t *cookie); + int (*sd_bus_message_get_reply_cookie)(sd_bus_message *m, uint64_t *cookie); + int (*sd_bus_message_get_priority)(sd_bus_message *m, int64_t *priority); + + int (*sd_bus_message_get_expect_reply)(sd_bus_message *m); + int (*sd_bus_message_get_auto_start)(sd_bus_message *m); + int (*sd_bus_message_get_allow_interactive_authorization)(sd_bus_message *m); + + const char *(*sd_bus_message_get_signature)(sd_bus_message *m, int complete); + const char *(*sd_bus_message_get_path)(sd_bus_message *m); + const char *(*sd_bus_message_get_interface)(sd_bus_message *m); + const char *(*sd_bus_message_get_member)(sd_bus_message *m); + const char *(*sd_bus_message_get_destination)(sd_bus_message *m); + const char *(*sd_bus_message_get_sender)(sd_bus_message *m); + const sd_bus_error *(*sd_bus_message_get_error)(sd_bus_message *m); + int (*sd_bus_message_get_errno)(sd_bus_message *m); + + int (*sd_bus_message_get_monotonic_usec)(sd_bus_message *m, uint64_t *usec); + int (*sd_bus_message_get_realtime_usec)(sd_bus_message *m, uint64_t *usec); + int (*sd_bus_message_get_seqnum)(sd_bus_message *m, uint64_t* seqnum); + + sd_bus* (*sd_bus_message_get_bus)(sd_bus_message *m); + sd_bus_creds *(*sd_bus_message_get_creds)(sd_bus_message *m); /* do not unref the result */ + + int (*sd_bus_message_is_signal)(sd_bus_message *m, const char *interface, const char *member); + int (*sd_bus_message_is_method_call)(sd_bus_message *m, const char *interface, const char *member); + int (*sd_bus_message_is_method_error)(sd_bus_message *m, const char *name); + int (*sd_bus_message_is_empty)(sd_bus_message *m); + int (*sd_bus_message_has_signature)(sd_bus_message *m, const char *signature); + + int (*sd_bus_message_set_expect_reply)(sd_bus_message *m, int b); + int (*sd_bus_message_set_auto_start)(sd_bus_message *m, int b); + int (*sd_bus_message_set_allow_interactive_authorization)(sd_bus_message *m, int b); + + int (*sd_bus_message_set_destination)(sd_bus_message *m, const char *destination); + int (*sd_bus_message_set_sender)(sd_bus_message *m, const char *sender); + int (*sd_bus_message_set_priority)(sd_bus_message *m, int64_t priority); + + int (*sd_bus_message_append)(sd_bus_message *m, const char *types, ...); + int (*sd_bus_message_appendv)(sd_bus_message *m, const char *types, va_list ap); + int (*sd_bus_message_append_basic)(sd_bus_message *m, char type, const void *p); + int (*sd_bus_message_append_array)(sd_bus_message *m, char type, const void *ptr, size_t size); + int (*sd_bus_message_append_array_space)(sd_bus_message *m, char type, size_t size, void **ptr); + int (*sd_bus_message_append_array_iovec)(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); + int (*sd_bus_message_append_array_memfd)(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); + int (*sd_bus_message_append_string_space)(sd_bus_message *m, size_t size, char **s); + int (*sd_bus_message_append_string_iovec)(sd_bus_message *m, const struct iovec *iov, unsigned n); + int (*sd_bus_message_append_string_memfd)(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); + int (*sd_bus_message_append_strv)(sd_bus_message *m, char **l); + int (*sd_bus_message_open_container)(sd_bus_message *m, char type, const char *contents); + int (*sd_bus_message_close_container)(sd_bus_message *m); + int (*sd_bus_message_copy)(sd_bus_message *m, sd_bus_message *source, int all); + + int (*sd_bus_message_read)(sd_bus_message *m, const char *types, ...); + int (*sd_bus_message_readv)(sd_bus_message *m, const char *types, va_list ap); + int (*sd_bus_message_read_basic)(sd_bus_message *m, char type, void *p); + int (*sd_bus_message_read_array)(sd_bus_message *m, char type, const void **ptr, size_t *size); + int (*sd_bus_message_read_strv)(sd_bus_message *m, char ***l); /* free the result! */ + int (*sd_bus_message_skip)(sd_bus_message *m, const char *types); + int (*sd_bus_message_enter_container)(sd_bus_message *m, char type, const char *contents); + int (*sd_bus_message_exit_container)(sd_bus_message *m); + int (*sd_bus_message_peek_type)(sd_bus_message *m, char *type, const char **contents); + int (*sd_bus_message_verify_type)(sd_bus_message *m, char type, const char *contents); + int (*sd_bus_message_at_end)(sd_bus_message *m, int complete); + int (*sd_bus_message_rewind)(sd_bus_message *m, int complete); + + int (*sd_bus_get_unique_name)(sd_bus *bus, const char **unique); + int (*sd_bus_request_name)(sd_bus *bus, const char *name, uint64_t flags); + int (*sd_bus_request_name_async)(sd_bus *bus, sd_bus_slot **ret_slot, const char *name, uint64_t flags, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_release_name)(sd_bus *bus, const char *name); + int (*sd_bus_release_name_async)(sd_bus *bus, sd_bus_slot **ret_slot, const char *name, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_list_names)(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ + int (*sd_bus_get_name_creds)(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ + int (*sd_bus_get_name_machine_id)(sd_bus *bus, const char *name, sd_id128_t *machine); + + int (*sd_bus_call_method)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); + int (*sd_bus_call_method_async)(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); + int (*sd_bus_get_property)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); + int (*sd_bus_get_property_trivial)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); + int (*sd_bus_get_property_string)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ + int (*sd_bus_get_property_strv)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ + int (*sd_bus_set_property)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); + + int (*sd_bus_reply_method_return)(sd_bus_message *call, const char *types, ...); + int (*sd_bus_reply_method_error)(sd_bus_message *call, const sd_bus_error *e); + int (*sd_bus_reply_method_errorf)(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); + int (*sd_bus_reply_method_errno)(sd_bus_message *call, int error, const sd_bus_error *e); + int (*sd_bus_reply_method_errnof)(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); + + int (*sd_bus_emit_signal)(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); + + int (*sd_bus_emit_properties_changed_strv)(sd_bus *bus, const char *path, const char *interface, char **names); + int (*sd_bus_emit_properties_changed)(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; + + int (*sd_bus_emit_object_added)(sd_bus *bus, const char *path); + int (*sd_bus_emit_object_removed)(sd_bus *bus, const char *path); + int (*sd_bus_emit_interfaces_added_strv)(sd_bus *bus, const char *path, char **interfaces); + int (*sd_bus_emit_interfaces_added)(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; + int (*sd_bus_emit_interfaces_removed_strv)(sd_bus *bus, const char *path, char **interfaces); + int (*sd_bus_emit_interfaces_removed)(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; + + int (*sd_bus_query_sender_creds)(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); + int (*sd_bus_query_sender_privilege)(sd_bus_message *call, int capability); + + int (*sd_bus_match_signal)(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata); + int (*sd_bus_match_signal_async)(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t match_callback, sd_bus_message_handler_t add_callback, void *userdata); + + int (*sd_bus_creds_new_from_pid)(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); + sd_bus_creds *(*sd_bus_creds_ref)(sd_bus_creds *c); + sd_bus_creds *(*sd_bus_creds_unref)(sd_bus_creds *c); + uint64_t (*sd_bus_creds_get_mask)(const sd_bus_creds *c); + uint64_t (*sd_bus_creds_get_augmented_mask)(const sd_bus_creds *c); + + int (*sd_bus_creds_get_pid)(sd_bus_creds *c, pid_t *pid); + int (*sd_bus_creds_get_ppid)(sd_bus_creds *c, pid_t *ppid); + int (*sd_bus_creds_get_tid)(sd_bus_creds *c, pid_t *tid); + int (*sd_bus_creds_get_uid)(sd_bus_creds *c, uid_t *uid); + int (*sd_bus_creds_get_euid)(sd_bus_creds *c, uid_t *euid); + int (*sd_bus_creds_get_suid)(sd_bus_creds *c, uid_t *suid); + int (*sd_bus_creds_get_fsuid)(sd_bus_creds *c, uid_t *fsuid); + int (*sd_bus_creds_get_gid)(sd_bus_creds *c, gid_t *gid); + int (*sd_bus_creds_get_egid)(sd_bus_creds *c, gid_t *egid); + int (*sd_bus_creds_get_sgid)(sd_bus_creds *c, gid_t *sgid); + int (*sd_bus_creds_get_fsgid)(sd_bus_creds *c, gid_t *fsgid); + int (*sd_bus_creds_get_supplementary_gids)(sd_bus_creds *c, const gid_t **gids); + int (*sd_bus_creds_get_comm)(sd_bus_creds *c, const char **comm); + int (*sd_bus_creds_get_tid_comm)(sd_bus_creds *c, const char **comm); + int (*sd_bus_creds_get_exe)(sd_bus_creds *c, const char **exe); + int (*sd_bus_creds_get_cmdline)(sd_bus_creds *c, char ***cmdline); + int (*sd_bus_creds_get_cgroup)(sd_bus_creds *c, const char **cgroup); + int (*sd_bus_creds_get_unit)(sd_bus_creds *c, const char **unit); + int (*sd_bus_creds_get_slice)(sd_bus_creds *c, const char **slice); + int (*sd_bus_creds_get_user_unit)(sd_bus_creds *c, const char **unit); + int (*sd_bus_creds_get_user_slice)(sd_bus_creds *c, const char **slice); + int (*sd_bus_creds_get_session)(sd_bus_creds *c, const char **session); + int (*sd_bus_creds_get_owner_uid)(sd_bus_creds *c, uid_t *uid); + int (*sd_bus_creds_has_effective_cap)(sd_bus_creds *c, int capability); + int (*sd_bus_creds_has_permitted_cap)(sd_bus_creds *c, int capability); + int (*sd_bus_creds_has_inheritable_cap)(sd_bus_creds *c, int capability); + int (*sd_bus_creds_has_bounding_cap)(sd_bus_creds *c, int capability); + int (*sd_bus_creds_get_selinux_context)(sd_bus_creds *c, const char **context); + int (*sd_bus_creds_get_audit_session_id)(sd_bus_creds *c, uint32_t *sessionid); + int (*sd_bus_creds_get_audit_login_uid)(sd_bus_creds *c, uid_t *loginuid); + int (*sd_bus_creds_get_tty)(sd_bus_creds *c, const char **tty); + int (*sd_bus_creds_get_unique_name)(sd_bus_creds *c, const char **name); + int (*sd_bus_creds_get_well_known_names)(sd_bus_creds *c, char ***names); + int (*sd_bus_creds_get_description)(sd_bus_creds *c, const char **name); + + void (*sd_bus_error_free)(sd_bus_error *e); + int (*sd_bus_error_set)(sd_bus_error *e, const char *name, const char *message); + int (*sd_bus_error_setf)(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); + int (*sd_bus_error_set_const)(sd_bus_error *e, const char *name, const char *message); + int (*sd_bus_error_set_errno)(sd_bus_error *e, int error); + int (*sd_bus_error_set_errnof)(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); + int (*sd_bus_error_set_errnofv)(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); + int (*sd_bus_error_get_errno)(const sd_bus_error *e); + int (*sd_bus_error_copy)(sd_bus_error *dest, const sd_bus_error *e); + int (*sd_bus_error_move)(sd_bus_error *dest, sd_bus_error *e); + int (*sd_bus_error_is_set)(const sd_bus_error *e); + int (*sd_bus_error_has_name)(const sd_bus_error *e, const char *name); + + int (*sd_bus_error_add_map)(const sd_bus_error_map *map); + + int (*sd_bus_path_encode)(const char *prefix, const char *external_id, char **ret_path); + int (*sd_bus_path_encode_many)(char **out, const char *path_template, ...); + int (*sd_bus_path_decode)(const char *path, const char *prefix, char **ret_external_id); + int (*sd_bus_path_decode_many)(const char *path, const char *path_template, ...); + + int (*sd_bus_track_new)(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); + sd_bus_track* (*sd_bus_track_ref)(sd_bus_track *track); + sd_bus_track* (*sd_bus_track_unref)(sd_bus_track *track); + + sd_bus* (*sd_bus_track_get_bus)(sd_bus_track *track); + void *(*sd_bus_track_get_userdata)(sd_bus_track *track); + void *(*sd_bus_track_set_userdata)(sd_bus_track *track, void *userdata); + + int (*sd_bus_track_add_sender)(sd_bus_track *track, sd_bus_message *m); + int (*sd_bus_track_remove_sender)(sd_bus_track *track, sd_bus_message *m); + int (*sd_bus_track_add_name)(sd_bus_track *track, const char *name); + int (*sd_bus_track_remove_name)(sd_bus_track *track, const char *name); + + int (*sd_bus_track_set_recursive)(sd_bus_track *track, int b); + int (*sd_bus_track_get_recursive)(sd_bus_track *track); + + unsigned (*sd_bus_track_count)(sd_bus_track *track); + int (*sd_bus_track_count_sender)(sd_bus_track *track, sd_bus_message *m); + int (*sd_bus_track_count_name)(sd_bus_track *track, const char *name); + + const char* (*sd_bus_track_contains)(sd_bus_track *track, const char *name); + const char* (*sd_bus_track_first)(sd_bus_track *track); + const char* (*sd_bus_track_next)(sd_bus_track *track); + + int (*sd_bus_track_set_destroy_callback)(sd_bus_track *s, sd_bus_destroy_t callback); + int (*sd_bus_track_get_destroy_callback)(sd_bus_track *s, sd_bus_destroy_t *ret); +}; + +struct omxplayer_mgr; + +struct omxplayer_video_player { + char event_channel_name[256]; + char video_uri[256]; + int64_t player_id; + int64_t view_id; + + /// If flutter says the video should be looping, this value is true. + /// Note: this is not related to whether omxplayer is looping. + /// omxplayer is always looping so it doesn't accidentally terminate on us. + bool should_loop; + + struct omxplayer_mgr *mgr; +}; + +struct omxplayer_mgr { + pthread_t thread; + struct omxplayer_video_player *player; + struct concurrent_queue task_queue; +}; + +enum omxplayer_mgr_task_type { + kCreate, + kDispose, + kListen, + kUnlisten, + kSetLooping, + kSetVolume, + kPlay, + kPause, + kGetPosition, + kSetPosition, + kUpdateView +}; + +struct omxplayer_mgr_task { + enum omxplayer_mgr_task_type type; + + FlutterPlatformMessageResponseHandle *responsehandle; + + union { + struct { + char *omxplayer_dbus_name; + bool omxplayer_online; + }; + bool loop; + float volume; + int64_t position; + struct { + bool visible; + }; + struct { + int offset_x, offset_y; + int width, height; + int zpos; + }; + }; +}; + + + +#define OMXPLAYER_VIDEO_PLAYER_INITIALIZER {0} + +enum data_source_type { + kDataSourceTypeAsset, + kDataSourceTypeNetwork, + kDataSourceTypeFile +}; + +extern int omxpvidpp_init(void); +extern int omxpvidpp_deinit(void); + +#endif \ No newline at end of file diff --git a/include/texture_registry.h b/include/texture_registry.h new file mode 100644 index 00000000..1ac9c164 --- /dev/null +++ b/include/texture_registry.h @@ -0,0 +1,50 @@ +#ifndef _TEXTURE_REGISTRY_H +#define _TEXTURE_REGISTRY_H + +#include +#include + +typedef int (*texreg_collect_gl_texture_cb)( + GLenum gl_texture_target, + GLuint gl_texture_id, + GLuint gl_texture_format, + void *userdata, + size_t width, + size_t height +); + +struct texture_details { + FlutterOpenGLTexture gl_texture; + texreg_collect_gl_texture_cb collection_cb; + void *collection_cb_userdata; +}; + +struct texture_map_entry { + int64_t texture_id; + struct texture_details details; +}; + +extern bool texreg_gl_external_texture_frame_callback( + void *userdata, + int64_t texture_id, + size_t width, + size_t height, + FlutterOpenGLTexture *texture_out +); + +extern int texreg_register_texture( + GLenum gl_texture_target, + GLuint gl_texture_id, + GLuint gl_texture_format, + void *userdata, + texreg_collect_gl_texture_cb collection_cb, + size_t width, + size_t height, + int64_t *texture_id_out +); + +extern int texreg_mark_texture_frame_available(int64_t texture_id); + +extern int texreg_unregister_texture(int64_t texture_id); + +#endif \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c new file mode 100644 index 00000000..741dcd07 --- /dev/null +++ b/src/compositor.c @@ -0,0 +1,858 @@ +#include +#include + +#include +#include +#include +#include +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include +#define GL_GLEXT_PROTOTYPES +#include +#include + +#include +#include +#include + +struct view_cb_data { + int64_t view_id; + platform_view_present_cb present_cb; + void *userdata; +}; + +struct flutterpi_compositor { + bool should_create_window_surface_backing_store; + struct concurrent_pointer_set cbs; +} flutterpi_compositor = { + .should_create_window_surface_backing_store = true, + .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) +}; + +static bool should_create_window_surface_backing_store = true; + +static void destroy_gbm_bo(struct gbm_bo *bo, void *userdata) { + struct drm_fb *fb = userdata; + + if (fb && fb->fb_id) + drmModeRmFB(drm.fd, fb->fb_id); + + free(fb); +} + +uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { + uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; + int ok = -1; + + // if the buffer object already has some userdata associated with it, + // it's the framebuffer we allocated. + struct drm_fb *fb = gbm_bo_get_user_data(bo); + if (fb) return fb->fb_id; + + // if there's no framebuffer for the bo, we need to create one. + 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); + + 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, "drm_fb_get_from_bo: 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, "drm_fb_get_from_bo: failed to create fb: %s\n", strerror(errno)); + free(fb); + return 0; + } + + gbm_bo_set_user_data(bo, fb, destroy_gbm_bo); + + return fb->fb_id; +} + +/// Find the next DRM overlay plane that has no FB associated with it +static uint32_t find_next_unused_drm_plane(void) { + uint32_t result; + bool has_result; + + has_result = false; + + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm.fd); + for (int i = 0; (i < plane_res->count_planes) && !has_result; i++) { + drmModePlane *plane = drmModeGetPlane(drm.fd, plane_res->planes[i]); + drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, plane->plane_id, DRM_MODE_OBJECT_ANY); + + for (int j = 0; (j < props->count_props) && !has_result; j++) { + drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[j]); + + if ((strcmp(prop->name, "type") == 0) + && (props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY)) { + + result = plane->plane_id; + has_result = true; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); + } + drmModeFreePlaneResources(plane_res); + + return has_result? result : 0; +} + +static int set_property_value( + const uint32_t object_id, + const uint32_t object_type, + char name[32], + uint64_t value +) { + bool has_result = false; + int ok; + + drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, object_id, object_type); + if (props == NULL) { + perror("[compositor] Could not get object properties. drmModeObjectGetProperties"); + return errno; + } + + for (int i = 0; (i < props->count_props) && (!has_result); i++) { + drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[i]); + + if (strcmp(prop->name, name) == 0) { + ok = drmModeObjectSetProperty(drm.fd, object_id, object_type, prop->prop_id, value); + if (ok == -1) { + perror("Could not set object property. drmModeObjectSetProperty"); + return errno; + } + + has_result = true; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + + if (!has_result) { + fprintf(stderr, "[compositor] Could not find any property with name %32s\n", name); + } + + return has_result? 0 : ENOENT; +} + +static int set_plane_property_value( + const uint32_t plane_id, + char name[32], + uint64_t value +) { + return set_property_value( + plane_id, + DRM_MODE_OBJECT_PLANE, + name, + value + ); +} + +static int get_property_value( + const uint32_t object_id, + const uint32_t object_type, + char name[32], + uint64_t *value_out +) { + bool has_result = false; + + drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, object_id, object_type); + if (props == NULL) { + perror("[compositor] Could not get object properties. drmModeObjectGetProperties"); + return errno; + } + + for (int i = 0; (i < props->count_props) && (!has_result); i++) { + drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[i]); + + if (strcmp(prop->name, name) == 0) { + *value_out = props->prop_values[i]; + has_result = true; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + + if (!has_result) { + fprintf(stderr, "[compositor] Could not find any property with name %32s\n", name); + } + + return has_result? 0 : ENOENT; +} + +static int get_plane_property_value( + const uint32_t plane_id, + char name[32], + uint64_t *value_out +) { + return get_property_value( + plane_id, + DRM_MODE_OBJECT_ANY, + name, + value_out + ); +} + +/// Destroy the OpenGL ES framebuffer-object that is associated with this +/// EGL Window Surface backed backing store +static void destroy_window_surface_backing_store_fb(void *userdata) { + struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; + + +} + +/// Destroy the OpenGL ES framebuffer-object that is associated with this +/// DRM FB backed backing store +static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { + struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; + + +} + + +/// Create a flutter backing store that is backed by the +/// EGL window surface (created using libgbm). +/// I.e. create a EGL window surface and set fbo_id to 0. +static int create_window_surface_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *user_data +) { + struct backing_store_metadata *meta; + + meta = malloc(sizeof(*meta)); + if (meta == NULL) { + perror("[compositor] Could not allocate metadata for backing store. malloc"); + return ENOMEM; + } + memset(meta, 0, sizeof(*meta)); + + meta->type = kWindowSurface; + meta->window_surface.current_front_bo = drm.current_bo; + meta->window_surface.gbm_surface = gbm.surface; + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; + backing_store_out->open_gl.framebuffer.name = 0; + backing_store_out->open_gl.framebuffer.destruction_callback = destroy_window_surface_backing_store_fb; + backing_store_out->open_gl.framebuffer.user_data = meta; + backing_store_out->user_data = meta; + + return 0; +} + +/// Create a flutter backing store that +static int create_drm_fb_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *user_data +) { + struct backing_store_metadata *meta; + struct drm_fb_backing_store *inner; + uint32_t plane_id; + EGLint egl_error; + GLenum gl_error; + int ok; + + plane_id = find_next_unused_drm_plane(); + if (!plane_id) { + fprintf(stderr, "[compositor] Could not find an unused DRM overlay plane for flutter backing store creation.\n"); + return false; + } + + meta = malloc(sizeof(struct backing_store_metadata)); + if (meta == NULL) { + perror("[compositor] Could not allocate backing store metadata, malloc"); + return false; + } + + memset(meta, 0, sizeof(*meta)); + + meta->type = kDrmFb; + meta->drm_fb.drm_plane_id = plane_id; + inner = &meta->drm_fb; + + eglGetError(); + glGetError(); + + inner->egl_image = egl.createDRMImageMESA(egl.display, (const EGLint[]) { + EGL_WIDTH, (int) config->size.width, + EGL_HEIGHT, (int) config->size.height, + EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA, + EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA, + EGL_NONE + }); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[compositor] error creating DRM EGL Image for flutter backing store, eglCreateDRMImageMESA: %ld\n", egl_error); + return false; + } + + egl.exportDRMImageMESA(egl.display, inner->egl_image, NULL, &inner->gem_handle, &inner->gem_stride); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[compositor] error getting handle & stride for DRM EGL Image, eglExportDRMImageMESA: %d\n", egl_error); + return false; + } + + glGenRenderbuffers(1, &inner->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error generating renderbuffers for flutter backing store, glGenRenderbuffers: %ld\n", gl_error); + return false; + } + + glBindRenderbuffer(GL_RENDERBUFFER, inner->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); + return false; + } + + gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, inner->egl_image); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding DRM EGL Image to renderbuffer, glEGLImageTargetRenderbufferStorageOES: %ld\n", gl_error); + return false; + } + + glGenFramebuffers(1, &inner->gl_fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, inner->gl_fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); + return false; + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, inner->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); + return false; + } + + GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + ok = drmModeAddFB2( + drm.fd, + (uint32_t) config->size.width, + (uint32_t) config->size.height, + DRM_FORMAT_ARGB8888, + (const uint32_t*) &(uint32_t[4]) { + inner->gem_handle, + 0, + 0, + 0 + }, + (const uint32_t*) &(uint32_t[4]) { + inner->gem_stride, 0, 0, 0 + }, + (const uint32_t*) &(uint32_t[4]) { + 0, 0, 0, 0 + }, + &inner->drm_fb_id, + 0 + ); + if (ok == -1) { + perror("[compositor] Could not make DRM fb from EGL Image, drmModeAddFB2"); + return false; + } + + ok = get_plane_property_value(inner->drm_plane_id, "zpos", &inner->current_zpos); + if (ok != 0) { + return false; + } + + // TODO: Only reflect Y when running on Raspberry Pi. + ok = set_plane_property_value(inner->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); + if (ok == -1) { + perror("[compositor] Could not set rotation & reflection of hardware plane. drmModeObjectSetProperty"); + return false; + } + + // We don't scan out anything yet. Just attach the FB to this plane to reserve it. + // Compositing details (offset, size, zpos) are set in the present + // procedure. + ok = drmModeSetPlane( + drm.fd, + inner->drm_plane_id, + drm.crtc_id, + inner->drm_fb_id, + 0, + 0, 0, 0, 0, + 0, 0, ((uint32_t) config->size.width) << 16, ((uint32_t) config->size.height) << 16 + ); + if (ok == -1) { + perror("[compositor] Could not attach DRM framebuffer to hardware plane. drmModeSetPlane"); + return false; + } + + get_plane_property_value(inner->drm_plane_id, "zpos", (uint64_t*) &inner->current_zpos); + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; + backing_store_out->open_gl.framebuffer.name = inner->gl_fbo_id; + backing_store_out->open_gl.framebuffer.destruction_callback = destroy_drm_fb_backing_store_gl_fb; + backing_store_out->open_gl.framebuffer.user_data = meta; + backing_store_out->user_data = meta; + + return 0; +} + +static bool create_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *user_data +) { + int ok; + + if (should_create_window_surface_backing_store) { + // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY + // plane. That backing store isn't really a backing store at all, it's + // FBO id is 0, so it's actually rendering to the window surface. + ok = create_window_surface_backing_store( + config, + backing_store_out, + user_data + ); + + if (ok != 0) { + return false; + } + + should_create_window_surface_backing_store = false; + } else { + // After the primary plane backing store was created, + // we only create overlay plane backing stores. I.e. + // backing stores, which have a FBO, that have a + // color-attached RBO, that has a DRM EGLImage as the storage, + // which in turn has a DRM FB associated with it. + ok = create_drm_fb_backing_store( + config, + backing_store_out, + user_data + ); + + if (ok != 0) { + return false; + } + } + + return true; +} + +static int collect_window_surface_backing_store( + const FlutterBackingStore *store, + struct backing_store_metadata *meta +) { + struct window_surface_backing_store *inner = &meta->window_surface; + return 0; +} + +static int collect_drm_fb_backing_store( + const FlutterBackingStore *store, + struct backing_store_metadata *meta +) { + struct drm_fb_backing_store *inner = &meta->drm_fb; + return 0; +} + +static bool collect_backing_store( + const FlutterBackingStore *renderer, + void *user_data +) { + int ok; + + if (renderer->type == kFlutterBackingStoreTypeOpenGL && + renderer->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer) { + struct backing_store_metadata *meta = renderer->open_gl.framebuffer.user_data; + + if (meta->type == kWindowSurface) { + ok = collect_window_surface_backing_store(renderer, meta); + if (ok != 0) { + return false; + } + + should_create_window_surface_backing_store = true; + } else if (meta->type == kDrmFb) { + ok = collect_drm_fb_backing_store(renderer, meta); + if (ok != 0) { + return false; + } + } else { + fprintf(stderr, "[compositor] Unsupported flutter backing store backend: %d\n", meta->type); + return false; + } + } else { + fprintf(stderr, "[compositor] Unsupported flutter backing store type\n"); + return false; + } + + return true; +} + + +static int present_window_surface_backing_store( + struct window_surface_backing_store *backing_store, + int offset_x, + int offset_y, + int width, + int height +) { + struct gbm_bo *next_front_bo; + uint32_t next_front_fb_id; + int ok; + + next_front_bo = gbm_surface_lock_front_buffer(backing_store->gbm_surface); + next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); + + // workaround for #38 + if (!drm.disable_vsync) { + ok = drmModePageFlip(drm.fd, drm.crtc_id, next_front_fb_id, DRM_MODE_PAGE_FLIP_EVENT, backing_store->current_front_bo); + if (ok) { + perror("failed to queue page flip"); + return false; + } + } else { + ok = drmModeSetCrtc(drm.fd, drm.crtc_id, next_front_fb_id, 0, 0, &drm.connector_id, 1, drm.mode); + if (ok == -1) { + perror("failed swap buffers"); + return false; + } + } + + // TODO: move this to the page flip handler. + // We can only be sure the buffer can be released when the buffer swap + // ocurred. + gbm_surface_release_buffer(backing_store->gbm_surface, backing_store->current_front_bo); + backing_store->current_front_bo = (struct gbm_bo *) next_front_bo; + + return 0; +} + +static int present_drm_fb_backing_store( + struct drm_fb_backing_store *backing_store, + int offset_x, + int offset_y, + int width, + int height, + int zpos +) { + int ok; + + if (zpos != backing_store->current_zpos) { + ok = set_plane_property_value(backing_store->drm_plane_id, "zpos", zpos); + if (ok != 0) { + perror("[compositor] Could not update zpos of hardware layer. drmModeObjectSetProperty"); + return errno; + } + } + + ok = drmModeSetPlane( + drm.fd, + backing_store->drm_plane_id, + drm.crtc_id, + backing_store->drm_fb_id, + 0, + offset_x, offset_y, width, height, + 0, 0, ((uint16_t) width) << 16, ((uint16_t) height) << 16 + ); + if (ok == -1) { + perror("[compositor] Could not update overlay plane for presenting a DRM FB backed backing store. drmModeSetPlane"); + return errno; + } + + return 0; +} + +static struct view_cb_data *get_data_for_view_id(int64_t view_id) { + struct view_cb_data *data; + + cpset_lock(&flutterpi_compositor.cbs); + + for_each_pointer_in_cpset(&flutterpi_compositor.cbs, data) { + if (data->view_id == view_id) { + cpset_unlock(&flutterpi_compositor.cbs); + return data; + } + } + + cpset_unlock(&flutterpi_compositor.cbs); + return NULL; +} + +static int present_platform_view( + int64_t view_id, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos +) { + struct view_cb_data *data; + + data = get_data_for_view_id(view_id); + if (data == NULL) { + return EINVAL; + } + + return data->present_cb( + view_id, + mutations, + num_mutations, + offset_x, + offset_y, + width, + height, + zpos, + data->userdata + ); +} + +static bool present_layers_callback( + const FlutterLayer **layers, + size_t layers_count, + void *user_data +) { + int window_surface_index; + int ok; + + /* + printf("[compositor] present_layers_callback(\n" + " layers_count: %lu,\n" + " layers = {\n", + layers_count); + for (int i = 0; i < layers_count; i++) { + printf( + " [%d] = {\n" + " type: %s,\n" + " offset: {x: %f, y: %f},\n" + " size: %f x %f,\n", + i, + layers[i]->type == kFlutterLayerContentTypeBackingStore ? "backing store" : "platform view", + layers[i]->offset.x, + layers[i]->offset.y, + layers[i]->size.width, + layers[i]->size.height + ); + + if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { + struct backing_store_metadata *meta = layers[i]->backing_store->user_data; + + printf(" %s\n", meta->type == kWindowSurface ? "window surface" : "drm fb"); + } else if (layers[i]->type == kFlutterLayerContentTypePlatformView) { + printf( + " platform_view: {\n" + " identifier: %lld\n" + " mutations: {\n", + layers[i]->platform_view->identifier + ); + for (int j = 0; j < layers[i]->platform_view->mutations_count; j++) { + const FlutterPlatformViewMutation *mut = layers[i]->platform_view->mutations[j]; + + printf( + " [%d] = {\n" + " type: %s,\n", + j, + mut->type == kFlutterPlatformViewMutationTypeOpacity ? "opacity" : + mut->type == kFlutterPlatformViewMutationTypeClipRect ? "clip rect" : + mut->type == kFlutterPlatformViewMutationTypeClipRoundedRect ? "clip rounded rect" : + mut->type == kFlutterPlatformViewMutationTypeTransformation ? "transformation" : + "(?)" + ); + + if (mut->type == kFlutterPlatformViewMutationTypeOpacity) { + printf( + " opacity: %f\n", + mut->opacity + ); + } else if (mut->type == kFlutterPlatformViewMutationTypeClipRect) { + printf( + " clip_rect: {bottom: %f, left: %f, right: %f, top: %f}\n", + mut->clip_rect.bottom, + mut->clip_rect.left, + mut->clip_rect.right, + mut->clip_rect.top + ); + } else if (mut->type == kFlutterPlatformViewMutationTypeClipRoundedRect) { + printf( + " clip_rounded_rect: {\n" + " lower_left_corner_radius: %f,\n" + " lower_right_corner_radius: %f,\n" + " upper_left_corner_radius: %f,\n" + " upper_right_corner_radius: %f,\n" + " rect: {bottom: %f, left: %f, right: %f, top: %f}\n" + " }\n", + mut->clip_rounded_rect.lower_left_corner_radius, + mut->clip_rounded_rect.lower_right_corner_radius, + mut->clip_rounded_rect.upper_left_corner_radius, + mut->clip_rounded_rect.upper_right_corner_radius, + mut->clip_rounded_rect.rect.bottom, + mut->clip_rounded_rect.rect.left, + mut->clip_rounded_rect.rect.right, + mut->clip_rounded_rect.rect.top + ); + } else if (mut->type == kFlutterPlatformViewMutationTypeTransformation) { + printf( + " transformation\n" + ); + } + + printf(" },\n"); + } + printf(" }\n"); + } + printf(" },\n"); + } + printf(" }\n)\n"); + */ + + FlutterEngineTraceEventDurationBegin("present"); + + eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); + eglSwapBuffers(egl.display, egl.surface); + eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + /// find the index of the window surface. + /// the window surface's zpos can't change, so we need to + /// normalize all other backing stores' zpos around the + /// window surfaces zpos. + for (int i = 0; i < layers_count; i++) { + if (layers[i]->type == kFlutterLayerContentTypeBackingStore + && layers[i]->backing_store->type == kFlutterBackingStoreTypeOpenGL + && layers[i]->backing_store->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer + && ((struct backing_store_metadata *) layers[i]->backing_store->user_data)->type == kWindowSurface) { + + window_surface_index = i; + + break; + } + } + + for (int i = 0; i < layers_count; i++) { + const FlutterLayer *layer = layers[i]; + + if (layer->type == kFlutterLayerContentTypeBackingStore) { + const FlutterBackingStore *backing_store = layer->backing_store; + struct backing_store_metadata *meta = backing_store->user_data; + + if (meta->type == kWindowSurface) { + ok = present_window_surface_backing_store( + &meta->window_surface, + (int) layer->offset.x, + (int) layer->offset.y, + (int) layer->size.width, + (int) layer->size.height + ); + } else if (meta->type == kDrmFb) { + continue; + ok = present_drm_fb_backing_store( + &meta->drm_fb, + (int) layer->offset.x, + (int) layer->offset.y, + (int) layer->size.width, + (int) layer->size.height, + i - window_surface_index + ); + } + } else if (layer->type == kFlutterLayerContentTypePlatformView) { + ok = present_platform_view( + layer->platform_view->identifier, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + i - window_surface_index + ); + } else { + fprintf(stderr, "[compositor] Unsupported flutter layer type: %d\n", layer->type); + } + } + + FlutterEngineTraceEventDurationEnd("present"); + + return true; +} + +int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_present_cb cb, void *userdata) { + struct view_cb_data *entry; + + cpset_lock(&flutterpi_compositor.cbs); + + for_each_pointer_in_cpset(&flutterpi_compositor.cbs, entry) { + if (entry->view_id == view_id) { + break; + } + } + + if (entry && !cb) { + cpset_remove_locked(&flutterpi_compositor.cbs, entry); + } else if (!entry && cb) { + entry = calloc(1, sizeof(*entry)); + if (!entry) { + cpset_unlock(&flutterpi_compositor.cbs); + return ENOMEM; + } + + entry->view_id = view_id; + entry->present_cb = cb; + entry->userdata = userdata; + + cpset_put_locked(&flutterpi_compositor.cbs, entry); + } + + return cpset_unlock(&flutterpi_compositor.cbs); +} + +const FlutterCompositor flutter_compositor = { + .struct_size = sizeof(FlutterCompositor), + .create_backing_store_callback = create_backing_store, + .collect_backing_store_callback = collect_backing_store, + .present_layers_callback = present_layers_callback, + .user_data = &flutterpi_compositor +}; \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index ce76a03c..5511bb28 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -26,16 +26,20 @@ #include #include #include +#define EGL_EGLEXT_PROTOTYPES #include #include +#define GL_GLEXT_PROTOTYPES #include - +#include #include #include +#include #include #include #include +#include //#include #include #include @@ -107,39 +111,10 @@ enum device_orientation orientation; /// is used to determine if width/height should be swapped when sending a WindowMetrics event to flutter) int rotation = 0; -struct { - char device[PATH_MAX]; - bool has_device; - int fd; - uint32_t connector_id; - drmModeModeInfo *mode; - uint32_t crtc_id; - size_t crtc_index; - struct gbm_bo *previous_bo; - drmEventContext evctx; - bool disable_vsync; -} 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; - char *renderer; - - 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 drm drm = {0}; +struct gbm gbm = {0}; +struct egl egl = {0}; +struct gl gl = {0}; struct { char asset_bundle_path[240]; @@ -180,13 +155,12 @@ pthread_cond_t task_added = PTHREAD_COND_INITIALIZER; FlutterEngine engine; _Atomic bool engine_running = false; - /********************* * FLUTTER CALLBACKS * *********************/ bool make_current(void* userdata) { - if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) { - fprintf(stderr, "make_current: could not make the context current.\n"); + if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.flutter_render_context) != EGL_TRUE) { + fprintf(stderr, "make_current: could not make the context current. eglMakeCurrent: %d\n", eglGetError()); return false; } @@ -208,106 +182,21 @@ void pageflip_handler(int fd, unsigned int frame, unsigned int sec, unsigned .vblank_ns = sec*1000000000ull + usec*1000ull, }); } -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; - - // if the buffer object already has some userdata associated with it, - // it's the framebuffer we allocated. - struct drm_fb *fb = gbm_bo_get_user_data(bo); - if (fb) return fb; - - // if there's no framebuffer for the bo, we need to create one. - 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); - - 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, "drm_fb_get_from_bo: 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, "drm_fb_get_from_bo: 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; - - FlutterEngineTraceEventDurationBegin("present"); - - eglSwapBuffers(egl.display, egl.surface); - next_bo = gbm_surface_lock_front_buffer(gbm.surface); - fb = drm_fb_get_from_bo(next_bo); - - // workaround for #38 - if (!drm.disable_vsync) { - ok = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, drm.previous_bo); - if (ok) { - perror("failed to queue page flip"); - return false; - } - } else { - ok = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok == -1) { - perror("failed swap buffers\n"); - return false; - } - } - - gbm_surface_release_buffer(gbm.surface, drm.previous_bo); - drm.previous_bo = (struct gbm_bo *) next_bo; - - FlutterEngineTraceEventDurationEnd("present"); - + // NOP return true; } uint32_t fbo_callback(void* userdata) { return 0; } +bool make_resource_current(void *userdata) { + if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl.flutter_resource_uploading_context) != EGL_TRUE) { + fprintf(stderr, "make_resource_current: could not make the resource context current. eglMakeCurrent: %d\n", eglGetError()); + return false; + } + + return true; +} void cut_word_from_string(char* string, char* word) { size_t word_length = strlen(word); char* word_in_str = strstr(string, word); @@ -583,10 +472,20 @@ bool message_loop(void) { } free(task->message); - } else if (FlutterEngineRunTask(engine, &task->task) != kSuccess) { - fprintf(stderr, "Error running platform task\n"); - return false; - }; + } else if (task->type == kRegisterExternalTexture) { + FlutterEngineRegisterExternalTexture(engine, task->texture_id); + } else if (task->type == kUnregisterExternalTexture) { + FlutterEngineUnregisterExternalTexture(engine, task->texture_id); + } else if (task->type == kMarkExternalTextureFrameAvailable) { + FlutterEngineMarkExternalTextureFrameAvailable(engine, task->texture_id); + } else if (task->type == kFlutterTask) { + if (FlutterEngineRunTask(engine, &task->task) != kSuccess) { + fprintf(stderr, "Error running platform task\n"); + return false; + } + } else if (task->type == kGeneric) { + task->callback(task->userdata); + } free(task); } @@ -596,10 +495,13 @@ bool message_loop(void) { void post_platform_task(struct flutterpi_task *task) { struct flutterpi_task *to_insert; - to_insert = malloc(sizeof(struct flutterpi_task)); - if (!to_insert) return; + to_insert = malloc(sizeof(*task)); + if (to_insert == NULL) { + return; + } + + memcpy(to_insert, task, sizeof(*task)); - memcpy(to_insert, task, sizeof(struct flutterpi_task)); pthread_mutex_lock(&tasklist_lock); struct flutterpi_task* this = &tasklist; while ((this->next) != NULL && (to_insert->target_time > this->next->target_time)) @@ -736,6 +638,19 @@ bool setup_paths(void) { #undef PATH_EXISTS } +int load_gl_procs(void) { + LOAD_EGL_PROC(getPlatformDisplay); + LOAD_EGL_PROC(createPlatformWindowSurface); + LOAD_EGL_PROC(createPlatformPixmapSurface); + LOAD_EGL_PROC(createDRMImageMESA); + LOAD_EGL_PROC(exportDRMImageMESA); + + LOAD_GL_PROC(EGLImageTargetTexture2DOES); + LOAD_GL_PROC(EGLImageTargetRenderbufferStorageOES); + + return 0; +} + bool init_display(void) { /********************** * DRM INITIALIZATION * @@ -967,12 +882,64 @@ bool init_display(void) { return false; } + drmSetClientCap(drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + + printf("drm_crtcs = {\n"); + drm.crtc_index = 0; for (i = 0; i < resources->count_crtcs; i++) { - if (resources->crtcs[i] == drm.crtc_id) { + if ((resources->crtcs[i] == drm.crtc_id) && (!drm.crtc_index)) { drm.crtc_index = i; - break; } + + drmModeCrtc *crtc = drmModeGetCrtc(drm.fd, resources->crtcs[i]); + drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, crtc->crtc_id, DRM_MODE_OBJECT_ANY); + + printf(" [%d] = {,\n", i); + printf(" crtc_id: %lu\n", crtc->crtc_id); + printf(" properties: {\n"); + for (int j = 0; j < props->count_props; j++) { + printf(" [0x%08lX] = 0x%016llX,\n", props->props[j], props->prop_values[j]); + } + printf(" },\n"); + printf(" },\n"); + + drmModeFreeObjectProperties(props); + drmModeFreeCrtc(crtc); + } + printf("}\n"); + + drmModePlaneResPtr plane_res = drmModeGetPlaneResources(drm.fd); + + printf("drm_planes = {\n"); + for (i = 0; i < plane_res->count_planes; i++) { + drmModePlane *plane = drmModeGetPlane(drm.fd, plane_res->planes[i]); + drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, plane_res->planes[i], DRM_MODE_OBJECT_ANY); + + printf(" [%d] = {,\n", i); + printf(" plane_id: %lu\n", plane->plane_id); + printf(" x: %lu, y: %lu,\n", plane->x, plane->y); + printf(" crtc_x: %lu,\n crtc_y: %lu,\n", plane->crtc_x, plane->crtc_y); + printf(" crtc_id: %lu,\n", plane->crtc_id); + printf(" fb_id: %lu,\n", plane->fb_id); + printf(" gamma_size: %lu,\n", plane->gamma_size); + printf(" possible_crtcs: %lu,\n", plane->possible_crtcs); + printf(" properties: {\n"); + for (int j = 0; j < props->count_props; j++) { + drmModePropertyPtr prop = drmModeGetProperty(drm.fd, props->props[j]); + + printf(" %s: 0x%016llX,\n", prop->name, props->prop_values[j]); + + drmModeFreeProperty(prop); + } + printf(" },\n"); + printf(" },\n"); + + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); } + printf("}\n"); + + drmModeFreePlaneResources(plane_res); drmModeFreeResources(resources); @@ -985,7 +952,7 @@ bool init_display(void) { **********************/ printf("Creating GBM device\n"); gbm.device = gbm_create_device(drm.fd); - gbm.format = DRM_FORMAT_XRGB8888; + gbm.format = DRM_FORMAT_ARGB8888; gbm.surface = NULL; gbm.modifier = DRM_FORMAT_MOD_LINEAR; @@ -1016,10 +983,6 @@ bool init_display(void) { const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 1, - EGL_GREEN_SIZE, 1, - EGL_BLUE_SIZE, 1, - EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE @@ -1030,16 +993,27 @@ bool init_display(void) { printf("Querying EGL client extensions...\n"); egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - egl.eglGetPlatformDisplayEXT = (void*) eglGetProcAddress("eglGetPlatformDisplayEXT"); + printf("Loading EGL / GL ES procedure addresses...\n"); + ok = load_gl_procs(); + if (ok != 0) { + fprintf(stderr, "Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); + return false; + } + 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); - +#ifdef EGL_KHR_platform_gbm + if (egl.getPlatformDisplay) { + egl.display = egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); + } else +#endif + { + egl.display = eglGetDisplay((void*) gbm.device); + } + if (!egl.display) { fprintf(stderr, "Couldn't get EGL display\n"); return false; } - printf("Initializing EGL...\n"); if (!eglInitialize(egl.display, &major, &minor)) { @@ -1049,7 +1023,7 @@ bool init_display(void) { 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; + //egl.modifiers_supported = strstr(egl_exts_dpy, "EGL_EXT_image_dma_buf_import_modifiers") != NULL; printf("Using display %p with EGL version %d.%d\n", egl.display, major, minor); @@ -1081,6 +1055,99 @@ bool init_display(void) { configs = malloc(count * sizeof(EGLConfig)); if (!configs) return false; + + /* + eglGetConfigs(egl.display, configs, count * sizeof(EGLConfig), &count); + for (int i = 0; i < count; i++) { + EGLint value; + +# define GET_ATTRIB(attrib) eglGetConfigAttrib(egl.display, configs[i], attrib, &value) +# define PRINT_ATTRIB_STR(attrib, string) printf(" " #attrib ": %s,\n", string) +# define PRINT_ATTRIB(attrib, format, ...) printf(" " #attrib ": " format ",\n", __VA_ARGS__) +# define LOG_ATTRIB(attrib) \ + do { \ + eglGetConfigAttrib(egl.display, configs[i], attrib, &value); \ + printf(" " #attrib ": %d,\n", value); \ + } while (false) + printf("fb_config[%i] = {\n", i); + + GET_ATTRIB(EGL_COLOR_BUFFER_TYPE); + PRINT_ATTRIB_STR( + EGL_COLOR_BUFFER_TYPE, + value == EGL_RGB_BUFFER ? "EGL_RGB_BUFFER" : "EGL_LUMINANCE_BUFFER" + ); + + LOG_ATTRIB(EGL_RED_SIZE); + LOG_ATTRIB(EGL_GREEN_SIZE); + LOG_ATTRIB(EGL_BLUE_SIZE); + LOG_ATTRIB(EGL_ALPHA_SIZE); + LOG_ATTRIB(EGL_DEPTH_SIZE); + LOG_ATTRIB(EGL_STENCIL_SIZE); + LOG_ATTRIB(EGL_ALPHA_MASK_SIZE); + LOG_ATTRIB(EGL_LUMINANCE_SIZE); + + LOG_ATTRIB(EGL_BUFFER_SIZE); + + GET_ATTRIB(EGL_NATIVE_RENDERABLE); + PRINT_ATTRIB_STR( + EGL_NATIVE_RENDERABLE, + value ? "true" : "false" + ); + + LOG_ATTRIB(EGL_NATIVE_VISUAL_TYPE); + + GET_ATTRIB(EGL_NATIVE_VISUAL_ID); + PRINT_ATTRIB( + EGL_NATIVE_VISUAL_ID, + "%4s", + &value + ); + + LOG_ATTRIB(EGL_BIND_TO_TEXTURE_RGB); + LOG_ATTRIB(EGL_BIND_TO_TEXTURE_RGBA); + + GET_ATTRIB(EGL_CONFIG_CAVEAT); + PRINT_ATTRIB_STR( + EGL_CONFIG_CAVEAT, + value == EGL_NONE ? "EGL_NONE" : + value == EGL_SLOW_CONFIG ? "EGL_SLOW_CONFIG" : + value == EGL_NON_CONFORMANT_CONFIG ? "EGL_NON_CONFORMANT_CONFIG" : + "(?)" + ); + + LOG_ATTRIB(EGL_CONFIG_ID); + LOG_ATTRIB(EGL_CONFORMANT); + + LOG_ATTRIB(EGL_LEVEL); + + LOG_ATTRIB(EGL_MAX_PBUFFER_WIDTH); + LOG_ATTRIB(EGL_MAX_PBUFFER_HEIGHT); + LOG_ATTRIB(EGL_MAX_PBUFFER_PIXELS); + LOG_ATTRIB(EGL_MAX_SWAP_INTERVAL); + LOG_ATTRIB(EGL_MIN_SWAP_INTERVAL); + + LOG_ATTRIB(EGL_RENDERABLE_TYPE); + LOG_ATTRIB(EGL_SAMPLE_BUFFERS); + LOG_ATTRIB(EGL_SAMPLES); + + LOG_ATTRIB(EGL_SURFACE_TYPE); + + GET_ATTRIB(EGL_TRANSPARENT_TYPE); + PRINT_ATTRIB_STR( + EGL_TRANSPARENT_TYPE, + value == EGL_NONE ? "EGL_NONE" : + "EGL_TRANSPARENT_RGB" + ); + + LOG_ATTRIB(EGL_TRANSPARENT_RED_VALUE); + LOG_ATTRIB(EGL_TRANSPARENT_GREEN_VALUE); + LOG_ATTRIB(EGL_TRANSPARENT_BLUE_VALUE); + + printf("}\n"); + +# undef LOG_ATTRIB + } + */ printf("Finding EGL configs with appropriate attributes...\n"); if (!eglChooseConfig(egl.display, config_attribs, configs, count, &matched) || !matched) { @@ -1111,13 +1178,38 @@ bool init_display(void) { } - 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"); + printf("Creating EGL contexts...\n"); + egl.root_context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); + if (egl.root_context == NULL) { + fprintf(stderr, "failed to create OpenGL ES root context\n"); return false; } + egl.flutter_render_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); + if (egl.flutter_render_context == NULL) { + fprintf(stderr, "failed to create OpenGL ES context for flutter rendering\n"); + return false; + } + + egl.flutter_resource_uploading_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); + if (egl.flutter_resource_uploading_context == NULL) { + fprintf(stderr, "failed to create OpenGL ES context for flutter resource uploads\n"); + return false; + } + + egl.compositor_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); + if (egl.compositor_context == NULL) { + fprintf(stderr, "failed to create OpenGL ES context for compositor\n"); + return false; + } + +#ifdef BUILD_VIDEO_PLAYER_PLUGIN + egl.vidpp_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); + if (egl.vidpp_context == NULL) { + fprintf(stderr, "failed to OpenGL ES context for video player plugin texture uploads\n"); + return false; + } +#endif printf("Creating EGL window surface...\n"); egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType) gbm.surface, NULL); @@ -1126,8 +1218,8 @@ bool init_display(void) { 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"); + if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context)) { + fprintf(stderr, "Could not make OpenGL ES root context current to get OpenGL information\n"); return false; } @@ -1165,18 +1257,19 @@ bool init_display(void) { printf("Swapping buffers...\n"); eglSwapBuffers(egl.display, egl.surface); + printf("Locking front buffer...\n"); - drm.previous_bo = gbm_surface_lock_front_buffer(gbm.surface); + drm.current_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) { + uint32_t current_fb_id = gbm_bo_get_drm_fb_id(drm.current_bo); + if (!current_fb_id) { 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); + ok = drmModeSetCrtc(drm.fd, drm.crtc_id, current_fb_id, 0, 0, &drm.connector_id, 1, drm.mode); if (ok) { fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); return false; @@ -1213,8 +1306,12 @@ bool init_application(void) { 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.make_resource_current = make_resource_current; flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; - flutter.renderer_config.open_gl.surface_transformation = transformation_callback; + flutter.renderer_config.open_gl.surface_transformation + = transformation_callback; + flutter.renderer_config.open_gl.gl_external_texture_frame_callback + = texreg_gl_external_texture_frame_callback; // configure flutter flutter.args.struct_size = sizeof(FlutterProjectArgs); @@ -1240,7 +1337,8 @@ bool init_application(void) { .post_task_callback = &flutter_post_platform_task } }; - + flutter.args.compositor = &flutter_compositor; + // only enable vsync if the kernel supplies valid vblank timestamps uint64_t ns = 0; ok = drmCrtcGetSequence(drm.fd, drm.crtc_id, NULL, &ns); @@ -1656,6 +1754,7 @@ void on_evdev_input(fd_set fds, size_t n_ready_fds) { if (i_flutterevent == 0) return; // now, send the data to the flutter engine + // TODO: do this on the main thread ok = kSuccess == FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent); if (!ok) { fprintf(stderr, "could not send pointer events to flutter engine\n"); @@ -1693,57 +1792,63 @@ void on_console_input(void) { } } void *io_loop(void *userdata) { + fd_set read_fds; + fd_set write_fds; + fd_set except_fds; int n_ready_fds; - fd_set fds; int nfds; - // put all file-descriptors in the `fds` fd set nfds = 0; - FD_ZERO(&fds); + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + FD_ZERO(&except_fds); for (int i = 0; i < n_input_devices; i++) { - FD_SET(input_devices[i].fd, &fds); + FD_SET(input_devices[i].fd, &read_fds); if (input_devices[i].fd + 1 > nfds) nfds = input_devices[i].fd + 1; } - FD_SET(drm.fd, &fds); + FD_SET(drm.fd, &read_fds); if (drm.fd + 1 > nfds) nfds = drm.fd + 1; - //FD_SET(STDIN_FILENO, &fds); + FD_SET(STDIN_FILENO, &read_fds); - const fd_set const_fds = fds; + fd_set const_read_fds = read_fds; + fd_set const_write_fds = write_fds; + fd_set const_except_fds = except_fds; while (engine_running) { // wait for any device to offer new data, // but only if no file descriptors have new data. - n_ready_fds = select(nfds, &fds, NULL, NULL, NULL); - + n_ready_fds = select(nfds, &read_fds, &write_fds, &except_fds, NULL); + if (n_ready_fds == -1) { perror("error while waiting for I/O"); return NULL; } else if (n_ready_fds == 0) { - perror("reached EOF while waiting for I/O"); - return NULL; + continue; } - if (FD_ISSET(drm.fd, &fds)) { + if (FD_ISSET(drm.fd, &read_fds)) { drmHandleEvent(drm.fd, &drm.evctx); - FD_CLR(drm.fd, &fds); + FD_CLR(drm.fd, &read_fds); n_ready_fds--; } - if (FD_ISSET(STDIN_FILENO, &fds)) { + if (FD_ISSET(STDIN_FILENO, &read_fds)) { on_console_input(); - FD_CLR(STDIN_FILENO, &fds); + FD_CLR(STDIN_FILENO, &read_fds); n_ready_fds--; } if (n_ready_fds > 0) { - on_evdev_input(fds, n_ready_fds); + on_evdev_input(read_fds, n_ready_fds); } - fds = const_fds; + read_fds = const_read_fds; + write_fds = const_write_fds; + except_fds = const_except_fds; } return NULL; diff --git a/src/platformchannel.c b/src/platformchannel.c index f0adf61e..384f038a 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1095,6 +1095,9 @@ int platch_respond_not_implemented(FlutterPlatformMessageResponseHandle *handle) }); } +/**************************** + * STANDARD METHOD CHANNELS * + ****************************/ int platch_respond_success_std(FlutterPlatformMessageResponseHandle *handle, struct std_value *return_value) { @@ -1141,6 +1144,10 @@ int platch_respond_native_error_std(FlutterPlatformMessageResponseHandle *handle } +/************************ + * JSON METHOD CHANNELS * + ************************/ + int platch_respond_success_json(FlutterPlatformMessageResponseHandle *handle, struct json_value *return_value) { return platch_respond( @@ -1184,7 +1191,97 @@ int platch_respond_native_error_json(FlutterPlatformMessageResponseHandle *handl ); } +/************************** + * PIGEON METHOD CHANNELS * + **************************/ +int platch_respond_success_pigeon( + FlutterPlatformMessageResponseHandle *handle, + struct std_value *return_value +) { + return platch_respond( + handle, + &(struct platch_obj) { + .codec = kStandardMessageCodec, + .std_value = { + .type = kStdMap, + .size = 1, + .keys = (struct std_value[1]) { + STDSTRING("result") + }, + .values = return_value != NULL ? + return_value : + (struct std_value[1]) {STDNULL} + } + } + ); +} + +int platch_respond_error_pigeon( + FlutterPlatformMessageResponseHandle *handle, + char *error_code, + char *error_msg, + struct std_value *error_details +) { + return platch_respond( + handle, + &(struct platch_obj) { + .codec = kStandardMessageCodec, + .std_value = { + .type = kStdMap, + .size = 1, + .keys = (struct std_value[1]) { + STDSTRING("error") + }, + .values = (struct std_value[1]) { + { + .type = kStdMap, + .size = 3, + .keys = (struct std_value[3]) { + STDSTRING("code"), + STDSTRING("message"), + STDSTRING("details") + }, + .values = (struct std_value[3]) { + STDSTRING(error_code), + STDSTRING(error_msg), + error_details != NULL ? + *error_details : + STDNULL + } + } + } + } + } + ); +} + +int platch_respond_illegal_arg_pigeon( + FlutterPlatformMessageResponseHandle *handle, + char *error_msg +) { + return platch_respond_error_pigeon( + handle, + "illegalargument", + error_msg, + NULL + ); +} +int platch_respond_native_error_pigeon( + FlutterPlatformMessageResponseHandle *handle, + int _errno +) { + return platch_respond_error_pigeon( + handle, + "nativeerror", + strerror(_errno), + &STDINT32(_errno) + ); +} + +/*************************** + * STANDARD EVENT CHANNELS * + ***************************/ int platch_send_success_event_std(char *channel, struct std_value *event_value) { return platch_send( channel, @@ -1214,7 +1311,9 @@ int platch_send_error_event_std(char *channel, ); } - +/*********************** + * JSON EVENT CHANNELS * + ***********************/ int platch_send_success_event_json(char *channel, struct json_value *event_value) { return platch_send(channel, &(struct platch_obj) { diff --git a/src/pluginregistry.c b/src/pluginregistry.c index ec4b43ca..2d4f23ce 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -24,6 +24,10 @@ #ifdef BUILD_SPIDEV_PLUGIN # include #endif +#ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN +# include +#endif + struct platch_obj_recv_data { char *channel; @@ -62,7 +66,11 @@ struct flutterpi_plugin hardcoded_plugins[] = { #endif #ifdef BUILD_SPIDEV_PLUGIN - {.name = "flutter_spidev", .init = spidevp_init, .deinit = spidevp_deinit} + {.name = "flutter_spidev", .init = spidevp_init, .deinit = spidevp_deinit}, +#endif + +#ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN + {.name = "omxplayer_video_player", .init = omxpvidpp_init, .deinit = omxpvidpp_deinit}, #endif }; diff --git a/src/plugins/services.c b/src/plugins/services.c index bb9169f2..e2d16ca1 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -172,6 +172,19 @@ int services_on_receive_accessibility(char *channel, struct platch_obj *object, return platch_respond_not_implemented(responsehandle); } +int services_on_receive_platform_views(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + struct json_value *value; + struct json_value *arg = &(object->json_arg); + int ok; + + if STREQ("create", object->method) { + return platch_respond_not_implemented(responsehandle); + } else if STREQ("dispose", object->method) { + return platch_respond_not_implemented(responsehandle); + } + + return platch_respond_not_implemented(responsehandle); +} int services_init(void) { @@ -203,6 +216,12 @@ int services_init(void) { return ok; } + ok = plugin_registry_set_receiver("flutter/platform_views", kStandardMethodCall, services_on_receive_platform_views); + if (ok != 0) { + fprintf(stderr, "[services-plugin] could not set \"flutter/platform_views\" ChannelObject receiver: %s\n", strerror(ok)); + return ok; + } + printf("[services] Done.\n"); return 0; diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c new file mode 100644 index 00000000..ae4fb596 --- /dev/null +++ b/src/plugins/video_player.c @@ -0,0 +1,1487 @@ +#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE <= 200809L +# define _POSIX_C_SOURCE 200809L +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +static struct { + bool initialized; + + /// On creation of a new player, + /// the id stored here will be used and incremented. + int64_t next_unused_player_id; + + /// Collection of players. + struct omxplayer_video_player **players; + size_t size_players; + size_t n_players; + + /// The D-Bus that where omxplayer instances can be talked to. + /// Typically the session dbus. + //sd_bus *dbus; + //pthread_t dbus_processor_thread; +} omxpvidpp = {0}; + +/// libsystemd DLL +struct libsystemd libsystemd = {0}; + +/// Add a player instance into the collection in [omxpvidpp]. +static int add_player(struct omxplayer_video_player *player) { + if (omxpvidpp.n_players == omxpvidpp.size_players) { + // expand the texture map + size_t new_size = omxpvidpp.size_players? omxpvidpp.size_players*2 : 1; + + struct omxplayer_video_player **new = realloc(omxpvidpp.players, new_size*sizeof(*omxpvidpp.players)); + + if (new == NULL) { + perror("[video_player plugin] Could not expand video player map. realloc"); + return ENOMEM; + } + + memset(new + omxpvidpp.size_players, 0, (new_size - omxpvidpp.size_players)*sizeof(*omxpvidpp.players)); + + omxpvidpp.players = new; + omxpvidpp.size_players = new_size; + } + + size_t index; + for (index = 0; index < omxpvidpp.size_players; index++) { + if (omxpvidpp.players[index] == NULL) { + break; + } + } + + omxpvidpp.players[index] = player; + omxpvidpp.n_players++; + + return 0; +} + +/// Gets the player instance corresponding to a player id. +/// Returns NULL when no player with this id was found. +static struct omxplayer_video_player *get_player_by_id(int64_t player_id) { + size_t index; + for (index = 0; index < omxpvidpp.size_players; index++) { + if ((omxpvidpp.players[index]) && (omxpvidpp.players[index]->player_id == player_id)) { + break; + } + } + + if (index == omxpvidpp.size_players) { + return NULL; + } + + return omxpvidpp.players[index]; +} + +/// Gets the player instance corresponding to an event channel name. +/// Returns NULL when no player with this event channel name was found. +static struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { + size_t index; + for (index = 0; index < omxpvidpp.size_players; index++) { + if (omxpvidpp.players[index] && + STREQ(omxpvidpp.players[index]->event_channel_name, event_channel_name) + ) { + break; + } + } + + if (index == omxpvidpp.size_players) { + return NULL; + } + + return omxpvidpp.players[index]; +} + +static int get_player_id_from_map_arg( + struct std_value *arg, + int64_t *player_id_out, + FlutterPlatformMessageResponseHandle *responsehandle +) { + int ok; + + if (arg->type != kStdMap) { + ok = platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg` to be a Map" + ); + if (ok != 0) return ok; + + return EINVAL; + } + + struct std_value *id = stdmap_get_str(arg, "playerId"); + if (id == NULL || !STDVALUE_IS_INT(*id)) { + ok = platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['playerId']` to be an integer" + ); + if (ok != 0) return ok; + + return EINVAL; + } + + *player_id_out = STDVALUE_AS_INT(*id); + + return 0; +} + +static int get_player_from_map_arg( + struct std_value *arg, + struct omxplayer_video_player **player_out, + FlutterPlatformMessageResponseHandle *responsehandle +) { + struct omxplayer_video_player *player; + int64_t player_id; + int ok; + + player_id = 0; + ok = get_player_id_from_map_arg(arg, &player_id, responsehandle); + if (ok != 0) { + return ok; + } + + player = get_player_by_id(player_id); + if (player == NULL) { + ok = platch_respond_illegal_arg_std(responsehandle, "Expected `arg['playerId']` to be a valid player id."); + if (ok != 0) return ok; + + return EINVAL; + } + + *player_out = player; + + return 0; +} + +static int on_present( + int64_t view_id, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +) { + struct omxplayer_video_player *player = userdata; + + return cqueue_enqueue( + &player->mgr->task_queue, + &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .offset_x = offset_x, + .offset_y = offset_y, + .width = width, + .height = height, + .zpos = zpos + } + ); +} + +static int get_dbus_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *ret_error, + char type, + void *ret_ptr +) { + sd_bus_message *msg; + int ok; + + ok = libsystemd.sd_bus_call_method( + bus, + destination, + path, + DBUS_PROPERTY_FACE, + DBUS_PROPERTY_GET, + ret_error, + &msg, + "ss", + interface, + member + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s, %s\n", ret_error->name, ret_error->message); + return -ok; + } + + ok = libsystemd.sd_bus_message_read_basic(msg, type, ret_ptr); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s\n", strerror(-ok)); + return -ok; + } + + libsystemd.sd_bus_message_unref(msg); + + return 0; +} + + +static int omxplayer_mgr_on_dbus_message(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct omxplayer_mgr_task *task; + const char *sender, *member; + char *old_owner, *new_owner, *name; + int ok; + + task = userdata; + + sender = libsystemd.sd_bus_message_get_sender(m); + member = libsystemd.sd_bus_message_get_member(m); + + if (STREQ(sender, "org.freedesktop.DBus") && STREQ(member, "NameOwnerChanged")) { + ok = libsystemd.sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); + if (ok < 0) { + fprintf(stderr, "Could not read message"); + return -1; + } + + if STREQ(name, task->omxplayer_dbus_name) { + task->omxplayer_online = true; + } + } + + libsystemd.sd_bus_message_unref(m); + + return 0; +} + +static void *omxplayer_mgr_entry(void *userdata) { + struct omxplayer_mgr_task task; + struct concurrent_queue *q; + struct omxplayer_mgr *mgr; + sd_bus_message *msg; + sd_bus_error err; + sd_bus_slot *slot; + int64_t duration_us, video_width, video_height; + sd_bus *bus; + pid_t omxplayer_pid; + char dbus_name[256]; + bool omxplayer_online; + bool has_sent_initialized_event; + int ok; + + mgr = userdata; + q = &mgr->task_queue; + + ok = cqueue_dequeue(q, &task); + if (ok != 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not dequeue creation task in manager thread. cqueue_dequeue: %s\n", strerror(ok)); + return (void*) EXIT_FAILURE; + } + + snprintf( + dbus_name, + sizeof(dbus_name), + "org.mpris.MediaPlayer2.omxplayer_%d_%lld", + (int) getpid(), + mgr->player->player_id + ); + + if (task.type != kCreate || task.responsehandle == NULL) { + fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); + return (void*) EXIT_FAILURE; + } + + ok = libsystemd.sd_bus_open_user(&bus); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); + return (void*) EXIT_FAILURE; + } + + task.omxplayer_online = false; + task.omxplayer_dbus_name = dbus_name; + ok = libsystemd.sd_bus_match_signal( + bus, + &slot, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + omxplayer_mgr_on_dbus_message, + &task + ); + + pid_t me = fork(); + if (me == 0) { + // I'm the child! + prctl(PR_SET_PDEATHSIG, SIGKILL); + int _ok = execvp( + "omxplayer.bin", + (char*[]) { + "omxplayer.bin", + "--nohdmiclocksync", + "--no-osd", + "--no-keys", + "--loop", + "--layer", "-1", + "--win", "0,0,1,1", + "--dbus_name", dbus_name, + mgr->player->video_uri, + NULL + } + ); + + if (_ok != 0) { + _exit(_ok); + } + _exit(0); + } else if (me > 0) { + // I'm the parent! + omxplayer_pid = me; + } else if (me < 0) { + // something went wrong. + perror("[omxplayer_video_player plugin] Could not spawn omxplayer subprocess. fork"); + platch_respond_native_error_std( + task.responsehandle, + errno + ); + + return (void*) EXIT_FAILURE; + } + + while (!task.omxplayer_online) { + ok = libsystemd.sd_bus_wait(bus, 1000*1000*5); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(-ok)); + } + + ok = libsystemd.sd_bus_process(bus, NULL); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(-ok)); + } + } + + libsystemd.sd_bus_slot_unref(slot); + slot = NULL; + + { // wait for the first frame to appear + struct timespec delta = { + .tv_sec = 0, + .tv_nsec = 350*1000*1000 + }; + while (nanosleep(&delta, &delta)); + } + + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Pause", + &err, + &msg, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send initial pause message: %s, %s\n", err.name, err.message); + return (void*) EXIT_FAILURE; + } + + libsystemd.sd_bus_message_unref(msg); + msg = NULL; + + duration_us = 0; + ok = get_dbus_property( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Duration", + &err, + 'x', + &duration_us + ); + if (ok != 0) { + return (void*) EXIT_FAILURE; + } + + video_width = 0; + ok = get_dbus_property( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "ResWidth", + &err, + 'x', + &video_width + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not query video width: %s, %s\n", err.name, err.message); + return (void*) EXIT_FAILURE; + } + + video_height = 0; + ok = get_dbus_property( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "ResHeight", + &err, + 'x', + &video_height + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not query video height: %s, %s\n", err.name, err.message); + return (void*) EXIT_FAILURE; + } + + platch_respond_success_std(task.responsehandle, &STDINT64(mgr->player->player_id)); + + has_sent_initialized_event = false; + while (1) { + ok = cqueue_dequeue(q, &task); + + if (task.type == kCreate) { + // dont do anything + } else if (task.type == kDispose) { + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_ROOT_FACE, + "Quit", + &err, + &msg, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send Quit message to omxplayer: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, -ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); + + waitpid(omxplayer_pid, NULL, 0); + + // DISPOSE HERE + + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kListen) { + platch_respond_success_std(task.responsehandle, NULL); + + if (!has_sent_initialized_event) { + platch_send_success_event_std( + mgr->player->event_channel_name, + &(struct std_value) { + .type = kStdMap, + .size = 4, + .keys = (struct std_value[4]) { + STDSTRING("event"), + STDSTRING("duration"), + STDSTRING("width"), + STDSTRING("height") + }, + .values = (struct std_value[4]) { + STDSTRING("initialized"), + STDINT64(duration_us / 1000), + STDINT32(video_width), + STDINT32(video_height) + } + } + ); + + has_sent_initialized_event = true; + } + } else if (task.type == kUnlisten) { + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kPlay) { + printf("play\n"); + + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Play", + &err, + &msg, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send play message: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, -ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); + + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kPause) { + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Pause", + &err, + &msg, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send pause message: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, -ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); + msg = NULL; + + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kUpdateView) { + char video_pos_str[256]; + snprintf( + video_pos_str, + sizeof(video_pos_str), + "%f %f %f %f", + (double) task.offset_x, + (double) task.offset_y, + (double) (task.offset_x + task.width), + (double) (task.offset_y + task.height) + ); + + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "VideoPos", + &err, + &msg, + "os", + "/obj/not/used", + video_pos_str + ); + if (ok < 0) { + platch_respond_native_error_std(task.responsehandle, -ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); + + /* + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetLayer", + &err, + &msg, + "x", + (int64_t) task.zpos + ); + if (ok < 0) { + platch_respond_native_error_std(task.responsehandle, -ok); + } + + libsystemd.sd_bus_message_unref(msg); + */ + } else if (task.type == kGetPosition) { + int64_t position = 0; + + ok = get_dbus_property( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Position", + &err, + 'x', + &position + ); + if (ok != 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not get omxplayer position: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, ok); + continue; + } + + position = position / 1000; + + platch_respond_success_std(task.responsehandle, &STDINT64(position)); + } else if (task.type == kSetPosition) { + libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetPosition", + &err, + &msg, + "ox", + "/path/not/used", + (int64_t) (task.position * 1000) + ); + + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kSetLooping) { + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kSetVolume) { + platch_respond_success_std(task.responsehandle, NULL); + } + } + + return (void*) EXIT_SUCCESS; +} + + +static int ensure_binding_initialized(void) { + int ok; + + if (omxpvidpp.initialized) return 0; + + libsystemd.handle = dlopen("libsystemd.so", RTLD_NOW | RTLD_LOCAL); + + LOAD_LIBSYSTEMD_PROC(sd_bus_default); + LOAD_LIBSYSTEMD_PROC(sd_bus_default_user); + LOAD_LIBSYSTEMD_PROC(sd_bus_default_system); + + LOAD_LIBSYSTEMD_PROC(sd_bus_open); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_with_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_user); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_user_with_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_system); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_with_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_remote); + LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_machine); + + LOAD_LIBSYSTEMD_PROC(sd_bus_new); + + LOAD_LIBSYSTEMD_PROC(sd_bus_set_address); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_fd); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_exec); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_address); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_bus_client); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_bus_client); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_server); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_server); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_anonymous); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_anonymous); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_trusted); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_trusted); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_monitor); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_monitor); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_creds); + LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_timestamp); + LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_fds); + LOAD_LIBSYSTEMD_PROC(sd_bus_can_send); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_creds_mask); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_allow_interactive_authorization); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_allow_interactive_authorization); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_exit_on_disconnect); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_exit_on_disconnect); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_close_on_exit); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_close_on_exit); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_watch_bind); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_watch_bind); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_connected_signal); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_connected_signal); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_sender); + + LOAD_LIBSYSTEMD_PROC(sd_bus_start); + + LOAD_LIBSYSTEMD_PROC(sd_bus_try_close); + LOAD_LIBSYSTEMD_PROC(sd_bus_close); + + LOAD_LIBSYSTEMD_PROC(sd_bus_ref); + LOAD_LIBSYSTEMD_PROC(sd_bus_unref); + LOAD_LIBSYSTEMD_PROC(sd_bus_close_unref); + LOAD_LIBSYSTEMD_PROC(sd_bus_flush_close_unref); + + LOAD_LIBSYSTEMD_PROC(sd_bus_default_flush_close); + + LOAD_LIBSYSTEMD_PROC(sd_bus_is_open); + LOAD_LIBSYSTEMD_PROC(sd_bus_is_ready); + + LOAD_LIBSYSTEMD_PROC(sd_bus_get_bus_id); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_scope); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_tid); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_owner_creds); + + LOAD_LIBSYSTEMD_PROC(sd_bus_send); + LOAD_LIBSYSTEMD_PROC(sd_bus_send_to); + LOAD_LIBSYSTEMD_PROC(sd_bus_call); + LOAD_LIBSYSTEMD_PROC(sd_bus_call_async); + + LOAD_LIBSYSTEMD_PROC(sd_bus_get_fd); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_events); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_timeout); + LOAD_LIBSYSTEMD_PROC(sd_bus_process); + LOAD_LIBSYSTEMD_PROC(sd_bus_process_priority); + LOAD_LIBSYSTEMD_PROC(sd_bus_wait); + LOAD_LIBSYSTEMD_PROC(sd_bus_flush); + + LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_slot); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_message); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_handler); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_userdata); + + LOAD_LIBSYSTEMD_PROC(sd_bus_attach_event); + LOAD_LIBSYSTEMD_PROC(sd_bus_detach_event); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_event); + + LOAD_LIBSYSTEMD_PROC(sd_bus_get_n_queued_read); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_n_queued_write); + + LOAD_LIBSYSTEMD_PROC(sd_bus_set_method_call_timeout); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_method_call_timeout); + + LOAD_LIBSYSTEMD_PROC(sd_bus_add_filter); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_match); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_match_async); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_object); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_fallback); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_object_vtable); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_fallback_vtable); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_node_enumerator); + LOAD_LIBSYSTEMD_PROC(sd_bus_add_object_manager); + + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_ref); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_unref); + + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_bus); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_userdata); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_userdata); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_description); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_floating); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_floating); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_destroy_callback); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_destroy_callback); + + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_message); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_handler); + LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_userdata); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_signal); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_call); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_return); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_error); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errorf); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errno); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errnof); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_ref); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_unref); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_seal); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_type); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_cookie); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_reply_cookie); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_priority); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_expect_reply); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_auto_start); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_allow_interactive_authorization); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_signature); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_path); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_interface); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_member); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_destination); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_error); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_errno); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_monotonic_usec); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_realtime_usec); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_seqnum); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_bus); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_creds); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_signal); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_method_call); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_method_error); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_empty); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_has_signature); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_expect_reply); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_auto_start); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_allow_interactive_authorization); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_destination); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_priority); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_appendv); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_basic); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_space); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_iovec); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_memfd); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_space); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_iovec); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_memfd); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_open_container); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_close_container); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_copy); + + LOAD_LIBSYSTEMD_PROC(sd_bus_message_read); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_readv); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_basic); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_array); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_skip); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_enter_container); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_exit_container); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_peek_type); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_verify_type); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_at_end); + LOAD_LIBSYSTEMD_PROC(sd_bus_message_rewind); + + LOAD_LIBSYSTEMD_PROC(sd_bus_get_unique_name); + LOAD_LIBSYSTEMD_PROC(sd_bus_request_name); + LOAD_LIBSYSTEMD_PROC(sd_bus_request_name_async); + LOAD_LIBSYSTEMD_PROC(sd_bus_release_name); + LOAD_LIBSYSTEMD_PROC(sd_bus_release_name_async); + LOAD_LIBSYSTEMD_PROC(sd_bus_list_names); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_name_creds); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_name_machine_id); + + LOAD_LIBSYSTEMD_PROC(sd_bus_call_method); + LOAD_LIBSYSTEMD_PROC(sd_bus_call_method_async); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_property); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_trivial); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_string); + LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_set_property); + + LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_return); + LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_error); + LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errorf); + LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errno); + LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errnof); + + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_signal); + + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_properties_changed_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_properties_changed); + + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_object_added); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_object_removed); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_added_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_added); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_removed_strv); + LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_removed); + + LOAD_LIBSYSTEMD_PROC(sd_bus_query_sender_creds); + LOAD_LIBSYSTEMD_PROC(sd_bus_query_sender_privilege); + + LOAD_LIBSYSTEMD_PROC(sd_bus_match_signal); + LOAD_LIBSYSTEMD_PROC(sd_bus_match_signal_async); + + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_new_from_pid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_ref); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_unref); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_mask); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_augmented_mask); + + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_pid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_ppid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_uid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_euid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_suid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_fsuid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_gid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_egid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_sgid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_fsgid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_supplementary_gids); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_comm); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tid_comm); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_exe); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_cmdline); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_cgroup); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_unit); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_slice); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_user_unit); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_user_slice); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_session); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_owner_uid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_effective_cap); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_permitted_cap); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_inheritable_cap); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_bounding_cap); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_selinux_context); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_audit_session_id); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_audit_login_uid); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tty); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_unique_name); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_well_known_names); + LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_description); + + LOAD_LIBSYSTEMD_PROC(sd_bus_error_free); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_set); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_setf); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_const); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errno); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errnof); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errnofv); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_get_errno); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_copy); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_move); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_is_set); + LOAD_LIBSYSTEMD_PROC(sd_bus_error_has_name); + + LOAD_LIBSYSTEMD_PROC(sd_bus_error_add_map); + + LOAD_LIBSYSTEMD_PROC(sd_bus_path_encode); + LOAD_LIBSYSTEMD_PROC(sd_bus_path_encode_many); + LOAD_LIBSYSTEMD_PROC(sd_bus_path_decode); + LOAD_LIBSYSTEMD_PROC(sd_bus_path_decode_many); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_new); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_ref); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_unref); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_bus); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_userdata); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_userdata); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_add_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_remove_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_add_name); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_remove_name); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_recursive); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_recursive); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_count); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_count_sender); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_count_name); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_contains); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_first); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_next); + + LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_destroy_callback); + LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_destroy_callback); + + omxpvidpp.initialized = true; + + return 0; +} + +static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { + return platch_respond_error_std( + handle, + "couldnotinit", + "omxplayer_video_player plugin failed to initialize libsystemd bindings. See flutter-pi log for details.", + NULL + ); +} + + +/******************************************************* + * CHANNEL HANDLERS * + * handle method calls on the method and event channel * + *******************************************************/ +static int on_receive_evch( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + player = get_player_by_evch(channel); + if (player == NULL) { + return platch_respond_not_implemented(responsehandle); + } + + if STREQ("listen", object->method) { + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kListen, + .responsehandle = responsehandle + }); + } else if STREQ("cancel", object->method) { + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kUnlisten, + .responsehandle = responsehandle + }); + } else { + return platch_respond_not_implemented(responsehandle); + } +} + +static int on_initialize( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + int ok; + + ok = ensure_binding_initialized(); + if (ok != 0) { + return respond_init_failed(responsehandle); + } + + return platch_respond_success_std(responsehandle, NULL); +} + +/// Creates a new video player. +/// Should respond to the platform message when the player has established its viewport. +static int on_create( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + struct omxplayer_mgr *mgr; + enum data_source_type source_type; + struct std_value *temp; + char *asset, *uri, *package_name, *format_hint; + int ok; + + ok = ensure_binding_initialized(); + if (ok != 0) { + return respond_init_failed(responsehandle); + } + + temp = stdmap_get_str(arg, "sourceType"); + if (temp != NULL && STDVALUE_IS_STRING(*temp)) { + char *source_type_str = temp->string_value; + + if STREQ("DataSourceType.asset", source_type_str) { + source_type = kDataSourceTypeAsset; + } else if STREQ("DataSourceType.network", source_type_str) { + source_type = kDataSourceTypeNetwork; + } else if STREQ("DataSourceType.file", source_type_str) { + source_type = kDataSourceTypeFile; + } else { + goto invalid_source_type; + } + } else { + invalid_source_type: + + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['sourceType']` to be a stringification of the [DataSourceType] enum." + ); + } + + temp = stdmap_get_str(arg, "asset"); + if (temp == NULL || temp->type == kStdNull) { + asset = NULL; + } else if (temp != NULL && temp->type == kStdString) { + asset = temp->string_value; + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['asset']` to be a String or null." + ); + } + + temp = stdmap_get_str(arg, "uri"); + if (temp == NULL || temp->type == kStdNull) { + uri = NULL; + } else if (temp != NULL && temp->type == kStdString) { + uri = temp->string_value; + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['uri']` to be a String or null." + ); + } + + temp = stdmap_get_str(arg, "packageName"); + if (temp == NULL || temp->type == kStdNull) { + package_name = NULL; + } else if (temp != NULL && temp->type == kStdString) { + package_name = temp->string_value; + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['packageName']` to be a String or null." + ); + } + + temp = stdmap_get_str(arg, "formatHint"); + if (temp == NULL || temp->type == kStdNull) { + format_hint = NULL; + } else if (temp != NULL && temp->type == kStdString) { + format_hint = temp->string_value; + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['formatHint']` to be a String or null." + ); + } + + mgr = calloc(1, sizeof *mgr); + if (mgr == NULL) { + return platch_respond_native_error_std(responsehandle, ENOMEM); + } + + ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_QUEUE_SIZE); + if (ok != 0) { + free(mgr); + return platch_respond_native_error_std(responsehandle, ok); + } + + // Allocate the player metadata + player = calloc(1, sizeof(*player)); + if (player == NULL) { + cqueue_deinit(&mgr->task_queue); + return platch_respond_native_error_std(responsehandle, ENOMEM); + } + + player->player_id = omxpvidpp.next_unused_player_id++; + player->mgr = mgr; + strncpy(player->video_uri, uri, sizeof(player->video_uri)); + + mgr->player = player; + + ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kCreate, + .responsehandle = responsehandle + }); + + if (ok != 0) { + free(mgr); + cqueue_deinit(&mgr->task_queue); + free(player); + return platch_respond_native_error_std(responsehandle, ok); + } + + snprintf( + player->event_channel_name, + sizeof(player->event_channel_name), + "flutter.io/omxplayerVideoPlayer/videoEvents%lld", + player->player_id + ); + + // add it to our player collection + ok = add_player(player); + if (ok != 0) { + free(mgr); + cqueue_deinit(&mgr->task_queue); + free(player); + return platch_respond_native_error_std(responsehandle, ok); + } + + // set a receiver on the videoEvents event channel + ok = plugin_registry_set_receiver( + player->event_channel_name, + kStandardMethodCall, + on_receive_evch + ); + if (ok != 0) { + //remove_player(player); + free(mgr); + cqueue_deinit(&mgr->task_queue); + free(player); + return platch_respond_native_error_std(responsehandle, ok); + } + + // before we fork, wait for DBus NameOwnerChanged signals, + // so we can immediately pause omxplayer once it has registered + // to dbus. + + ok = pthread_create(&mgr->thread, NULL, omxplayer_mgr_entry, mgr); + if (ok != 0) { + //remove_player(player); + plugin_registry_set_receiver( + player->event_channel_name, + kStandardMethodCall, + NULL + ); + free(mgr); + cqueue_deinit(&mgr->task_queue); + free(player); + return platch_respond_native_error_std(responsehandle, ok); + } + + return 0; +} + +static int on_dispose( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) { + return ok; + } + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kDispose, + .responsehandle = responsehandle + }); +} + +static int on_set_looping( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + struct std_value *temp; + bool loop; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + temp = stdmap_get_str(arg, "looping"); + if (STDVALUE_IS_BOOL(*temp)) { + loop = STDVALUE_AS_BOOL(*temp); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['looping']` to be a boolean." + ); + } + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kSetLooping, + .loop = loop, + .responsehandle = responsehandle + }); +} + +static int on_set_volume( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + struct std_value *temp; + float volume; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + temp = stdmap_get_str(arg, "volume"); + if (STDVALUE_IS_FLOAT(*temp)) { + volume = STDVALUE_AS_FLOAT(*temp); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['volume']` to be a float/double." + ); + } + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kSetVolume, + .volume = volume, + .responsehandle = responsehandle + }); +} + +static int on_play( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + printf("on_play\n"); + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kPlay, + .responsehandle = responsehandle + }); +} + +static int on_get_position( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kGetPosition, + .responsehandle = responsehandle + }); +} + +static int on_seek_to( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + struct std_value *temp; + int64_t position; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + temp = stdmap_get_str(arg, "position"); + if (STDVALUE_IS_INT(*temp)) { + position = STDVALUE_AS_INT(*temp); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['position']` to be an integer." + ); + } + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kSetPosition, + .position = position, + .responsehandle = responsehandle + }); +} + +static int on_pause( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + printf("on_pause\n"); + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + return cqueue_enqueue(&player->mgr->task_queue, &(const struct omxplayer_mgr_task) { + .type = kPause, + .responsehandle = responsehandle + }); +} + +static int on_create_platform_view( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + struct std_value *temp; + int64_t view_id; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + temp = stdmap_get_str(arg, "platformViewId"); + if (STDVALUE_IS_INT(*temp)) { + view_id = STDVALUE_AS_INT(*temp); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['platformViewId']` to be an integer." + ); + } + + ok = compositor_set_platform_view_present_cb(view_id, on_present, player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + player->view_id = view_id; + + return platch_respond_success_std(responsehandle, NULL); +} + +static int on_dispose_platform_view( + struct std_value *arg, + FlutterPlatformMessageResponseHandle* responsehandle +) { + struct omxplayer_video_player *player; + int ok; + + ok = get_player_from_map_arg(arg, &player, responsehandle); + if (ok != 0) return ok; + + player->view_id = -1; + + return platch_respond_success_std(responsehandle, NULL); +} + +/// Called when a platform channel object is received on the method channel. +/// Finds out which method was called an then calls the corresponding above method, +/// or else responds with not implemented. +static int on_receive_mch( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + if STREQ("init", object->method) { + return on_initialize(&object->std_arg, responsehandle); + } else if STREQ("create", object->method) { + return on_create(&object->std_arg, responsehandle); + } else if STREQ("dispose", object->method) { + return on_dispose(&object->std_arg, responsehandle); + } else if STREQ("setLooping", object->method) { + return on_set_looping(&object->std_arg, responsehandle); + } else if STREQ("setVolume", object->method) { + return on_set_volume(&object->std_arg, responsehandle); + } else if STREQ("play", object->method) { + return on_play(&object->std_arg, responsehandle); + } else if STREQ("pause", object->method) { + return on_pause(&object->std_arg, responsehandle); + } else if STREQ("getPosition", object->method) { + return on_get_position(&object->std_arg, responsehandle); + } else if STREQ("seekTo", object->method) { + return on_seek_to(&object->std_arg, responsehandle); + } else if STREQ("createPlatformView", object->method) { + return on_create_platform_view(&object->std_arg, responsehandle); + } else if STREQ("disposePlatformView", object->method) { + return on_dispose_platform_view(&object->std_arg, responsehandle); + } + + return platch_respond_not_implemented(responsehandle); +} + + +int omxpvidpp_init(void) { + int ok; + + omxpvidpp.next_unused_player_id = 1; + + ok = plugin_registry_set_receiver("flutter.io/omxplayerVideoPlayer", kStandardMethodCall, on_receive_mch); + if (ok != 0) return ok; + + return 0; +} + +int omxpvidpp_deinit(void) { + + return 0; +} \ No newline at end of file diff --git a/src/texture_registry.c b/src/texture_registry.c new file mode 100644 index 00000000..37270002 --- /dev/null +++ b/src/texture_registry.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct { + struct texture_map_entry *entries; + size_t size_entries; + size_t n_entries; + int64_t last_id; +} texreg = { + .entries = NULL, + .size_entries = 0, + .n_entries = 0, + .last_id = 0 +}; + +static int add_texture_details(const struct texture_details const *details, int64_t *tex_id_out) { + if (texreg.n_entries == texreg.size_entries) { + // expand the texture map + size_t new_size = texreg.size_entries? texreg.size_entries*2 : 1; + + struct texture_map_entry *new = realloc(texreg.entries, new_size*sizeof(struct texture_map_entry)); + + if (new == NULL) { + perror("[texture registry] Could not expand external texture map. realloc"); + return ENOMEM; + } + + memset(new + texreg.size_entries, 0, (new_size - texreg.size_entries)*sizeof(struct texture_map_entry)); + + texreg.entries = new; + texreg.size_entries = new_size; + } + + size_t index; + for (index = 0; index < texreg.size_entries; index++) { + if (texreg.entries[index].texture_id == 0) { + break; + } + } + + texreg.entries[index].texture_id = ++(texreg.last_id); + texreg.entries[index].details = *details; + + texreg.n_entries++; + + *tex_id_out = texreg.entries[index].texture_id; + + return 0; +} + +static int remove_texture_details(int64_t tex_id) { + size_t index; + for (index = 0; index < texreg.size_entries; index++) { + if (texreg.entries[index].texture_id == tex_id) { + break; + } + } + + if (index == texreg.size_entries) { + return EINVAL; + } + + texreg.entries[index].texture_id = 0; + + texreg.n_entries--; + + return 0; +} + +static void on_collect_texture(void *userdata) { + struct texture_details *details = (struct texture_details *) userdata; + + if (details->collection_cb) { + details->collection_cb( + details->gl_texture.target, + details->gl_texture.name, + details->gl_texture.format, + details->collection_cb_userdata, + details->gl_texture.width, + details->gl_texture.height + ); + } + + //free(details); +} + +bool texreg_gl_external_texture_frame_callback( + void *userdata, + int64_t texture_id, + size_t width, + size_t height, + FlutterOpenGLTexture *texture_out +) { + printf("[texture registry] gl_external_texture_frame_callback(\n" + " userdata: %p,\n" + " texture_id: %"PRIi64",\n" + " width: %"PRIu32",\n" + " height: %"PRIu32",\n" + " texture_out: %p\n" + ");\n", + userdata, texture_id, width, height, texture_out + ); + size_t index; + for (index = 0; index < texreg.size_entries; index++) { + printf("texreg.entries[%lu].texture_id = %lu\n", index, texreg.entries[index].texture_id); + if (texreg.entries[index].texture_id == texture_id) { + break; + } + } + + if (index == texreg.size_entries) + return false; + + *texture_out = texreg.entries[index].details.gl_texture; + + printf("texture_out = {\n" + " .target = %"PRIu32",\n" + " .name = %"PRIu32",\n" + " .format = %"PRIu32",\n" + " .user_data = %p,\n" + " .destruction_callback = %p,\n" + " .width = %"PRIu32",\n" + " .height = %"PRIu32",\n" + "}\n", + texture_out->target, + texture_out->name, + texture_out->format, + texture_out->user_data, + texture_out->destruction_callback, + texture_out->width, + texture_out->height + ); + + return true; +} + +int texreg_register_texture( + GLenum gl_texture_target, + GLuint gl_texture_id, + GLuint gl_texture_format, + void *userdata, + texreg_collect_gl_texture_cb collection_cb, + size_t width, + size_t height, + int64_t *texture_id_out +) { + struct texture_details *details; + FlutterEngineResult engine_result; + int64_t tex_id = 0; + int ok; + + printf("[texture registry] texreg_register_texture(\n" + " gl_texture_target: %"PRIu32 ",\n" + " gl_texture_id: %"PRIu32 ",\n" + " gl_texture_format: %"PRIu32 ",\n" + " userdata: %p,\n" + " collection_cb: %p,\n" + " width: %"PRIu32",\n" + " height: %"PRIu32",\n" + ");\n", + gl_texture_target, + gl_texture_id, + gl_texture_format, + userdata, + collection_cb, + width, + height + ); + + details = malloc(sizeof(struct texture_details)); + + *details = (struct texture_details) { + .gl_texture = { + .target = (uint32_t) gl_texture_target, + .name = (uint32_t) gl_texture_id, + .format = (uint32_t) gl_texture_format, + .user_data = details, + .destruction_callback = on_collect_texture, + .width = width, + .height = height, + }, + .collection_cb = collection_cb, + .collection_cb_userdata = userdata + }; + + ok = add_texture_details( + details, + &tex_id + ); + + if (ok != 0) { + free(details); + return ok; + } + + engine_result = FlutterEngineRegisterExternalTexture(engine, tex_id); + if (engine_result != kSuccess) { + free(details); + return EINVAL; + } + + *texture_id_out = tex_id; + + return 0; +} + +int texreg_mark_texture_frame_available(int64_t texture_id) { + FlutterEngineResult engine_result; + + engine_result = FlutterEngineMarkExternalTextureFrameAvailable(engine, texture_id); + if (engine_result != kSuccess) { + return EINVAL; + } + + return 0; +} + +int texreg_unregister_texture(int64_t texture_id) { + FlutterEngineResult engine_result; + int ok; + + ok = remove_texture_details(texture_id); + if (ok != 0) { + return ok; + } + + engine_result = FlutterEngineUnregisterExternalTexture(engine, texture_id); + if (engine_result != kSuccess) { + return EINVAL; + } +} \ No newline at end of file From 652908aa4ad0b74ae096543f3b62fbe6484dde49 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 7 Jun 2020 23:34:23 +0200 Subject: [PATCH 02/14] remove event_loop --- include/event_loop.h | 1 - 1 file changed, 1 deletion(-) delete mode 100644 include/event_loop.h diff --git a/include/event_loop.h b/include/event_loop.h deleted file mode 100644 index 4289c357..00000000 --- a/include/event_loop.h +++ /dev/null @@ -1 +0,0 @@ -struct evloop; \ No newline at end of file From 1fb1004aebb4f554d622994049e1b429838cf3b9 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 8 Jun 2020 14:51:23 +0200 Subject: [PATCH 03/14] double buffered pseudo-offscreen backing stores made the backing stores that aren't based on window surfaces (i.e. anything drawn over the omxplayer video) double buffered They're still not vsynced though --- include/compositor.h | 21 ++++- src/compositor.c | 195 +++++++++++++++++++++++++++++++------------ 2 files changed, 159 insertions(+), 57 deletions(-) diff --git a/include/compositor.h b/include/compositor.h index 43fa74e8..40d9cc55 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -23,13 +23,30 @@ struct window_surface_backing_store { struct gbm_bo *current_front_bo; }; -struct drm_fb_backing_store { +struct drm_rbo { EGLImage egl_image; - GLuint gl_fbo_id; GLuint gl_rbo_id; uint32_t gem_handle; uint32_t gem_stride; uint32_t drm_fb_id; +}; + +struct drm_fb_backing_store { + /*EGLImage egl_image; + GLuint gl_fbo_id; + GLuint gl_rbo_id; + uint32_t gem_handle; + uint32_t gem_stride; + uint32_t drm_fb_id;*/ + + // Our two + GLuint gl_fbo_id; + struct drm_rbo rbos[2]; + + // The front FB is the one GL is rendering to right now, similiar + // to libgbm. + int current_front_rbo; + uint32_t drm_plane_id; int64_t current_zpos; }; diff --git a/src/compositor.c b/src/compositor.c index 741dcd07..fec54f32 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -280,130 +280,206 @@ static int create_window_surface_backing_store( return 0; } -/// Create a flutter backing store that -static int create_drm_fb_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, - void *user_data +static int create_drm_rbo( + size_t width, + size_t height, + struct drm_rbo *out ) { - struct backing_store_metadata *meta; - struct drm_fb_backing_store *inner; - uint32_t plane_id; + struct drm_rbo fbo; EGLint egl_error; GLenum gl_error; int ok; - plane_id = find_next_unused_drm_plane(); - if (!plane_id) { - fprintf(stderr, "[compositor] Could not find an unused DRM overlay plane for flutter backing store creation.\n"); - return false; - } - - meta = malloc(sizeof(struct backing_store_metadata)); - if (meta == NULL) { - perror("[compositor] Could not allocate backing store metadata, malloc"); - return false; - } - - memset(meta, 0, sizeof(*meta)); - - meta->type = kDrmFb; - meta->drm_fb.drm_plane_id = plane_id; - inner = &meta->drm_fb; - eglGetError(); glGetError(); - inner->egl_image = egl.createDRMImageMESA(egl.display, (const EGLint[]) { - EGL_WIDTH, (int) config->size.width, - EGL_HEIGHT, (int) config->size.height, + fbo.egl_image = egl.createDRMImageMESA(egl.display, (const EGLint[]) { + EGL_WIDTH, width, + EGL_HEIGHT, height, EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA, EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA, EGL_NONE }); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[compositor] error creating DRM EGL Image for flutter backing store, eglCreateDRMImageMESA: %ld\n", egl_error); - return false; + return EINVAL; } - egl.exportDRMImageMESA(egl.display, inner->egl_image, NULL, &inner->gem_handle, &inner->gem_stride); + egl.exportDRMImageMESA(egl.display, fbo.egl_image, NULL, &fbo.gem_handle, &fbo.gem_stride); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[compositor] error getting handle & stride for DRM EGL Image, eglExportDRMImageMESA: %d\n", egl_error); - return false; + return EINVAL; } - glGenRenderbuffers(1, &inner->gl_rbo_id); + glGenRenderbuffers(1, &fbo.gl_rbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error generating renderbuffers for flutter backing store, glGenRenderbuffers: %ld\n", gl_error); - return false; + return EINVAL; } - glBindRenderbuffer(GL_RENDERBUFFER, inner->gl_rbo_id); + glBindRenderbuffer(GL_RENDERBUFFER, fbo.gl_rbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); - return false; + return EINVAL; } - gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, inner->egl_image); + gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, fbo.egl_image); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error binding DRM EGL Image to renderbuffer, glEGLImageTargetRenderbufferStorageOES: %ld\n", gl_error); - return false; + return EINVAL; } - glGenFramebuffers(1, &inner->gl_fbo_id); + /* + glGenFramebuffers(1, &fbo.gl_fbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - return false; + return EINVAL; } - glBindFramebuffer(GL_FRAMEBUFFER, inner->gl_fbo_id); + glBindFramebuffer(GL_FRAMEBUFFER, fbo.gl_fbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); - return false; + return EINVAL; } - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, inner->gl_rbo_id); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fbo.gl_rbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); - return false; + return EINVAL; } GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + */ + glBindRenderbuffer(GL_RENDERBUFFER, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // glBindFramebuffer(GL_FRAMEBUFFER, 0); ok = drmModeAddFB2( drm.fd, - (uint32_t) config->size.width, - (uint32_t) config->size.height, + width, + height, DRM_FORMAT_ARGB8888, (const uint32_t*) &(uint32_t[4]) { - inner->gem_handle, + fbo.gem_handle, 0, 0, 0 }, (const uint32_t*) &(uint32_t[4]) { - inner->gem_stride, 0, 0, 0 + fbo.gem_stride, 0, 0, 0 }, (const uint32_t*) &(uint32_t[4]) { 0, 0, 0, 0 }, - &inner->drm_fb_id, + &fbo.drm_fb_id, 0 ); if (ok == -1) { perror("[compositor] Could not make DRM fb from EGL Image, drmModeAddFB2"); + return errno; + } + + *out = fbo; + + return 0; +} + +static int attach_drm_rbo_to_fbo( + GLuint fbo_id, + struct drm_rbo *rbo +) { + EGLint egl_error; + GLenum gl_error; + + eglGetError(); + glGetError(); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding FBO for attaching the renderbuffer, glBindFramebuffer: %d\n", gl_error); + return EINVAL; + } + + glBindRenderbuffer(GL_RENDERBUFFER, rbo->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); + return EINVAL; + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error attaching renderbuffer to FBO, glFramebufferRenderbuffer: %d\n", gl_error); + return EINVAL; + } + + return 0; +} + +/// Create a flutter backing store that +static int create_drm_fb_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *user_data +) { + struct backing_store_metadata *meta; + struct drm_fb_backing_store *inner; + uint32_t plane_id; + EGLint egl_error; + GLenum gl_error; + int ok; + + plane_id = find_next_unused_drm_plane(); + if (!plane_id) { + fprintf(stderr, "[compositor] Could not find an unused DRM overlay plane for flutter backing store creation.\n"); + return false; + } + + meta = malloc(sizeof(struct backing_store_metadata)); + if (meta == NULL) { + perror("[compositor] Could not allocate backing store metadata, malloc"); return false; } + + memset(meta, 0, sizeof(*meta)); + + meta->type = kDrmFb; + meta->drm_fb.drm_plane_id = plane_id; + inner = &meta->drm_fb; + + eglGetError(); + glGetError(); + + glGenFramebuffers(1, &inner->gl_fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); + return EINVAL; + } + + ok = create_drm_rbo( + config->size.width, + config->size.height, + inner->rbos + 0 + ); + if (ok != 0) return ok; + + ok = create_drm_rbo( + config->size.width, + config->size.height, + inner->rbos + 1 + ); + if (ok != 0) return ok; + + ok = attach_drm_rbo_to_fbo(inner->gl_fbo_id, inner->rbos + inner->current_front_rbo); + if (ok != 0) return ok; ok = get_plane_property_value(inner->drm_plane_id, "zpos", &inner->current_zpos); if (ok != 0) { return false; } - // TODO: Only reflect Y when running on Raspberry Pi. + // Reflect Y because GL draws to its buffers that upside-down. ok = set_plane_property_value(inner->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); if (ok == -1) { perror("[compositor] Could not set rotation & reflection of hardware plane. drmModeObjectSetProperty"); @@ -417,7 +493,7 @@ static int create_drm_fb_backing_store( drm.fd, inner->drm_plane_id, drm.crtc_id, - inner->drm_fb_id, + inner->rbos[1 ^ inner->current_front_rbo].drm_fb_id, 0, 0, 0, 0, 0, 0, 0, ((uint32_t) config->size.width) << 16, ((uint32_t) config->size.height) << 16 @@ -580,6 +656,8 @@ static int present_drm_fb_backing_store( int zpos ) { int ok; + + printf("Presenting drm fb backing store\n"); if (zpos != backing_store->current_zpos) { ok = set_plane_property_value(backing_store->drm_plane_id, "zpos", zpos); @@ -589,11 +667,18 @@ static int present_drm_fb_backing_store( } } + backing_store->current_front_rbo ^= 1; + printf("Attaching rbo with id %u\n", backing_store->rbos[backing_store->current_front_rbo].gl_rbo_id); + ok = attach_drm_rbo_to_fbo(backing_store->gl_fbo_id, backing_store->rbos + backing_store->current_front_rbo); + if (ok != 0) return ok; + + // present the back buffer + printf("Presenting rbo with id %u\n", backing_store->rbos[backing_store->current_front_rbo ^ 1].gl_rbo_id); ok = drmModeSetPlane( drm.fd, backing_store->drm_plane_id, drm.crtc_id, - backing_store->drm_fb_id, + backing_store->rbos[backing_store->current_front_rbo ^ 1].drm_fb_id, 0, offset_x, offset_y, width, height, 0, 0, ((uint16_t) width) << 16, ((uint16_t) height) << 16 @@ -754,7 +839,6 @@ static bool present_layers_callback( eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); eglSwapBuffers(egl.display, egl.surface); - eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); /// find the index of the window surface. /// the window surface's zpos can't change, so we need to @@ -788,7 +872,6 @@ static bool present_layers_callback( (int) layer->size.height ); } else if (meta->type == kDrmFb) { - continue; ok = present_drm_fb_backing_store( &meta->drm_fb, (int) layer->offset.x, @@ -814,6 +897,8 @@ static bool present_layers_callback( } } + eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + FlutterEngineTraceEventDurationEnd("present"); return true; From 1dc295a056e64967012300e225c76772f96b9bc3 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 12 Jun 2020 16:40:19 +0200 Subject: [PATCH 04/14] some fixes, backing store collection --- include/collection.h | 54 ++--- include/compositor.h | 20 +- include/event_loop.h | 1 + include/plugins/video_player.h | 11 +- include/threading.h | 233 ++++++++++++++++++++++ src/compositor.c | 167 +++++++++++++--- src/plugins/video_player.c | 355 +++++++++++++++++++++++---------- 7 files changed, 666 insertions(+), 175 deletions(-) create mode 100644 include/event_loop.h create mode 100644 include/threading.h diff --git a/include/collection.h b/include/collection.h index a08cebdc..34b54822 100644 --- a/include/collection.h +++ b/include/collection.h @@ -316,6 +316,7 @@ static inline int cpset_put_locked( if ((index == -1) && (set->size == set->count_pointers)) { new_size = set->size ? set->size << 1 : 1; + if (new_size < set->max_size) { void **new_pointers = (void**) realloc(set->pointers, new_size * sizeof(void*)); @@ -323,14 +324,14 @@ static inline int cpset_put_locked( return ENOMEM; } - memset(new_pointers + set->size, 0, new_size - set->size); + memset(new_pointers + set->size, 0, (new_size - set->size) * sizeof(void*)); index = set->size; set->pointers = new_pointers; set->size = new_size; } else { - return EBUSY; + return ENOSPC; } } @@ -358,36 +359,35 @@ static inline int cpset_put( return ok; } -static inline bool cpset_contains( +static inline bool cpset_contains_locked( struct concurrent_pointer_set *const set, const void const *pointer ) { - cpset_lock(set); - for (int i = 0; i < set->size; i++) { if (set->pointers[i] && (set->pointers[i] == pointer)) { return true; } } - cpset_unlock(set); return false; } -static inline bool cpset_contains_locked( +static inline bool cpset_contains( struct concurrent_pointer_set *const set, const void const *pointer ) { - for (int i = 0; i < set->size; i++) { - if (set->pointers[i] && (set->pointers[i] == pointer)) { - return true; - } - } + bool result; + + cpset_lock(set); + result = cpset_contains_locked(set, pointer); + cpset_unlock(set); - return false; + return result; } -static inline void *cpset_remove( + + +static inline void *cpset_remove_locked( struct concurrent_pointer_set *const set, const void const *pointer ) { @@ -397,8 +397,6 @@ static inline void *cpset_remove( result = NULL; - cpset_lock(set); - for (index = 0; index < set->size; index++) { if (set->pointers[index] && (set->pointers[index] == pointer)) { result = set->pointers[index]; @@ -406,38 +404,24 @@ static inline void *cpset_remove( set->pointers[index] = NULL; set->count_pointers--; - cpset_unlock(set); - return result; } } - cpset_unlock(set); return NULL; } -static inline void *cpset_remove_locked( +static inline void *cpset_remove( struct concurrent_pointer_set *const set, const void const *pointer ) { void *result; - size_t new_size; - int index; - - result = NULL; - for (index = 0; index < set->size; index++) { - if (set->pointers[index] && (set->pointers[index] == pointer)) { - result = set->pointers[index]; - - set->pointers[index] = NULL; - set->count_pointers--; - - return result; - } - } + cpset_lock(set); + result = cpset_remove_locked(set, pointer); + cpset_unlock(set); - return NULL; + return result; } static inline void *__cpset_next_pointer( diff --git a/include/compositor.h b/include/compositor.h index 40d9cc55..e19490ab 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -6,6 +6,16 @@ #include #include +typedef int (*platform_view_mount_cb)( + int64_t view_id, + void *userdata +); + +typedef int (*platform_view_unmount_cb)( + int64_t view_id, + void *userdata +); + typedef int (*platform_view_present_cb)( int64_t view_id, const FlutterPlatformViewMutation **mutations, @@ -68,6 +78,14 @@ extern const FlutterCompositor flutter_compositor; uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo); -int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_present_cb cb, void *userdata); +int compositor_set_view_callbacks( + int64_t view_id, + platform_view_present_cb present, + void *userdata +); + +int compositor_remove_view_callbacks( + int64_t view_id +); #endif \ No newline at end of file diff --git a/include/event_loop.h b/include/event_loop.h new file mode 100644 index 00000000..4289c357 --- /dev/null +++ b/include/event_loop.h @@ -0,0 +1 @@ +struct evloop; \ No newline at end of file diff --git a/include/plugins/video_player.h b/include/plugins/video_player.h index 00584d15..4d1055f3 100644 --- a/include/plugins/video_player.h +++ b/include/plugins/video_player.h @@ -371,15 +371,12 @@ struct libsystemd { struct omxplayer_mgr; struct omxplayer_video_player { + int64_t player_id; char event_channel_name[256]; char video_uri[256]; - int64_t player_id; - int64_t view_id; - /// If flutter says the video should be looping, this value is true. - /// Note: this is not related to whether omxplayer is looping. - /// omxplayer is always looping so it doesn't accidentally terminate on us. - bool should_loop; + bool has_view; + int64_t view_id; struct omxplayer_mgr *mgr; }; @@ -419,8 +416,6 @@ struct omxplayer_mgr_task { int64_t position; struct { bool visible; - }; - struct { int offset_x, offset_y; int width, height; int zpos; diff --git a/include/threading.h b/include/threading.h new file mode 100644 index 00000000..461ba43b --- /dev/null +++ b/include/threading.h @@ -0,0 +1,233 @@ +#ifndef _THREADING_H +#define _THREADING_H + +#include +#include +#include + +#include + +#define CQUEUE_DEFAULT_MAX_QUEUE_SIZE 64 + +struct concurrent_queue { + pthread_mutex_t mutex; + pthread_cond_t is_dequeueable; + pthread_cond_t is_enqueueable; + size_t start_index; + size_t length; + size_t size; + void *elements; + + size_t max_queue_size; + size_t element_size; +}; + +#define CQUEUE_INITIALIZER(element_type, _max_queue_size) \ + ((struct concurrent_queue) { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .is_dequeueable = PTHREAD_COND_INITIALIZER, \ + .is_enqueueable = PTHREAD_COND_INITIALIZER, \ + .start_index = 0, \ + .length = 0, \ + .size = 0, \ + .elements = NULL, \ + .max_queue_size = _max_queue_size, \ + .element_size = sizeof(element_type) \ + }) + +static inline int cqueue_init(struct concurrent_queue *queue, size_t element_size, size_t max_queue_size) { + memset(queue, 0, sizeof(*queue)); + + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->is_dequeueable, NULL); + pthread_cond_init(&queue->is_enqueueable, NULL); + + queue->start_index = 0; + queue->length = 0; + queue->size = 2; + queue->elements = calloc(2, element_size); + + queue->max_queue_size = max_queue_size; + queue->element_size = element_size; + + if (queue->elements == NULL) { + queue->size = 0; + return ENOMEM; + } + + return 0; +} + +static inline int cqueue_deinit(struct concurrent_queue *queue) { + pthread_mutex_destroy(&queue->mutex); + pthread_cond_destroy(&queue->is_dequeueable); + pthread_cond_destroy(&queue->is_enqueueable); + + if (queue->elements != NULL) { + free(queue->elements); + } + + queue->start_index = 0; + queue->length = 0; + queue->size = 0; + queue->elements = NULL; + + queue->max_queue_size = 0; + queue->element_size = 0; + + return 0; +} + +static inline int cqueue_lock(struct concurrent_queue * const queue) { + return pthread_mutex_lock(&queue->mutex); +} + +static inline int cqueue_unlock(struct concurrent_queue * const queue) { + return pthread_mutex_unlock(&queue->mutex); +} + +static inline int cqueue_try_enqueue( + struct concurrent_queue * const queue, + const void const *p_element +) { + cqueue_lock(queue); + + if (queue->size == queue->length) { + // expand the queue. + + size_t new_size = queue->size ? queue->size << 1 : 1; + + if (new_size > queue->max_queue_size) { + cqueue_unlock(queue); + return EAGAIN; + } + + void *new_elements = realloc(queue->elements, new_size * queue->element_size); + + if (new_elements == NULL) { + cqueue_unlock(queue); + return ENOMEM; + } + + if (queue->size) { + memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); + } + + queue->elements = new_elements; + queue->size = new_size; + } + + memcpy( + ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), + p_element, + queue->element_size + ); + + queue->length++; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + +static inline int cqueue_try_dequeue( + struct concurrent_queue * const queue, + void const *element_out +) { + cqueue_lock(queue); + + if (queue->length == 0) { + cqueue_unlock(queue); + return EAGAIN; + } + + memcpy( + ((char*) queue->elements) + queue->element_size*queue->start_index, + element_out, + queue->element_size + ); + + queue->start_index = (queue->start_index + 1) & (queue->size - 1); + queue->length--; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_enqueueable); + + return 0; +} + +static inline int cqueue_enqueue( + struct concurrent_queue * const queue, + const void const *p_element +) { + cqueue_lock(queue); + + if (queue->size == queue->length) { + // expand the queue or wait for an element to be dequeued. + + size_t new_size = queue->size ? queue->size << 1 : 1; + if (new_size < queue->max_queue_size) { + void *new_elements = realloc(queue->elements, new_size * queue->element_size); + + if (new_elements == NULL) { + cqueue_unlock(queue); + return ENOMEM; + } + + if (queue->size) { + memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); + } + + queue->elements = new_elements; + queue->size = new_size; + } else { + do { + pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); + } while (queue->size == queue->length); + } + } + + memcpy( + ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), + p_element, + queue->element_size + ); + + queue->length++; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + +static inline int cqueue_dequeue( + struct concurrent_queue *const queue, + void *const element_out +) { + cqueue_lock(queue); + + while (queue->length == 0) + pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); + + memcpy( + element_out, + ((char*) queue->elements) + queue->element_size*queue->start_index, + queue->element_size + ); + + queue->start_index = (queue->start_index + 1) & (queue->size - 1); + queue->length--; + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_enqueueable); + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c index fec54f32..a485d7c4 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -19,7 +19,7 @@ struct view_cb_data { int64_t view_id; - platform_view_present_cb present_cb; + platform_view_present_cb present; void *userdata; }; @@ -236,15 +236,59 @@ static int get_plane_property_value( static void destroy_window_surface_backing_store_fb(void *userdata) { struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; + printf("destroying window surface backing store FBO\n"); +} + +static void destroy_drm_rbo( + struct drm_rbo *rbo +) { + EGLint egl_error; + GLenum gl_error; + int ok; + + eglGetError(); + glGetError(); + glDeleteRenderbuffers(1, &rbo->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); + } + + ok = drmModeRmFB(drm.fd, rbo->drm_fb_id); + if (ok < 0) { + fprintf(stderr, "[compositor] error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); + } + + eglDestroyImage(egl.display, rbo->egl_image); + if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { + fprintf(stderr, "[compositor] error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); + } } /// Destroy the OpenGL ES framebuffer-object that is associated with this /// DRM FB backed backing store static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { - struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; - + struct backing_store_metadata *meta; + EGLint egl_error; + GLenum gl_error; + int ok; + + meta = (struct backing_store_metadata*) userdata; + printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); + + eglGetError(); + glGetError(); + + glDeleteFramebuffers(1, &meta->drm_fb.gl_fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error destroying OpenGL FBO, glDeleteFramebuffers: %d\n", gl_error); + } + + destroy_drm_rbo(meta->drm_fb.rbos + 0); + destroy_drm_rbo(meta->drm_fb.rbos + 1); + + free(meta); } @@ -258,6 +302,8 @@ static int create_window_surface_backing_store( ) { struct backing_store_metadata *meta; + printf("create_window_surface_backing_store()\n"); + meta = malloc(sizeof(*meta)); if (meta == NULL) { perror("[compositor] Could not allocate metadata for backing store. malloc"); @@ -430,6 +476,8 @@ static int create_drm_fb_backing_store( GLenum gl_error; int ok; + printf("create_drm_fb_backing_store\n"); + plane_id = find_next_unused_drm_plane(); if (!plane_id) { fprintf(stderr, "[compositor] Could not find an unused DRM overlay plane for flutter backing store creation.\n"); @@ -563,6 +611,9 @@ static int collect_window_surface_backing_store( struct backing_store_metadata *meta ) { struct window_surface_backing_store *inner = &meta->window_surface; + + printf("collect_window_surface_backing_store\n"); + return 0; } @@ -571,6 +622,13 @@ static int collect_drm_fb_backing_store( struct backing_store_metadata *meta ) { struct drm_fb_backing_store *inner = &meta->drm_fb; + + printf("collect_drm_fb_backing_store(gl_fbo_id: %u)\n", inner->gl_fbo_id); + + // makes sense that the FlutterBackingStore collect callback is called before the + // FlutterBackingStore OpenGL framebuffer destroy callback. Thanks flutter. (/s) + // free(inner); + return 0; } @@ -657,8 +715,6 @@ static int present_drm_fb_backing_store( ) { int ok; - printf("Presenting drm fb backing store\n"); - if (zpos != backing_store->current_zpos) { ok = set_plane_property_value(backing_store->drm_plane_id, "zpos", zpos); if (ok != 0) { @@ -668,12 +724,10 @@ static int present_drm_fb_backing_store( } backing_store->current_front_rbo ^= 1; - printf("Attaching rbo with id %u\n", backing_store->rbos[backing_store->current_front_rbo].gl_rbo_id); ok = attach_drm_rbo_to_fbo(backing_store->gl_fbo_id, backing_store->rbos + backing_store->current_front_rbo); if (ok != 0) return ok; // present the back buffer - printf("Presenting rbo with id %u\n", backing_store->rbos[backing_store->current_front_rbo ^ 1].gl_rbo_id); ok = drmModeSetPlane( drm.fd, backing_store->drm_plane_id, @@ -691,22 +745,28 @@ static int present_drm_fb_backing_store( return 0; } -static struct view_cb_data *get_data_for_view_id(int64_t view_id) { +static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { struct view_cb_data *data; - cpset_lock(&flutterpi_compositor.cbs); - for_each_pointer_in_cpset(&flutterpi_compositor.cbs, data) { if (data->view_id == view_id) { - cpset_unlock(&flutterpi_compositor.cbs); return data; } } - cpset_unlock(&flutterpi_compositor.cbs); return NULL; } +static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { + struct view_cb_data *data; + + cpset_lock(&flutterpi_compositor.cbs); + data = get_cbs_for_view_id_locked(view_id); + cpset_unlock(&flutterpi_compositor.cbs); + + return data; +} + static int present_platform_view( int64_t view_id, const FlutterPlatformViewMutation **mutations, @@ -717,24 +777,28 @@ static int present_platform_view( int height, int zpos ) { - struct view_cb_data *data; + struct view_cb_data *cbs; - data = get_data_for_view_id(view_id); - if (data == NULL) { + cbs = get_cbs_for_view_id(view_id); + if (cbs == NULL) { return EINVAL; } - return data->present_cb( - view_id, - mutations, - num_mutations, - offset_x, - offset_y, - width, - height, - zpos, - data->userdata - ); + if (cbs->present != NULL) { + return cbs->present( + view_id, + mutations, + num_mutations, + offset_x, + offset_y, + width, + height, + zpos, + cbs->userdata + ); + } else { + return 0; + } } static bool present_layers_callback( @@ -745,7 +809,6 @@ static bool present_layers_callback( int window_surface_index; int ok; - /* printf("[compositor] present_layers_callback(\n" " layers_count: %lu,\n" " layers = {\n", @@ -833,7 +896,6 @@ static bool present_layers_callback( printf(" },\n"); } printf(" }\n)\n"); - */ FlutterEngineTraceEventDurationBegin("present"); @@ -904,6 +966,7 @@ static bool present_layers_callback( return true; } +/* int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_present_cb cb, void *userdata) { struct view_cb_data *entry; @@ -925,7 +988,9 @@ int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_prese } entry->view_id = view_id; - entry->present_cb = cb; + entry->present = present; + entry->mount = mount; + entry->unmount = unmount; entry->userdata = userdata; cpset_put_locked(&flutterpi_compositor.cbs, entry); @@ -933,6 +998,50 @@ int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_prese return cpset_unlock(&flutterpi_compositor.cbs); } +*/ + +int compositor_set_view_callbacks( + int64_t view_id, + platform_view_present_cb present, + void *userdata +) { + struct view_cb_data *entry; + + cpset_lock(&flutterpi_compositor.cbs); + + entry = get_cbs_for_view_id_locked(view_id); + + if (entry == NULL) { + entry = calloc(1, sizeof(*entry)); + if (!entry) { + cpset_unlock(&flutterpi_compositor.cbs); + return ENOMEM; + } + + cpset_put_locked(&flutterpi_compositor.cbs, entry); + } + + entry->view_id = view_id; + entry->present = present; + entry->userdata = userdata; + + return cpset_unlock(&flutterpi_compositor.cbs); +} + +int compositor_remove_view_callbacks(int64_t view_id) { + struct view_cb_data *entry; + + cpset_lock(&flutterpi_compositor.cbs); + + entry = get_cbs_for_view_id_locked(view_id); + if (entry == NULL) { + return EINVAL; + } + + cpset_remove_locked(&flutterpi_compositor.cbs, entry); + + return cpset_unlock(&flutterpi_compositor.cbs); +} const FlutterCompositor flutter_compositor = { .struct_size = sizeof(FlutterCompositor), diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c index ae4fb596..cbf8614a 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/video_player.c @@ -33,87 +33,72 @@ static struct { int64_t next_unused_player_id; /// Collection of players. + /* struct omxplayer_video_player **players; size_t size_players; size_t n_players; + */ + + struct concurrent_pointer_set players; /// The D-Bus that where omxplayer instances can be talked to. /// Typically the session dbus. //sd_bus *dbus; //pthread_t dbus_processor_thread; -} omxpvidpp = {0}; +} omxpvidpp = { + .initialized = false, + .next_unused_player_id = 1, + .players = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) +}; /// libsystemd DLL struct libsystemd libsystemd = {0}; -/// Add a player instance into the collection in [omxpvidpp]. +/// Add a player instance to the player collection. static int add_player(struct omxplayer_video_player *player) { - if (omxpvidpp.n_players == omxpvidpp.size_players) { - // expand the texture map - size_t new_size = omxpvidpp.size_players? omxpvidpp.size_players*2 : 1; - - struct omxplayer_video_player **new = realloc(omxpvidpp.players, new_size*sizeof(*omxpvidpp.players)); - - if (new == NULL) { - perror("[video_player plugin] Could not expand video player map. realloc"); - return ENOMEM; - } - - memset(new + omxpvidpp.size_players, 0, (new_size - omxpvidpp.size_players)*sizeof(*omxpvidpp.players)); - - omxpvidpp.players = new; - omxpvidpp.size_players = new_size; - } - - size_t index; - for (index = 0; index < omxpvidpp.size_players; index++) { - if (omxpvidpp.players[index] == NULL) { - break; - } - } - - omxpvidpp.players[index] = player; - omxpvidpp.n_players++; - - return 0; + return cpset_put(&omxpvidpp.players, player); } -/// Gets the player instance corresponding to a player id. -/// Returns NULL when no player with this id was found. +/// Get a player instance by its id. static struct omxplayer_video_player *get_player_by_id(int64_t player_id) { - size_t index; - for (index = 0; index < omxpvidpp.size_players; index++) { - if ((omxpvidpp.players[index]) && (omxpvidpp.players[index]->player_id == player_id)) { - break; + struct omxplayer_video_player *player; + + cpset_lock(&omxpvidpp.players); + for_each_pointer_in_cpset(&omxpvidpp.players, player) { + if (player->player_id == player_id) { + cpset_unlock(&omxpvidpp.players); + return player; } } - if (index == omxpvidpp.size_players) { - return NULL; - } - - return omxpvidpp.players[index]; + cpset_unlock(&omxpvidpp.players); + return NULL; } -/// Gets the player instance corresponding to an event channel name. -/// Returns NULL when no player with this event channel name was found. +/// Get a player instance by its event channel name. static struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { - size_t index; - for (index = 0; index < omxpvidpp.size_players; index++) { - if (omxpvidpp.players[index] && - STREQ(omxpvidpp.players[index]->event_channel_name, event_channel_name) - ) { - break; + struct omxplayer_video_player *player; + + cpset_lock(&omxpvidpp.players); + for_each_pointer_in_cpset(&omxpvidpp.players, player) { + if (strcmp(player->event_channel_name, event_channel_name) == 0) { + cpset_unlock(&omxpvidpp.players); + return player; } } - if (index == omxpvidpp.size_players) { - return NULL; - } + cpset_unlock(&omxpvidpp.players); + return NULL; +} - return omxpvidpp.players[index]; +/// Remove a player instance from the player collection. +static void *remove_player(struct omxplayer_video_player *player) { + return cpset_remove(&omxpvidpp.players, player); } +/// Get the player id from the given arg, which is a kStdMap. +/// (*player_id_out = arg['playerId']) +/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. static int get_player_id_from_map_arg( struct std_value *arg, int64_t *player_id_out, @@ -147,6 +132,9 @@ static int get_player_id_from_map_arg( return 0; } +/// Get the player associated with the id in the given arg, which is a kStdMap. +/// (*player_out = get_player_by_id(get_player_id_from_map_arg(arg))) +/// If an error ocurrs, this will respond with an illegal argument error to the given responsehandle. static int get_player_from_map_arg( struct std_value *arg, struct omxplayer_video_player **player_out, @@ -175,6 +163,8 @@ static int get_player_from_map_arg( return 0; } +/// Called on the flutter rasterizer thread when a players platform view +/// Should be presented. static int on_present( int64_t view_id, const FlutterPlatformViewMutation **mutations, @@ -201,6 +191,8 @@ static int on_present( ); } +/// Unfortunately, we can't use sd_bus for this, because it +/// wraps some things in containers. static int get_dbus_property( sd_bus *bus, const char *destination, @@ -242,8 +234,14 @@ static int get_dbus_property( return 0; } - -static int omxplayer_mgr_on_dbus_message(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { +/// Callback to be called when the omxplayer manager receives +/// a DBus message. (Currently only used for listening to NameOwnerChanged messages, +/// to find out when omxplayer registers to the dbus.) +static int omxplayer_mgr_on_dbus_message( + sd_bus_message *m, + void *userdata, + sd_bus_error *ret_error +) { struct omxplayer_mgr_task *task; const char *sender, *member; char *old_owner, *new_owner, *name; @@ -271,30 +269,52 @@ static int omxplayer_mgr_on_dbus_message(sd_bus_message *m, void *userdata, sd_b return 0; } +/// The entry function of the manager thread. static void *omxplayer_mgr_entry(void *userdata) { struct omxplayer_mgr_task task; struct concurrent_queue *q; struct omxplayer_mgr *mgr; + struct timespec t_scheduled_pause; sd_bus_message *msg; sd_bus_error err; sd_bus_slot *slot; - int64_t duration_us, video_width, video_height; + int64_t duration_us, video_width, video_height, current_zpos; sd_bus *bus; pid_t omxplayer_pid; char dbus_name[256]; bool omxplayer_online; bool has_sent_initialized_event; + bool pause_on_end; + bool has_scheduled_pause_time; + bool is_stream; int ok; mgr = userdata; q = &mgr->task_queue; + // dequeue the first task of the queue (creation task) ok = cqueue_dequeue(q, &task); if (ok != 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not dequeue creation task in manager thread. cqueue_dequeue: %s\n", strerror(ok)); return (void*) EXIT_FAILURE; } + // check that it really is a creation task + if (task.type != kCreate || task.responsehandle == NULL) { + fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); + return (void*) EXIT_FAILURE; + } + + // determine whether we're watching a stream or not. + // this is a heuristic. unfortunately, omxplayer itself doesn't even know whether it's playing + // back a stream or a video file. + if (strstr(mgr->player->video_uri, "rtsp://") == mgr->player->video_uri) { + is_stream = true; + } else { + is_stream = false; + } + + // generate the player name snprintf( dbus_name, sizeof(dbus_name), @@ -303,17 +323,15 @@ static void *omxplayer_mgr_entry(void *userdata) { mgr->player->player_id ); - if (task.type != kCreate || task.responsehandle == NULL) { - fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); - return (void*) EXIT_FAILURE; - } - + // open the session dbus ok = libsystemd.sd_bus_open_user(&bus); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); return (void*) EXIT_FAILURE; } + // register a callbacks that tells us when + // omxplayer has registered to the dbus task.omxplayer_online = false; task.omxplayer_dbus_name = dbus_name; ok = libsystemd.sd_bus_match_signal( @@ -326,7 +344,9 @@ static void *omxplayer_mgr_entry(void *userdata) { omxplayer_mgr_on_dbus_message, &task ); - + + // spawn the omxplayer process + current_zpos = -1; pid_t me = fork(); if (me == 0) { // I'm the child! @@ -365,6 +385,7 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_FAILURE; } + // wait for omxplayer to register to the dbus while (!task.omxplayer_online) { ok = libsystemd.sd_bus_wait(bus, 1000*1000*5); if (ok < 0) { @@ -380,7 +401,8 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_slot_unref(slot); slot = NULL; - { // wait for the first frame to appear + // wait for the first frame to appear + { struct timespec delta = { .tv_sec = 0, .tv_nsec = 350*1000*1000 @@ -388,6 +410,7 @@ static void *omxplayer_mgr_entry(void *userdata) { while (nanosleep(&delta, &delta)); } + // pause right on the first frame ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -406,6 +429,7 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_message_unref(msg); msg = NULL; + // get the video duration duration_us = 0; ok = get_dbus_property( bus, @@ -421,6 +445,7 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_FAILURE; } + // get the video width video_width = 0; ok = get_dbus_property( bus, @@ -437,6 +462,7 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_FAILURE; } + // get the video width video_height = 0; ok = get_dbus_property( bus, @@ -453,15 +479,28 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_FAILURE; } + // creation was a success! respond to the dart-side with our player id. platch_respond_success_std(task.responsehandle, &STDINT64(mgr->player->player_id)); has_sent_initialized_event = false; + pause_on_end = is_stream ? false : true; + has_scheduled_pause_time = false; while (1) { ok = cqueue_dequeue(q, &task); if (task.type == kCreate) { - // dont do anything + printf("[omxplayer_video_player plugin] Omxplayer manager got a creation task, even though the player is already running.\n"); } else if (task.type == kDispose) { + if (mgr->player->has_view) { + printf("[omxplayer_video_player plugin] flutter attempted to dispose the video player before its view was disposed.\n"); + + compositor_remove_view_callbacks(mgr->player->view_id); + + mgr->player->has_view = false; + mgr->player->view_id = -1; + } + + // tell omxplayer to quit ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -480,9 +519,14 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_message_unref(msg); + // wait for omxplayer to quit waitpid(omxplayer_pid, NULL, 0); - // DISPOSE HERE + // close the bus + libsystemd.sd_bus_unref(bus); + + // remove the player from the set of players + remove_player(mgr->player); platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kListen) { @@ -502,7 +546,7 @@ static void *omxplayer_mgr_entry(void *userdata) { }, .values = (struct std_value[4]) { STDSTRING("initialized"), - STDINT64(duration_us / 1000), + STDINT64(is_stream? INT64_MAX : duration_us / 1000), STDINT32(video_width), STDINT32(video_height) } @@ -514,8 +558,6 @@ static void *omxplayer_mgr_entry(void *userdata) { } else if (task.type == kUnlisten) { platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kPlay) { - printf("play\n"); - ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -536,6 +578,8 @@ static void *omxplayer_mgr_entry(void *userdata) { platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kPause) { + has_scheduled_pause_time = false; + ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -587,24 +631,28 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_message_unref(msg); - /* - ok = libsystemd.sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetLayer", - &err, - &msg, - "x", - (int64_t) task.zpos - ); - if (ok < 0) { - platch_respond_native_error_std(task.responsehandle, -ok); - } + if (current_zpos != task.zpos) { + printf("setting layer to %d\n", task.zpos); + + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetLayer", + &err, + &msg, + "x", + (int64_t) task.zpos + ); + if (ok < 0) { + platch_respond_native_error_std(task.responsehandle, -ok); + } - libsystemd.sd_bus_message_unref(msg); - */ + libsystemd.sd_bus_message_unref(msg); + + current_zpos = task.zpos; + } } else if (task.type == kGetPosition) { int64_t position = 0; @@ -628,7 +676,19 @@ static void *omxplayer_mgr_entry(void *userdata) { platch_respond_success_std(task.responsehandle, &STDINT64(position)); } else if (task.type == kSetPosition) { - libsystemd.sd_bus_call_method( + if (is_stream) { + // Don't allow flutter to seek on a stream. + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream).\n"); + platch_respond_error_std( + task.responsehandle, + "state-error", + "Attempted to seek on non-seekable video (a stream)", + NULL + ); + continue; + } + + ok = libsystemd.sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, @@ -640,11 +700,40 @@ static void *omxplayer_mgr_entry(void *userdata) { "/path/not/used", (int64_t) (task.position * 1000) ); + if (ok != 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kSetLooping) { + pause_on_end = false; platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kSetVolume) { + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_PROPERTY_FACE, + DBUS_PROPRETY_SET, + &err, + &msg, + "ssd", + DBUS_OMXPLAYER_PLAYER_FACE, + "Volume", + (double) task.volume + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer volume: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, ok); + continue; + } + + libsystemd.sd_bus_message_unref(msg); + platch_respond_success_std(task.responsehandle, NULL); } } @@ -652,7 +741,7 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_SUCCESS; } - +/// Ensures the bindings to libsystemd are initialized. static int ensure_binding_initialized(void) { int ok; @@ -999,6 +1088,7 @@ static int ensure_binding_initialized(void) { return 0; } +/// Respond to the handle with a "initialization failed" message. static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { return platch_respond_error_std( handle, @@ -1313,8 +1403,6 @@ static int on_play( struct omxplayer_video_player *player; int ok; - printf("on_play\n"); - ok = get_player_from_map_arg(arg, &player, responsehandle); if (ok != 0) return ok; @@ -1376,8 +1464,6 @@ static int on_pause( struct omxplayer_video_player *player; int ok; - printf("on_pause\n"); - ok = get_player_from_map_arg(arg, &player, responsehandle); if (ok != 0) return ok; @@ -1409,14 +1495,38 @@ static int on_create_platform_view( ); } - ok = compositor_set_platform_view_present_cb(view_id, on_present, player); - if (ok != 0) { - return platch_respond_native_error_std(responsehandle, ok); - } - - player->view_id = view_id; + if (player->has_view) { + /* don't change the view id anymore. Let the platform-side handle multiple omxplayer views for one player instance. - return platch_respond_success_std(responsehandle, NULL); + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to register more than one platform view for this player instance. flutter-pi will dispose the currently registered view.\n"); + + ok = compositor_hack_change_view_id(player->view_id, view_id); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + player->view_id = view_id; + + return platch_respond_success_std(responsehandle, ok); + */ + + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to register more than one platform view for one player instance.\n"); + + return platch_respond_illegal_arg_std( + responsehandle, + "Attempted to register more than one platform view for this player instance." + ); + } else { + ok = compositor_set_view_callbacks(view_id, on_present, player); + if (ok != 0) { + return platch_respond_native_error_std(responsehandle, ok); + } + + player->has_view = true; + player->view_id = view_id; + + return platch_respond_success_std(responsehandle, NULL); + } } static int on_dispose_platform_view( @@ -1424,14 +1534,57 @@ static int on_dispose_platform_view( FlutterPlatformMessageResponseHandle* responsehandle ) { struct omxplayer_video_player *player; + struct std_value *temp; + int64_t view_id; int ok; ok = get_player_from_map_arg(arg, &player, responsehandle); if (ok != 0) return ok; - player->view_id = -1; + temp = stdmap_get_str(arg, "platformViewId"); + if (STDVALUE_IS_INT(*temp)) { + view_id = STDVALUE_AS_INT(*temp); + } else { + return platch_respond_illegal_arg_std( + responsehandle, + "Expected `arg['platformViewId']` to be an integer." + ); + } - return platch_respond_success_std(responsehandle, NULL); + if (player->view_id != view_id) { + fprintf( + stderr, + "[omxplayer_video_player plugin] Flutter attempted to dispose an omxplayer platform view that is not associated with this player.\n" + ); + + return platch_respond_illegal_arg_std(responsehandle, "Attempted to dispose on omxplayer view that is not associated with `arg['playerId']`."); + } else { + ok = compositor_remove_view_callbacks(view_id); + if (ok != 0) { + fprintf( + stderr, + "[omxplayer_video_player plugin] Could not remove view callbacks for platform view %lld. compositor_remove_view_callbacks: %s\n", + view_id, + strerror(ok) + ); + return platch_respond_native_error_std(responsehandle, ok); + } + + player->has_view = false; + player->view_id = -1; + + // hide omxplayer + cqueue_enqueue(&player->mgr->task_queue, &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .offset_x = 0, + .offset_y = 0, + .width = 1, + .height = 1, + .zpos = -128 + }); + + return platch_respond_success_std(responsehandle, NULL); + } } /// Called when a platform channel object is received on the method channel. @@ -1473,8 +1626,6 @@ static int on_receive_mch( int omxpvidpp_init(void) { int ok; - omxpvidpp.next_unused_player_id = 1; - ok = plugin_registry_set_receiver("flutter.io/omxplayerVideoPlayer", kStandardMethodCall, on_receive_mch); if (ok != 0) return ok; From 629c0160cbf1d67a1fe6e2572d57d931e48c9a4e Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 14 Jun 2020 17:17:24 +0200 Subject: [PATCH 05/14] atomic modesetting, omxplayer rotation - implement atomic modesetting - make omxplayer satisfy device orientation --- Makefile | 7 +- include/compositor.h | 52 ++- include/flutter-pi.h | 21 +- include/modesetting.h | 197 ++++++++ include/plugins/video_player.h | 1 + src/compositor.c | 685 +++++++++++++-------------- src/flutter-pi.c | 813 +++++++++++++++------------------ src/modesetting.c | 768 +++++++++++++++++++++++++++++++ src/plugins/video_player.c | 10 +- 9 files changed, 1726 insertions(+), 828 deletions(-) create mode 100644 include/modesetting.h create mode 100644 src/modesetting.c diff --git a/Makefile b/Makefile index 358eec07..c13c390f 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,13 @@ REAL_LDFLAGS = $(shell pkg-config --libs gbm libdrm glesv2 egl) \ -lm \ $(LDFLAGS) -SOURCES = src/flutter-pi.c src/platformchannel.c src/pluginregistry.c src/console_keyboard.c src/texture_registry.c \ +SOURCES = src/flutter-pi.c \ + src/platformchannel.c \ + src/pluginregistry.c \ + src/console_keyboard.c \ + src/texture_registry.c \ src/compositor.c \ + src/modesetting.c \ src/plugins/elm327plugin.c src/plugins/services.c src/plugins/testplugin.c src/plugins/text_input.c \ src/plugins/raw_keyboard.c src/plugins/gpiod.c src/plugins/spidev.c src/plugins/video_player.c OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) diff --git a/include/compositor.h b/include/compositor.h index e19490ab..0f0a777d 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -6,18 +6,12 @@ #include #include -typedef int (*platform_view_mount_cb)( - int64_t view_id, - void *userdata -); - -typedef int (*platform_view_unmount_cb)( - int64_t view_id, - void *userdata -); +#include +#include typedef int (*platform_view_present_cb)( int64_t view_id, + struct drmdev_atomic_req *req, const FlutterPlatformViewMutation **mutations, size_t num_mutations, int offset_x, @@ -28,9 +22,19 @@ typedef int (*platform_view_present_cb)( void *userdata ); +struct flutterpi_compositor { + struct drmdev *drmdev; + struct concurrent_pointer_set cbs; + struct concurrent_pointer_set planes; + bool should_create_window_surface_backing_store; + bool has_applied_modeset; +}; + struct window_surface_backing_store { + struct flutterpi_compositor *compositor; struct gbm_surface *gbm_surface; struct gbm_bo *current_front_bo; + uint32_t drm_plane_id; }; struct drm_rbo { @@ -41,15 +45,9 @@ struct drm_rbo { uint32_t drm_fb_id; }; -struct drm_fb_backing_store { - /*EGLImage egl_image; - GLuint gl_fbo_id; - GLuint gl_rbo_id; - uint32_t gem_handle; - uint32_t gem_stride; - uint32_t drm_fb_id;*/ - - // Our two +struct drm_fb_backing_store { + struct flutterpi_compositor *compositor; + GLuint gl_fbo_id; struct drm_rbo rbos[2]; @@ -58,7 +56,6 @@ struct drm_fb_backing_store { int current_front_rbo; uint32_t drm_plane_id; - int64_t current_zpos; }; enum backing_store_type { @@ -76,7 +73,10 @@ struct backing_store_metadata { extern const FlutterCompositor flutter_compositor; -uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo); +int compositor_on_page_flip( + uint32_t sec, + uint32_t usec +); int compositor_set_view_callbacks( int64_t view_id, @@ -88,4 +88,16 @@ int compositor_remove_view_callbacks( int64_t view_id ); +int compositor_reserve_plane( + uint32_t *plane_id_out +); + +int compositor_free_plane( + uint32_t plane_id +); + +int compositor_initialize( + struct drmdev *drmdev +); + #endif \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 63d860b6..90a4e3b7 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -21,6 +21,8 @@ #include #include +#include + enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight }; @@ -38,6 +40,7 @@ enum device_orientation { extern enum device_orientation orientation; +extern int rotation; typedef enum { kVBlankRequest, @@ -110,16 +113,18 @@ struct mousepointer_mtslot { }; extern struct drm { - char device[PATH_MAX]; - bool has_device; - int fd; - uint32_t connector_id; - drmModeModeInfo *mode; - uint32_t crtc_id; - size_t crtc_index; + //char device[PATH_MAX]; + //bool has_device; + //int fd; + //uint32_t connector_id; + //drmModeModeInfo *mode; + //uint32_t crtc_id; + //size_t crtc_index; struct gbm_bo *current_bo; drmEventContext evctx; + bool disable_vsync; + struct drmdev *drmdev; } drm; extern struct gbm { @@ -250,8 +255,6 @@ extern FlutterEngine engine; struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo); -void *proc_resolver(void* userdata, const char* name); - void post_platform_task(struct flutterpi_task *task); int flutterpi_send_platform_message(const char *channel, diff --git a/include/modesetting.h b/include/modesetting.h new file mode 100644 index 00000000..2e17226e --- /dev/null +++ b/include/modesetting.h @@ -0,0 +1,197 @@ +#ifndef _MODESETTING_H +#define _MODESETTING_H + +#include +#include + +#include +#include + +struct drm_connector { + drmModeConnector *connector; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; +}; + +struct drm_encoder { + drmModeEncoder *encoder; +}; + +struct drm_crtc { + drmModeCrtc *crtc; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; +}; + +struct drm_plane { + drmModePlane *plane; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; +}; + +struct drmdev { + int fd; + + pthread_mutex_t mutex; + + size_t n_connectors; + struct drm_connector *connectors; + + size_t n_encoders; + struct drm_encoder *encoders; + + int n_crtcs; + struct drm_crtc *crtcs; + + int n_planes; + struct drm_plane *planes; + + drmModeRes *res; + drmModePlaneRes *plane_res; + + bool is_configured; + const struct drm_connector *selected_connector; + const struct drm_encoder *selected_encoder; + const struct drm_crtc *selected_crtc; + const drmModeModeInfo *selected_mode; + uint32_t selected_mode_blob_id; +}; + +struct drmdev_atomic_req { + struct drmdev *drmdev; + drmModeAtomicReq *atomic_req; +}; + +int drmdev_new_from_fd( + struct drmdev **drmdev_out, + int fd +); + +int drmdev_new_from_path( + struct drmdev **drmdev_out, + const char *path +); + +int drmdev_configure( + struct drmdev *drmdev, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + const drmModeModeInfo *mode +); + +int drmdev_new_atomic_req( + struct drmdev *drmdev, + struct drmdev_atomic_req **req_out +); + +void drmdev_destroy_atomic_req( + struct drmdev_atomic_req *req +); + +int drmdev_atomic_req_put_connector_property( + struct drmdev_atomic_req *req, + const char *name, + uint64_t value +); + +int drmdev_atomic_req_put_crtc_property( + struct drmdev_atomic_req *req, + const char *name, + uint64_t value +); + +int drmdev_atomic_req_put_plane_property( + struct drmdev_atomic_req *req, + uint32_t plane_id, + const char *name, + uint64_t value +); + +int drmdev_atomic_req_put_modeset_props( + struct drmdev_atomic_req *req, + uint32_t *flags +); + +int drmdev_atomic_req_commit( + struct drmdev_atomic_req *req, + uint32_t flags, + void *userdata +); + +inline static struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { + bool found = connector == NULL; + for (int i = 0; i < drmdev->n_connectors; i++) { + if (drmdev->connectors + i == connector) { + found = true; + } else if (found) { + return drmdev->connectors + i; + } + } + + return NULL; +} + +inline static struct drm_encoder *__next_encoder(const struct drmdev *drmdev, const struct drm_encoder *encoder) { + bool found = encoder == NULL; + for (int i = 0; i < drmdev->n_encoders; i++) { + if (drmdev->encoders + i == encoder) { + found = true; + } else if (found) { + return drmdev->encoders + i; + } + } + + return NULL; +} + +inline static struct drm_crtc *__next_crtc(const struct drmdev *drmdev, const struct drm_crtc *crtc) { + bool found = crtc == NULL; + for (int i = 0; i < drmdev->n_crtcs; i++) { + if (drmdev->crtcs + i == crtc) { + found = true; + } else if (found) { + return drmdev->crtcs + i; + } + } + + return NULL; +} + +inline static struct drm_plane *__next_plane(const struct drmdev *drmdev, const struct drm_plane *plane) { + bool found = plane == NULL; + for (int i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes + i == plane) { + found = true; + } else if (found) { + return drmdev->planes + i; + } + } + + return NULL; +} + +inline static drmModeModeInfo *__next_mode(const struct drm_connector *connector, const drmModeModeInfo *mode) { + bool found = mode == NULL; + for (int i = 0; i < connector->connector->count_modes; i++) { + if (connector->connector->modes + i == mode) { + found = true; + } else if (found) { + return connector->connector->modes + i; + } + } + + return NULL; +} + +#define for_each_connector_in_drmdev(drmdev, connector) for (connector = __next_connector(drmdev, NULL); connector != NULL; connector = __next_connector(drmdev, connector)) + +#define for_each_encoder_in_drmdev(drmdev, encoder) for (encoder = __next_encoder(drmdev, NULL); encoder != NULL; encoder = __next_encoder(drmdev, encoder)) + +#define for_each_crtc_in_drmdev(drmdev, crtc) for (crtc = __next_crtc(drmdev, NULL); crtc != NULL; crtc = __next_crtc(drmdev, crtc)) + +#define for_each_plane_in_drmdev(drmdev, plane) for (plane = __next_plane(drmdev, NULL); plane != NULL; plane = __next_plane(drmdev, plane)) + +#define for_each_mode_in_connector(connector, mode) for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) + +#endif \ No newline at end of file diff --git a/include/plugins/video_player.h b/include/plugins/video_player.h index 4d1055f3..a21152de 100644 --- a/include/plugins/video_player.h +++ b/include/plugins/video_player.h @@ -408,6 +408,7 @@ struct omxplayer_mgr_task { union { struct { + int orientation; char *omxplayer_dbus_name; bool omxplayer_online; }; diff --git a/src/compositor.c b/src/compositor.c index a485d7c4..057ac260 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -23,26 +23,57 @@ struct view_cb_data { void *userdata; }; -struct flutterpi_compositor { - bool should_create_window_surface_backing_store; - struct concurrent_pointer_set cbs; -} flutterpi_compositor = { +struct plane_reservation_data { + const struct drm_plane *plane; + bool is_reserved; +}; + +struct flutterpi_compositor flutterpi_compositor = { + .drmdev = NULL, + .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .planes = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .has_applied_modeset = false, .should_create_window_surface_backing_store = true, - .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) }; -static bool should_create_window_surface_backing_store = true; -static void destroy_gbm_bo(struct gbm_bo *bo, void *userdata) { +static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { + struct view_cb_data *data; + + for_each_pointer_in_cpset(&flutterpi_compositor.cbs, data) { + if (data->view_id == view_id) { + return data; + } + } + + return NULL; +} + +static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { + struct view_cb_data *data; + + cpset_lock(&flutterpi_compositor.cbs); + data = get_cbs_for_view_id_locked(view_id); + cpset_unlock(&flutterpi_compositor.cbs); + + return data; +} + +/// GBM BO funcs for the main (window) surface +static void destroy_gbm_bo( + struct gbm_bo *bo, + void *userdata +) { struct drm_fb *fb = userdata; if (fb && fb->fb_id) - drmModeRmFB(drm.fd, fb->fb_id); + drmModeRmFB(drm.drmdev->fd, fb->fb_id); free(fb); } -uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { +/// Get a DRM FB id for this GBM BO, so we can display it. +static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; int ok = -1; @@ -74,7 +105,7 @@ uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { flags = DRM_MODE_FB_MODIFIERS; } - ok = drmModeAddFB2WithModifiers(drm.fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); + ok = drmModeAddFB2WithModifiers(drm.drmdev->fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); if (ok) { if (flags) @@ -84,7 +115,7 @@ uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { 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); + ok = drmModeAddFB2(drm.drmdev->fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); } if (ok) { @@ -98,234 +129,7 @@ uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { return fb->fb_id; } -/// Find the next DRM overlay plane that has no FB associated with it -static uint32_t find_next_unused_drm_plane(void) { - uint32_t result; - bool has_result; - - has_result = false; - - drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm.fd); - for (int i = 0; (i < plane_res->count_planes) && !has_result; i++) { - drmModePlane *plane = drmModeGetPlane(drm.fd, plane_res->planes[i]); - drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, plane->plane_id, DRM_MODE_OBJECT_ANY); - - for (int j = 0; (j < props->count_props) && !has_result; j++) { - drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[j]); - - if ((strcmp(prop->name, "type") == 0) - && (props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY)) { - - result = plane->plane_id; - has_result = true; - } - - drmModeFreeProperty(prop); - } - - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); - } - drmModeFreePlaneResources(plane_res); - - return has_result? result : 0; -} - -static int set_property_value( - const uint32_t object_id, - const uint32_t object_type, - char name[32], - uint64_t value -) { - bool has_result = false; - int ok; - - drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, object_id, object_type); - if (props == NULL) { - perror("[compositor] Could not get object properties. drmModeObjectGetProperties"); - return errno; - } - - for (int i = 0; (i < props->count_props) && (!has_result); i++) { - drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[i]); - - if (strcmp(prop->name, name) == 0) { - ok = drmModeObjectSetProperty(drm.fd, object_id, object_type, prop->prop_id, value); - if (ok == -1) { - perror("Could not set object property. drmModeObjectSetProperty"); - return errno; - } - - has_result = true; - } - - drmModeFreeProperty(prop); - } - - drmModeFreeObjectProperties(props); - - if (!has_result) { - fprintf(stderr, "[compositor] Could not find any property with name %32s\n", name); - } - - return has_result? 0 : ENOENT; -} - -static int set_plane_property_value( - const uint32_t plane_id, - char name[32], - uint64_t value -) { - return set_property_value( - plane_id, - DRM_MODE_OBJECT_PLANE, - name, - value - ); -} - -static int get_property_value( - const uint32_t object_id, - const uint32_t object_type, - char name[32], - uint64_t *value_out -) { - bool has_result = false; - - drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, object_id, object_type); - if (props == NULL) { - perror("[compositor] Could not get object properties. drmModeObjectGetProperties"); - return errno; - } - - for (int i = 0; (i < props->count_props) && (!has_result); i++) { - drmModePropertyRes *prop = drmModeGetProperty(drm.fd, props->props[i]); - - if (strcmp(prop->name, name) == 0) { - *value_out = props->prop_values[i]; - has_result = true; - } - - drmModeFreeProperty(prop); - } - - drmModeFreeObjectProperties(props); - - if (!has_result) { - fprintf(stderr, "[compositor] Could not find any property with name %32s\n", name); - } - - return has_result? 0 : ENOENT; -} - -static int get_plane_property_value( - const uint32_t plane_id, - char name[32], - uint64_t *value_out -) { - return get_property_value( - plane_id, - DRM_MODE_OBJECT_ANY, - name, - value_out - ); -} - -/// Destroy the OpenGL ES framebuffer-object that is associated with this -/// EGL Window Surface backed backing store -static void destroy_window_surface_backing_store_fb(void *userdata) { - struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; - - printf("destroying window surface backing store FBO\n"); -} - -static void destroy_drm_rbo( - struct drm_rbo *rbo -) { - EGLint egl_error; - GLenum gl_error; - int ok; - - eglGetError(); - glGetError(); - - glDeleteRenderbuffers(1, &rbo->gl_rbo_id); - if (gl_error = glGetError()) { - fprintf(stderr, "[compositor] error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); - } - - ok = drmModeRmFB(drm.fd, rbo->drm_fb_id); - if (ok < 0) { - fprintf(stderr, "[compositor] error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); - } - - eglDestroyImage(egl.display, rbo->egl_image); - if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { - fprintf(stderr, "[compositor] error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); - } -} - -/// Destroy the OpenGL ES framebuffer-object that is associated with this -/// DRM FB backed backing store -static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { - struct backing_store_metadata *meta; - EGLint egl_error; - GLenum gl_error; - int ok; - - meta = (struct backing_store_metadata*) userdata; - - printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); - - eglGetError(); - glGetError(); - - glDeleteFramebuffers(1, &meta->drm_fb.gl_fbo_id); - if (gl_error = glGetError()) { - fprintf(stderr, "[compositor] error destroying OpenGL FBO, glDeleteFramebuffers: %d\n", gl_error); - } - - destroy_drm_rbo(meta->drm_fb.rbos + 0); - destroy_drm_rbo(meta->drm_fb.rbos + 1); - - free(meta); -} - - -/// Create a flutter backing store that is backed by the -/// EGL window surface (created using libgbm). -/// I.e. create a EGL window surface and set fbo_id to 0. -static int create_window_surface_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, - void *user_data -) { - struct backing_store_metadata *meta; - - printf("create_window_surface_backing_store()\n"); - - meta = malloc(sizeof(*meta)); - if (meta == NULL) { - perror("[compositor] Could not allocate metadata for backing store. malloc"); - return ENOMEM; - } - memset(meta, 0, sizeof(*meta)); - - meta->type = kWindowSurface; - meta->window_surface.current_front_bo = drm.current_bo; - meta->window_surface.gbm_surface = gbm.surface; - - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; - backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; - backing_store_out->open_gl.framebuffer.name = 0; - backing_store_out->open_gl.framebuffer.destruction_callback = destroy_window_surface_backing_store_fb; - backing_store_out->open_gl.framebuffer.user_data = meta; - backing_store_out->user_data = meta; - - return 0; -} - +/// Create a GL renderbuffer that is backed by a DRM buffer-object and registered as a DRM framebuffer static int create_drm_rbo( size_t width, size_t height, @@ -403,7 +207,7 @@ static int create_drm_rbo( // glBindFramebuffer(GL_FRAMEBUFFER, 0); ok = drmModeAddFB2( - drm.fd, + drm.drmdev->fd, width, height, DRM_FORMAT_ARGB8888, @@ -432,6 +236,7 @@ static int create_drm_rbo( return 0; } +/// Set the color attachment of a GL FBO to this DRM RBO. static int attach_drm_rbo_to_fbo( GLuint fbo_id, struct drm_rbo *rbo @@ -463,11 +268,123 @@ static int attach_drm_rbo_to_fbo( return 0; } -/// Create a flutter backing store that +/// Destroy the OpenGL ES framebuffer-object that is associated with this +/// EGL Window Surface backed backing store +static void destroy_window_surface_backing_store_fb(void *userdata) { + struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; + + printf("destroying window surface backing store FBO\n"); +} + +/// Destroy this GL renderbuffer, and the associated DRM buffer-object and DRM framebuffer +static void destroy_drm_rbo( + struct drm_rbo *rbo +) { + EGLint egl_error; + GLenum gl_error; + int ok; + + eglGetError(); + glGetError(); + + glDeleteRenderbuffers(1, &rbo->gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); + } + + ok = drmModeRmFB(drm.drmdev->fd, rbo->drm_fb_id); + if (ok < 0) { + fprintf(stderr, "[compositor] error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); + } + + eglDestroyImage(egl.display, rbo->egl_image); + if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { + fprintf(stderr, "[compositor] error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); + } +} + +/// Destroy the OpenGL ES framebuffer-object that is associated with this +/// DRM FB backed backing store +static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { + struct backing_store_metadata *meta; + EGLint egl_error; + GLenum gl_error; + int ok; + + meta = (struct backing_store_metadata*) userdata; + + printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); + + eglGetError(); + glGetError(); + + glDeleteFramebuffers(1, &meta->drm_fb.gl_fbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error destroying OpenGL FBO, glDeleteFramebuffers: %d\n", gl_error); + } + + destroy_drm_rbo(meta->drm_fb.rbos + 0); + destroy_drm_rbo(meta->drm_fb.rbos + 1); + + free(meta); +} + +int compositor_on_page_flip( + uint32_t sec, + uint32_t usec +) { + //x + return 0; +} + +/// CREATE FUNCS + +/// Create a flutter backing store that is backed by the +/// EGL window surface (created using libgbm). +/// I.e. create a EGL window surface and set fbo_id to 0. +static int create_window_surface_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + struct flutterpi_compositor *compositor +) { + struct backing_store_metadata *meta; + int ok; + + printf("create_window_surface_backing_store()\n"); + + meta = calloc(1, sizeof *meta); + if (meta == NULL) { + perror("[compositor] Could not allocate metadata for backing store. calloc"); + return ENOMEM; + } + + ok = compositor_reserve_plane(&meta->window_surface.drm_plane_id); + if (ok != 0) { + free(meta); + fprintf(stderr, "[compositor] Could not find an unused DRM plane for flutter backing store creation. compositor_reserve_plane: %s\n", strerror(ok)); + return ok; + } + + meta->type = kWindowSurface; + meta->window_surface.compositor = compositor; + meta->window_surface.current_front_bo = drm.current_bo; + meta->window_surface.gbm_surface = gbm.surface; + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; + backing_store_out->open_gl.framebuffer.name = 0; + backing_store_out->open_gl.framebuffer.destruction_callback = destroy_window_surface_backing_store_fb; + backing_store_out->open_gl.framebuffer.user_data = meta; + backing_store_out->user_data = meta; + + return 0; +} + static int create_drm_fb_backing_store( const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, - void *user_data + struct flutterpi_compositor *compositor ) { struct backing_store_metadata *meta; struct drm_fb_backing_store *inner; @@ -478,23 +395,23 @@ static int create_drm_fb_backing_store( printf("create_drm_fb_backing_store\n"); - plane_id = find_next_unused_drm_plane(); - if (!plane_id) { - fprintf(stderr, "[compositor] Could not find an unused DRM overlay plane for flutter backing store creation.\n"); - return false; - } - - meta = malloc(sizeof(struct backing_store_metadata)); + meta = calloc(1, sizeof *meta); if (meta == NULL) { - perror("[compositor] Could not allocate backing store metadata, malloc"); - return false; + perror("[compositor] Could not allocate backing store metadata, calloc"); + return ENOMEM; } - - memset(meta, 0, sizeof(*meta)); meta->type = kDrmFb; - meta->drm_fb.drm_plane_id = plane_id; + inner = &meta->drm_fb; + inner->compositor = compositor; + + ok = compositor_reserve_plane(&inner->drm_plane_id); + if (ok != 0) { + free(meta); + fprintf(stderr, "[compositor] Could not find an unused DRM plane for flutter backing store creation. compositor_reserve_plane: %s\n", strerror(ok)); + return ok; + } eglGetError(); glGetError(); @@ -502,6 +419,8 @@ static int create_drm_fb_backing_store( glGenFramebuffers(1, &inner->gl_fbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); + compositor_free_plane(inner->drm_plane_id); + free(meta); return EINVAL; } @@ -510,18 +429,37 @@ static int create_drm_fb_backing_store( config->size.height, inner->rbos + 0 ); - if (ok != 0) return ok; + if (ok != 0) { + glDeleteFramebuffers(1, &inner->gl_fbo_id); + compositor_free_plane(inner->drm_plane_id); + free(meta); + return ok; + } ok = create_drm_rbo( config->size.width, config->size.height, inner->rbos + 1 ); - if (ok != 0) return ok; + if (ok != 0) { + destroy_drm_rbo(inner->rbos + 0); + glDeleteFramebuffers(1, &inner->gl_fbo_id); + compositor_free_plane(inner->drm_plane_id); + free(meta); + return ok; + } ok = attach_drm_rbo_to_fbo(inner->gl_fbo_id, inner->rbos + inner->current_front_rbo); - if (ok != 0) return ok; + if (ok != 0) { + destroy_drm_rbo(inner->rbos + 1); + destroy_drm_rbo(inner->rbos + 0); + glDeleteFramebuffers(1, &inner->gl_fbo_id); + compositor_free_plane(inner->drm_plane_id); + free(meta); + return ok; + } + /* ok = get_plane_property_value(inner->drm_plane_id, "zpos", &inner->current_zpos); if (ok != 0) { return false; @@ -550,8 +488,9 @@ static int create_drm_fb_backing_store( perror("[compositor] Could not attach DRM framebuffer to hardware plane. drmModeSetPlane"); return false; } - + get_plane_property_value(inner->drm_plane_id, "zpos", (uint64_t*) &inner->current_zpos); + */ backing_store_out->type = kFlutterBackingStoreTypeOpenGL; backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; @@ -571,7 +510,7 @@ static bool create_backing_store( ) { int ok; - if (should_create_window_surface_backing_store) { + if (flutterpi_compositor.should_create_window_surface_backing_store) { // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY // plane. That backing store isn't really a backing store at all, it's // FBO id is 0, so it's actually rendering to the window surface. @@ -585,7 +524,7 @@ static bool create_backing_store( return false; } - should_create_window_surface_backing_store = false; + flutterpi_compositor.should_create_window_surface_backing_store = false; } else { // After the primary plane backing store was created, // we only create overlay plane backing stores. I.e. @@ -606,6 +545,7 @@ static bool create_backing_store( return true; } +/// COLLECT FUNCS static int collect_window_surface_backing_store( const FlutterBackingStore *store, struct backing_store_metadata *meta @@ -629,6 +569,8 @@ static int collect_drm_fb_backing_store( // FlutterBackingStore OpenGL framebuffer destroy callback. Thanks flutter. (/s) // free(inner); + compositor_free_plane(inner->drm_plane_id); + return 0; } @@ -648,7 +590,7 @@ static bool collect_backing_store( return false; } - should_create_window_surface_backing_store = true; + flutterpi_compositor.should_create_window_surface_backing_store = true; } else if (meta->type == kDrmFb) { ok = collect_drm_fb_backing_store(renderer, meta); if (ok != 0) { @@ -666,13 +608,15 @@ static bool collect_backing_store( return true; } - +/// PRESENT FUNCS static int present_window_surface_backing_store( struct window_surface_backing_store *backing_store, + struct drmdev_atomic_req *atomic_req, int offset_x, int offset_y, int width, - int height + int height, + int zpos ) { struct gbm_bo *next_front_bo; uint32_t next_front_fb_id; @@ -681,20 +625,17 @@ static int present_window_surface_backing_store( next_front_bo = gbm_surface_lock_front_buffer(backing_store->gbm_surface); next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - // workaround for #38 - if (!drm.disable_vsync) { - ok = drmModePageFlip(drm.fd, drm.crtc_id, next_front_fb_id, DRM_MODE_PAGE_FLIP_EVENT, backing_store->current_front_bo); - if (ok) { - perror("failed to queue page flip"); - return false; - } - } else { - ok = drmModeSetCrtc(drm.fd, drm.crtc_id, next_front_fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok == -1) { - perror("failed swap buffers"); - return false; - } - } + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "FB_ID", next_front_fb_id); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_X", 0); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_Y", 0); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) width) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) height) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_X", 0); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_Y", 0); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_W", width); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_H", height); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "zpos", zpos); // TODO: move this to the page flip handler. // We can only be sure the buffer can be released when the buffer swap @@ -707,6 +648,7 @@ static int present_window_surface_backing_store( static int present_drm_fb_backing_store( struct drm_fb_backing_store *backing_store, + struct drmdev_atomic_req *req, int offset_x, int offset_y, int width, @@ -715,19 +657,12 @@ static int present_drm_fb_backing_store( ) { int ok; - if (zpos != backing_store->current_zpos) { - ok = set_plane_property_value(backing_store->drm_plane_id, "zpos", zpos); - if (ok != 0) { - perror("[compositor] Could not update zpos of hardware layer. drmModeObjectSetProperty"); - return errno; - } - } - backing_store->current_front_rbo ^= 1; ok = attach_drm_rbo_to_fbo(backing_store->gl_fbo_id, backing_store->rbos + backing_store->current_front_rbo); if (ok != 0) return ok; // present the back buffer + /* ok = drmModeSetPlane( drm.fd, backing_store->drm_plane_id, @@ -741,34 +676,33 @@ static int present_drm_fb_backing_store( perror("[compositor] Could not update overlay plane for presenting a DRM FB backed backing store. drmModeSetPlane"); return errno; } - - return 0; -} + */ -static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { - struct view_cb_data *data; + printf("present drm_fb, zpos: %d\n", zpos); + + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "FB_ID", backing_store->rbos[backing_store->current_front_rbo ^ 1].drm_fb_id); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_X", 0); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_Y", 0); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) width) << 16); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) height) << 16); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_X", 0); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_Y", 0); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_W", width); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_H", height); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - for_each_pointer_in_cpset(&flutterpi_compositor.cbs, data) { - if (data->view_id == view_id) { - return data; - } + ok = drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "zpos", zpos); + if (ok != 0) { + fprintf(stderr, "Could not put zpos plane property: %s\n", strerror(ok)); } - return NULL; -} - -static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { - struct view_cb_data *data; - - cpset_lock(&flutterpi_compositor.cbs); - data = get_cbs_for_view_id_locked(view_id); - cpset_unlock(&flutterpi_compositor.cbs); - - return data; + return 0; } static int present_platform_view( int64_t view_id, + struct drmdev_atomic_req *req, const FlutterPlatformViewMutation **mutations, size_t num_mutations, int offset_x, @@ -787,6 +721,7 @@ static int present_platform_view( if (cbs->present != NULL) { return cbs->present( view_id, + req, mutations, num_mutations, offset_x, @@ -806,9 +741,15 @@ static bool present_layers_callback( size_t layers_count, void *user_data ) { - int window_surface_index; + struct plane_reservation_data *data; + struct flutterpi_compositor *compositor; + struct drmdev_atomic_req *req; + uint32_t req_flags; int ok; + compositor = user_data; + + /* printf("[compositor] present_layers_callback(\n" " layers_count: %lu,\n" " layers = {\n", @@ -896,12 +837,14 @@ static bool present_layers_callback( printf(" },\n"); } printf(" }\n)\n"); + */ FlutterEngineTraceEventDurationBegin("present"); eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); eglSwapBuffers(egl.display, egl.surface); + /* /// find the index of the window surface. /// the window surface's zpos can't change, so we need to /// normalize all other backing stores' zpos around the @@ -916,6 +859,16 @@ static bool present_layers_callback( break; } + }*/ + + drmdev_new_atomic_req(compositor->drmdev, &req); + + req_flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + if (compositor->has_applied_modeset == false) { + ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); + if (ok != 0) return false; + + compositor->has_applied_modeset = true; } for (int i = 0; i < layers_count; i++) { @@ -928,78 +881,59 @@ static bool present_layers_callback( if (meta->type == kWindowSurface) { ok = present_window_surface_backing_store( &meta->window_surface, + req, (int) layer->offset.x, (int) layer->offset.y, (int) layer->size.width, - (int) layer->size.height + (int) layer->size.height, + 0 ); } else if (meta->type == kDrmFb) { ok = present_drm_fb_backing_store( &meta->drm_fb, + req, (int) layer->offset.x, (int) layer->offset.y, (int) layer->size.width, (int) layer->size.height, - i - window_surface_index + 1 ); } } else if (layer->type == kFlutterLayerContentTypePlatformView) { ok = present_platform_view( layer->platform_view->identifier, + req, layer->platform_view->mutations, layer->platform_view->mutations_count, (int) round(layer->offset.x), (int) round(layer->offset.y), (int) round(layer->size.width), (int) round(layer->size.height), - i - window_surface_index + 0 ); } else { fprintf(stderr, "[compositor] Unsupported flutter layer type: %d\n", layer->type); } } - + eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - FlutterEngineTraceEventDurationEnd("present"); - - return true; -} - -/* -int compositor_set_platform_view_present_cb(int64_t view_id, platform_view_present_cb cb, void *userdata) { - struct view_cb_data *entry; - - cpset_lock(&flutterpi_compositor.cbs); - - for_each_pointer_in_cpset(&flutterpi_compositor.cbs, entry) { - if (entry->view_id == view_id) { - break; + for_each_pointer_in_cpset(&compositor->planes, data) { + if (data->is_reserved == false) { + drmdev_atomic_req_put_plane_property(req, data->plane->plane->plane_id, "FB_ID", 0); } } - if (entry && !cb) { - cpset_remove_locked(&flutterpi_compositor.cbs, entry); - } else if (!entry && cb) { - entry = calloc(1, sizeof(*entry)); - if (!entry) { - cpset_unlock(&flutterpi_compositor.cbs); - return ENOMEM; - } + drmdev_atomic_req_commit(req, req_flags, NULL); - entry->view_id = view_id; - entry->present = present; - entry->mount = mount; - entry->unmount = unmount; - entry->userdata = userdata; + drmdev_destroy_atomic_req(req); - cpset_put_locked(&flutterpi_compositor.cbs, entry); - } + FlutterEngineTraceEventDurationEnd("present"); - return cpset_unlock(&flutterpi_compositor.cbs); + return true; } -*/ +/// PLATFORM VIEW CALLBACKS int compositor_set_view_callbacks( int64_t view_id, platform_view_present_cb present, @@ -1043,6 +977,75 @@ int compositor_remove_view_callbacks(int64_t view_id) { return cpset_unlock(&flutterpi_compositor.cbs); } +/// DRM HARDWARE PLANE RESERVATION +int compositor_reserve_plane(uint32_t *plane_id_out) { + struct plane_reservation_data *data; + + cpset_lock(&flutterpi_compositor.planes); + + for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) { + if (data->is_reserved == false) { + data->is_reserved = true; + cpset_unlock(&flutterpi_compositor.planes); + + *plane_id_out = data->plane->plane->plane_id; + return 0; + } + } + + cpset_unlock(&flutterpi_compositor.planes); + + *plane_id_out = 0; + return EBUSY; +} + +int compositor_free_plane(uint32_t plane_id) { + struct plane_reservation_data *data; + + cpset_lock(&flutterpi_compositor.planes); + + for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) { + if (data->plane->plane->plane_id == plane_id) { + data->is_reserved = false; + cpset_unlock(&flutterpi_compositor.planes); + return 0; + } + } + + cpset_unlock(&flutterpi_compositor.planes); + return EINVAL; +} + +/// COMPOSITOR INITIALIZATION +int compositor_initialize(struct drmdev *drmdev) { + struct plane_reservation_data *data; + const struct drm_plane *plane; + + cpset_lock(&flutterpi_compositor.planes); + + for_each_plane_in_drmdev(drmdev, plane) { + data = calloc(1, sizeof (struct plane_reservation_data)); + if (data == NULL) { + for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) + free(data); + cpset_unlock(&flutterpi_compositor.planes); + return ENOMEM; + } + + data->plane = plane; + data->is_reserved = false; + + cpset_put_locked(&flutterpi_compositor.planes, data); + } + + cpset_unlock(&flutterpi_compositor.planes); + + flutterpi_compositor.drmdev = drmdev; + + return 0; +} + + const FlutterCompositor flutter_compositor = { .struct_size = sizeof(FlutterCompositor), .create_backing_store_callback = create_backing_store, diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 5511bb28..b60c9096 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -158,7 +158,7 @@ _Atomic bool engine_running = false; /********************* * FLUTTER CALLBACKS * *********************/ -bool make_current(void* userdata) { +static bool on_make_current(void* userdata) { if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.flutter_render_context) != EGL_TRUE) { fprintf(stderr, "make_current: could not make the context current. eglMakeCurrent: %d\n", eglGetError()); return false; @@ -166,7 +166,8 @@ bool make_current(void* userdata) { return true; } -bool clear_current(void* userdata) { + +static bool on_clear_current(void* userdata) { if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { fprintf(stderr, "clear_current: could not clear the current context.\n"); return false; @@ -174,22 +175,34 @@ bool clear_current(void* userdata) { return true; } -void pageflip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *userdata) { + +static bool on_present(void *userdata) { + +} + +static void on_pageflip_event( + int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + void *userdata +) { FlutterEngineTraceEventInstant("pageflip"); + + compositor_on_page_flip(sec, usec); + post_platform_task(&(struct flutterpi_task) { .type = kVBlankReply, .target_time = 0, .vblank_ns = sec*1000000000ull + usec*1000ull, }); } -bool present(void* userdata) { - // NOP - return true; -} -uint32_t fbo_callback(void* userdata) { + +static uint32_t fbo_callback(void* userdata) { return 0; } -bool make_resource_current(void *userdata) { + +static bool on_make_resource_current(void *userdata) { if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl.flutter_resource_uploading_context) != EGL_TRUE) { fprintf(stderr, "make_resource_current: could not make the resource context current. eglMakeCurrent: %d\n", eglGetError()); return false; @@ -197,7 +210,11 @@ bool make_resource_current(void *userdata) { return true; } -void cut_word_from_string(char* string, char* word) { + +static void cut_word_from_string( + char* string, + char* word +) { size_t word_length = strlen(word); char* word_in_str = strstr(string, word); @@ -214,7 +231,8 @@ void cut_word_from_string(char* string, char* word) { } while (word_in_str[i++ + word_length] != 0); } } -const GLubyte *hacked_glGetString(GLenum name) { + +static const GLubyte *hacked_glGetString(GLenum name) { static GLubyte *extensions = NULL; if (name != GL_EXTENSIONS) @@ -296,7 +314,11 @@ const GLubyte *hacked_glGetString(GLenum name) { return extensions; } -void *proc_resolver(void* userdata, const char* name) { + +static void *proc_resolver( + void* userdata, + const char* name +) { static int is_VC4 = -1; void *address; @@ -325,19 +347,28 @@ void *proc_resolver(void* userdata, const char* name) { return NULL; } -void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { + +static void on_platform_message( + const FlutterPlatformMessage* message, + void* userdata +) { int ok; if ((ok = plugin_registry_on_platform_message((FlutterPlatformMessage *)message)) != 0) fprintf(stderr, "plugin_registry_on_platform_message failed: %s\n", strerror(ok)); } -void vsync_callback(void* userdata, intptr_t baton) { + +static void vsync_callback( + void* userdata, + intptr_t baton +) { post_platform_task(&(struct flutterpi_task) { .type = kVBlankRequest, .target_time = 0, .baton = baton }); } -FlutterTransformation transformation_callback(void *userdata) { + +static FlutterTransformation transformation_callback(void *userdata) { // report a transform based on the current device orientation. static bool _transformsInitialized = false; static FlutterTransformation rotate0, rotate90, rotate180, rotate270; @@ -373,11 +404,12 @@ FlutterTransformation transformation_callback(void *userdata) { /************************ * PLATFORM TASK-RUNNER * ************************/ -bool init_message_loop() { +static bool init_message_loop(void) { platform_thread_id = pthread_self(); return true; } -bool message_loop(void) { + +static bool message_loop(void) { struct timespec abstargetspec; uint64_t currenttime, abstarget; intptr_t baton; @@ -412,7 +444,7 @@ bool message_loop(void) { if (scheduled_frames == 0) { baton = task->baton; has_baton = true; - drmCrtcGetSequence(drm.fd, drm.crtc_id, NULL, &ns); + drmCrtcGetSequence(drm.drmdev->fd, drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); } else { batons[(i_batons + (scheduled_frames-1)) & 63] = task->baton; } @@ -492,7 +524,8 @@ bool message_loop(void) { return true; } -void post_platform_task(struct flutterpi_task *task) { + +void post_platform_task(struct flutterpi_task *task) { struct flutterpi_task *to_insert; to_insert = malloc(sizeof(*task)); @@ -512,20 +545,29 @@ void post_platform_task(struct flutterpi_task *task) { pthread_mutex_unlock(&tasklist_lock); pthread_cond_signal(&task_added); } -void flutter_post_platform_task(FlutterTask task, uint64_t target_time, void* userdata) { + +static void flutter_post_platform_task( + FlutterTask task, + uint64_t target_time, + void* userdata +) { post_platform_task(&(struct flutterpi_task) { .type = kFlutterTask, .task = task, .target_time = target_time }); } -bool runs_platform_tasks_on_current_thread(void* userdata) { + +static bool runs_platform_tasks_on_current_thread(void* userdata) { return pthread_equal(pthread_self(), platform_thread_id) != 0; } -int flutterpi_send_platform_message(const char *channel, - const uint8_t *restrict message, - size_t message_size, - FlutterPlatformMessageResponseHandle *responsehandle) { + +int flutterpi_send_platform_message( + const char *channel, + const uint8_t *restrict message, + size_t message_size, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct flutterpi_task *task; int ok; @@ -567,9 +609,12 @@ int flutterpi_send_platform_message(const char *channel, return 0; } -int flutterpi_respond_to_platform_message(FlutterPlatformMessageResponseHandle *handle, - const uint8_t *restrict message, - size_t message_size) { + +int flutterpi_respond_to_platform_message( + FlutterPlatformMessageResponseHandle *handle, + const uint8_t *restrict message, + size_t message_size +) { struct flutterpi_task *task; int ok; @@ -610,7 +655,7 @@ int flutterpi_respond_to_platform_message(FlutterPlatformMessageResponseHandle /****************** * INITIALIZATION * ******************/ -bool setup_paths(void) { +static bool setup_paths(void) { #define PATH_EXISTS(path) (access((path),R_OK)==0) if (!PATH_EXISTS(flutter.asset_bundle_path)) { @@ -638,7 +683,7 @@ bool setup_paths(void) { #undef PATH_EXISTS } -int load_gl_procs(void) { +static int load_gl_procs(void) { LOAD_EGL_PROC(getPlatformDisplay); LOAD_EGL_PROC(createPlatformWindowSurface); LOAD_EGL_PROC(createPlatformPixmapSurface); @@ -651,324 +696,168 @@ int load_gl_procs(void) { return 0; } -bool init_display(void) { +static int init_display() { /********************** * DRM INITIALIZATION * **********************/ - - drmModeRes *resources = NULL; - drmModeConnector *connector; - drmModeEncoder *encoder = NULL; - int i, ok, area; + const struct drm_connector *connector; + const struct drm_encoder *encoder; + const struct drm_crtc *crtc; + const drmModeModeInfo *mode; + struct drmdev *drmdev; + drmDevicePtr devices[64]; + EGLint egl_error; + GLenum gl_error; + int ok, area, num_devices; - if (!drm.has_device) { - printf("Finding a suitable DRM device, since none is given...\n"); - drmDevicePtr devices[64] = { NULL }; - int num_devices, fd = -1; - - num_devices = drmGetDevices2(0, devices, sizeof(devices)/sizeof(drmDevicePtr)); - if (num_devices < 0) { - fprintf(stderr, "could not query drm device list: %s\n", strerror(-num_devices)); - return false; - } + num_devices = drmGetDevices2(0, devices, sizeof(devices)/sizeof(*devices)); + if (num_devices < 0) { + fprintf(stderr, "[flutter-pi] Could not query DRM device list: %s\n", strerror(-num_devices)); + return -num_devices; + } + + for (int i = 0; i < num_devices; i++) { + drmDevicePtr device; - printf("looking for a suitable DRM device from %d available DRM devices...\n", num_devices); - for (i = 0; i < num_devices; i++) { - drmDevicePtr device = devices[i]; - - printf(" devices[%d]: \n", i); - - printf(" available nodes: "); - if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) printf("DRM_NODE_PRIMARY, "); - if (device->available_nodes & (1 << DRM_NODE_CONTROL)) printf("DRM_NODE_CONTROL, "); - if (device->available_nodes & (1 << DRM_NODE_RENDER)) printf("DRM_NODE_RENDER"); - printf("\n"); - - for (int j=0; j < DRM_NODE_MAX; j++) { - if (device->available_nodes & (1 << j)) { - printf(" nodes[%s] = \"%s\"\n", - j == DRM_NODE_PRIMARY ? "DRM_NODE_PRIMARY" : - j == DRM_NODE_CONTROL ? "DRM_NODE_CONTROL" : - j == DRM_NODE_RENDER ? "DRM_NODE_RENDER" : "unknown", - device->nodes[j] - ); - } - } - - printf(" bustype: %s\n", - device->bustype == DRM_BUS_PCI ? "DRM_BUS_PCI" : - device->bustype == DRM_BUS_USB ? "DRM_BUS_USB" : - device->bustype == DRM_BUS_PLATFORM ? "DRM_BUS_PLATFORM" : - device->bustype == DRM_BUS_HOST1X ? "DRM_BUS_HOST1X" : - "unknown" - ); + device = devices[i]; - if (device->bustype == DRM_BUS_PLATFORM) { - printf(" businfo.fullname: %s\n", device->businfo.platform->fullname); - // seems like deviceinfo.platform->compatible is not really used. - //printf(" deviceinfo.compatible: %s\n", device->deviceinfo.platform->compatible); - } - - // we want a device that's DRM_NODE_PRIMARY and that we can call a drmModeGetResources on. - if (drm.has_device) continue; - if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) continue; - - printf(" opening DRM device candidate at \"%s\"...\n", device->nodes[DRM_NODE_PRIMARY]); - fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR); - if (fd < 0) { - printf(" could not open DRM device candidate at \"%s\": %s\n", device->nodes[DRM_NODE_PRIMARY], strerror(errno)); - continue; - } - - printf(" getting resources of DRM device candidate at \"%s\"...\n", device->nodes[DRM_NODE_PRIMARY]); - resources = drmModeGetResources(fd); - if (resources == NULL) { - printf(" could not query DRM resources for DRM device candidate at \"%s\":", device->nodes[DRM_NODE_PRIMARY]); - if ((errno = EOPNOTSUPP) || (errno = EINVAL)) printf("doesn't look like a modeset device.\n"); - else printf("%s\n", strerror(errno)); - close(fd); - continue; - } - - // we found our DRM device. - printf(" flutter-pi chose \"%s\" as its DRM device.\n", device->nodes[DRM_NODE_PRIMARY]); - drm.fd = fd; - drm.has_device = true; - snprintf(drm.device, sizeof(drm.device)-1, "%s", device->nodes[DRM_NODE_PRIMARY]); + if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { + // We need a primary node. + continue; } - if (!drm.has_device) { - fprintf(stderr, "flutter-pi couldn't find a usable DRM device.\n" - "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" - "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n"); - return false; + ok = drmdev_new_from_path(&drmdev, device->nodes[DRM_NODE_PRIMARY]); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); + continue; } - } - if (drm.fd <= 0) { - 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("[flutter-pi] Chose \"%s\" as the DRM device.\n", device->nodes[DRM_NODE_PRIMARY]); + break; } - if (!resources) { - printf("Getting DRM resources...\n"); - resources = drmModeGetResources(drm.fd); - if (resources == NULL) { - if ((errno == EOPNOTSUPP) || (errno = EINVAL)) - fprintf(stderr, "%s doesn't look like a modeset device\n", drm.device); - else - fprintf(stderr, "drmModeGetResources failed: %s\n", strerror(errno)); - - return false; - } + if (drmdev == NULL) { + fprintf(stderr, "flutter-pi couldn't find a usable DRM device.\n" + "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" + "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n"); + return false; } - - printf("Finding a connected connector from %d available connectors...\n", resources->count_connectors); - connector = NULL; - for (i = 0; i < resources->count_connectors; i++) { - drmModeConnector *conn = drmModeGetConnector(drm.fd, resources->connectors[i]); - - printf(" connectors[%d]: connected? %s, type: 0x%02X%s, %umm x %umm\n", - i, - (conn->connection == DRM_MODE_CONNECTED) ? "yes" : - (conn->connection == DRM_MODE_DISCONNECTED) ? "no" : "unknown", - conn->connector_type, - (conn->connector_type == DRM_MODE_CONNECTOR_HDMIA) ? " (HDMI-A)" : - (conn->connector_type == DRM_MODE_CONNECTOR_HDMIB) ? " (HDMI-B)" : - (conn->connector_type == DRM_MODE_CONNECTOR_DSI) ? " (DSI)" : - (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ? " (DisplayPort)" : "", - conn->mmWidth, conn->mmHeight - ); - - if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) { - connector = conn; - + for_each_connector_in_drmdev(drmdev, connector) { + if (connector->connector->connection == DRM_MODE_CONNECTED) { // only update the physical size of the display if the values // are not yet initialized / not set with a commandline option if ((width_mm == 0) && (height_mm == 0)) { - if ((conn->mmWidth == 160) && (conn->mmHeight == 90)) { + if ((connector->connector->mmWidth == 160) && + (connector->connector->mmHeight == 90)) + { // if width and height is exactly 160mm x 90mm, the values are probably bogus. width_mm = 0; height_mm = 0; - } else if ((conn->connector_type == DRM_MODE_CONNECTOR_DSI) && (conn->mmWidth == 0) && (conn->mmHeight == 0)) { + } else if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && + (connector->connector->mmWidth == 0) && + (connector->connector->mmHeight == 0)) + { // if it's connected via DSI, and the width & height are 0, // it's probably the official 7 inch touchscreen. width_mm = 155; height_mm = 86; } else { - width_mm = conn->mmWidth; - height_mm = conn->mmHeight; + width_mm = connector->connector->mmWidth; + height_mm = connector->connector->mmHeight; } } - } else { - drmModeFreeConnector(conn); + + break; } } - if (!connector) { - fprintf(stderr, "could not find a connected connector!\n"); - return false; - } - printf("Choosing DRM mode from %d available modes...\n", connector->count_modes); - bool found_preferred = false; - for (i = 0, area = 0; i < connector->count_modes; i++) { - drmModeModeInfo *current_mode = &connector->modes[i]; - - printf(" modes[%d]: name: \"%s\", %ux%u%s, %uHz, type: %u, flags: %u\n", - i, current_mode->name, current_mode->hdisplay, current_mode->vdisplay, - (current_mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "p", - current_mode->vrefresh, current_mode->type, current_mode->flags - ); - - if (found_preferred) continue; - - // we choose the highest resolution with the highest refresh rate, preferably non-interlaced (= progressive) here. - int current_area = current_mode->hdisplay * current_mode->vdisplay; - if (( current_area > area) || - ((current_area == area) && (current_mode->vrefresh > refresh_rate)) || - ((current_area == area) && (current_mode->vrefresh == refresh_rate) && ((current_mode->flags & DRM_MODE_FLAG_INTERLACE) == 0)) || - ( current_mode->type & DRM_MODE_TYPE_PREFERRED)) { - - drm.mode = current_mode; - width = current_mode->hdisplay; - height = current_mode->vdisplay; - refresh_rate = current_mode->vrefresh; - area = current_area; - orientation = width >= height ? kLandscapeLeft : kPortraitUp; - - // if the preferred DRM mode is bogus, we're screwed. - if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { - printf(" this mode is preferred by DRM. (DRM_MODE_TYPE_PREFERRED)\n"); - found_preferred = true; + if (connector == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a connected connector!\n"); + return EINVAL; + } + + { + drmModeModeInfo *mode_iter; + for_each_mode_in_connector(connector, mode_iter) { + if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { + mode = mode_iter; + break; + } else if (mode == NULL) { + mode = mode_iter; + } else { + int area = mode_iter->hdisplay * mode_iter->vdisplay; + int old_area = mode->hdisplay * mode->vdisplay; + + if ((area > old_area) || + ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || + ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { + mode = mode_iter; + } } } } - if (!drm.mode) { - fprintf(stderr, "could not find a suitable DRM mode!\n"); - return false; + if (mode == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a preferred output mode!\n"); + return EINVAL; } - - // calculate the pixel ratio + + width = mode->hdisplay; + height = mode->vdisplay; + refresh_rate = mode->vrefresh; + orientation = width >= height ? kLandscapeLeft : kPortraitUp; + if (pixel_ratio == 0.0) { if ((width_mm == 0) || (height_mm == 0)) { pixel_ratio = 1.0; } else { pixel_ratio = (10.0 * width) / (width_mm * 38.0); - if (pixel_ratio < 1.0) pixel_ratio = 1.0; + // lets try if this works. + // if (pixel_ratio < 1.0) pixel_ratio = 1.0; } } - 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) + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->connector->encoder_id) { break; - drmModeFreeEncoder(encoder); - encoder = NULL; - } - - if (encoder) { - drm.crtc_id = encoder->crtc_id; - } else { - fprintf(stderr, "could not find a suitable crtc!\n"); - return false; - } - - drmSetClientCap(drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - - printf("drm_crtcs = {\n"); - drm.crtc_index = 0; - for (i = 0; i < resources->count_crtcs; i++) { - if ((resources->crtcs[i] == drm.crtc_id) && (!drm.crtc_index)) { - drm.crtc_index = i; } - - drmModeCrtc *crtc = drmModeGetCrtc(drm.fd, resources->crtcs[i]); - drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, crtc->crtc_id, DRM_MODE_OBJECT_ANY); - - printf(" [%d] = {,\n", i); - printf(" crtc_id: %lu\n", crtc->crtc_id); - printf(" properties: {\n"); - for (int j = 0; j < props->count_props; j++) { - printf(" [0x%08lX] = 0x%016llX,\n", props->props[j], props->prop_values[j]); - } - printf(" },\n"); - printf(" },\n"); + } - drmModeFreeObjectProperties(props); - drmModeFreeCrtc(crtc); + if (encoder == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a suitable DRM encoder.\n"); + return EINVAL; } - printf("}\n"); - drmModePlaneResPtr plane_res = drmModeGetPlaneResources(drm.fd); - - printf("drm_planes = {\n"); - for (i = 0; i < plane_res->count_planes; i++) { - drmModePlane *plane = drmModeGetPlane(drm.fd, plane_res->planes[i]); - drmModeObjectProperties *props = drmModeObjectGetProperties(drm.fd, plane_res->planes[i], DRM_MODE_OBJECT_ANY); - - printf(" [%d] = {,\n", i); - printf(" plane_id: %lu\n", plane->plane_id); - printf(" x: %lu, y: %lu,\n", plane->x, plane->y); - printf(" crtc_x: %lu,\n crtc_y: %lu,\n", plane->crtc_x, plane->crtc_y); - printf(" crtc_id: %lu,\n", plane->crtc_id); - printf(" fb_id: %lu,\n", plane->fb_id); - printf(" gamma_size: %lu,\n", plane->gamma_size); - printf(" possible_crtcs: %lu,\n", plane->possible_crtcs); - printf(" properties: {\n"); - for (int j = 0; j < props->count_props; j++) { - drmModePropertyPtr prop = drmModeGetProperty(drm.fd, props->props[j]); - - printf(" %s: 0x%016llX,\n", prop->name, props->prop_values[j]); - - drmModeFreeProperty(prop); + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { + break; } - printf(" },\n"); - printf(" },\n"); - - drmModeFreeObjectProperties(props); - drmModeFreePlane(plane); } - printf("}\n"); - - drmModeFreePlaneResources(plane_res); - - drmModeFreeResources(resources); - drm.connector_id = connector->connector_id; + if (crtc == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a suitable DRM CRTC.\n"); + return EINVAL; + } + ok = drmdev_configure(drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); + if (ok != 0) return ok; + printf("[flutter-pi] 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); /********************** * GBM INITIALIZATION * **********************/ printf("Creating GBM device\n"); - gbm.device = gbm_create_device(drm.fd); + gbm.device = gbm_create_device(drmdev->fd); gbm.format = DRM_FORMAT_ARGB8888; gbm.surface = NULL; gbm.modifier = DRM_FORMAT_MOD_LINEAR; gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); - - if (!gbm.surface) { - if (gbm.modifier != DRM_FORMAT_MOD_LINEAR) { - 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; + if (gbm.surface == NULL) { + perror("[flutter-pi] Could not create GBM Surface. gbm_surface_create_with_modifiers"); + return errno; } /********************** @@ -990,41 +879,37 @@ bool init_display(void) { 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); - printf("Loading EGL / GL ES procedure addresses...\n"); ok = load_gl_procs(); if (ok != 0) { - fprintf(stderr, "Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); - return false; + fprintf(stderr, "[flutter-pi] Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); + return ok; } - printf("Getting EGL display for GBM device...\n"); + eglGetError(); + #ifdef EGL_KHR_platform_gbm - if (egl.getPlatformDisplay) { - egl.display = egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); - } else -#endif - { - egl.display = eglGetDisplay((void*) gbm.device); + egl.display = egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not get EGL display! eglGetPlatformDisplay: 0x%08X\n", egl_error); + return EIO; } - - if (!egl.display) { - fprintf(stderr, "Couldn't get EGL display\n"); - return false; +#else + egl.display = eglGetDisplay((void*) gbm.device); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not get EGL display! eglGetDisplay: 0x%08X\n", egl_error); + return EIO; } - - printf("Initializing EGL...\n"); - if (!eglInitialize(egl.display, &major, &minor)) { - fprintf(stderr, "failed to initialize EGL\n"); - return false; +#endif + + eglInitialize(egl.display, &major, &minor); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Failed to initialize EGL! eglInitialize: 0x%08X\n", egl_error); + return EIO; } - 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 %p with EGL version %d.%d\n", egl.display, major, minor); printf("===================================\n"); @@ -1035,26 +920,24 @@ bool init_display(void) { 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; + eglBindAPI(EGL_OPENGL_ES_API); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Failed to bind OpenGL ES API! eglBindAPI: 0x%08X\n", egl_error); + return EIO; } - - printf("Choosing EGL config...\n"); EGLint count = 0, matched = 0; EGLConfig *configs; bool _found_matching_config = false; - if (!eglGetConfigs(egl.display, NULL, 0, &count) || count < 1) { - fprintf(stderr, "No EGL configs to choose from.\n"); - return false; + eglGetConfigs(egl.display, NULL, 0, &count); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not get the number of EGL framebuffer configurations. eglGetConfigs: 0x%08X\n", egl_error); + return EIO; } configs = malloc(count * sizeof(EGLConfig)); - if (!configs) return false; + if (!configs) return ENOMEM; /* eglGetConfigs(egl.display, configs, count * sizeof(EGLConfig), &count); @@ -1149,36 +1032,39 @@ bool init_display(void) { } */ - 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; + eglChooseConfig(egl.display, config_attribs, configs, count, &matched); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", egl_error); + return EIO; } - 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 (matched == 0) { + fprintf(stderr, "[flutter-pi] No fitting EGL framebuffer configuration found.\n"); + return EIO; + } - if (id == gbm.format) { - egl.config = configs[i]; - _found_matching_config = true; - break; - } + for (int i = 0; i < count; i++) { + EGLint native_visual_id; + + eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &native_visual_id); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not query native visual ID of EGL config. eglGetConfigAttrib: 0x%08X\n", egl_error); + continue; + } + + if (native_visual_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; + if (_found_matching_config == false) { + fprintf(stderr, "[flutter-pi] Could not find EGL framebuffer configuration with appropriate attributes & native visual ID.\n"); + return EIO; } - - printf("Creating EGL contexts...\n"); egl.root_context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); if (egl.root_context == NULL) { fprintf(stderr, "failed to create OpenGL ES root context\n"); @@ -1203,24 +1089,16 @@ bool init_display(void) { return false; } -#ifdef BUILD_VIDEO_PLAYER_PLUGIN - egl.vidpp_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); - if (egl.vidpp_context == NULL) { - fprintf(stderr, "failed to OpenGL ES context for video player plugin texture uploads\n"); - return false; - } -#endif - - 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 ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not create EGL window surface. eglCreateWindowSurface: 0x%08X\n", egl_error); + return EIO; } - if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context)) { - fprintf(stderr, "Could not make OpenGL ES root context current to get OpenGL information\n"); - return false; + eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); + return EIO; } egl.renderer = (char*) glGetString(GL_RENDERER); @@ -1239,23 +1117,29 @@ bool init_display(void) { // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ // as read-write. flutter-pi must be run as root then. // sometimes it works fine without root, sometimes it doesn't. - if (strncmp(egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) + if (strncmp(egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) { printf("WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" " Check that flutter-pi has permission to use the 3D graphics hardware,\n" " or try running it as root.\n" " This warning will probably result in a \"failed to set mode\" error\n" " later on in the initialization.\n"); + } drm.evctx = (drmEventContext) { .version = 4, .vblank_handler = NULL, - .page_flip_handler = pageflip_handler, + .page_flip_handler = on_pageflip_event, .page_flip_handler2 = NULL, .sequence_handler = NULL }; + /* printf("Swapping buffers...\n"); eglSwapBuffers(egl.display, egl.surface); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); + return EIO; + } printf("Locking front buffer...\n"); @@ -1268,81 +1152,92 @@ bool init_display(void) { return false; } - printf("Setting CRTC...\n"); ok = drmModeSetCrtc(drm.fd, drm.crtc_id, current_fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok) { + if (ok < 0) { fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); return false; } + */ - 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; + eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); + return EIO; + } + + ok = compositor_initialize(drmdev); + if (ok != 0) { + return ok; } - printf("finished display setup!\n"); + drm.drmdev = drmdev; - return true; + return 0; } -void destroy_display(void) { + +static void destroy_display(void) { fprintf(stderr, "Deinitializing display not yet implemented\n"); } -bool init_application(void) { +static int init_application(void) { + FlutterEngineResult engine_result; + uint64_t ns; int ok, _errno; - printf("Initializing Plugin Registry...\n"); ok = plugin_registry_init(); if (ok != 0) { - fprintf(stderr, "Could not initialize plugin registry: %s\n", strerror(ok)); - return false; + fprintf(stderr, "[flutter-pi] Could not initialize plugin registry: %s\n", strerror(ok)); + return ok; } // configure flutter rendering + memset(&flutter.renderer_config, 0, sizeof(FlutterRendererConfig)); 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.make_resource_current = make_resource_current; - flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; - flutter.renderer_config.open_gl.surface_transformation - = transformation_callback; - flutter.renderer_config.open_gl.gl_external_texture_frame_callback - = texreg_gl_external_texture_frame_callback; - - // configure flutter - flutter.args.struct_size = sizeof(FlutterProjectArgs); - flutter.args.assets_path = flutter.asset_bundle_path; - flutter.args.icu_data_path = flutter.icu_data_path; - flutter.args.isolate_snapshot_data_size = 0; - flutter.args.isolate_snapshot_data = NULL; - flutter.args.isolate_snapshot_instructions_size = 0; - flutter.args.isolate_snapshot_instructions = NULL; - flutter.args.vm_snapshot_data_size = 0; - flutter.args.vm_snapshot_data = NULL; - flutter.args.vm_snapshot_instructions_size = 0; - flutter.args.vm_snapshot_instructions = NULL; - flutter.args.command_line_argc = flutter.engine_argc; - flutter.args.command_line_argv = flutter.engine_argv; - flutter.args.platform_message_callback = on_platform_message; - flutter.args.custom_task_runners = &(FlutterCustomTaskRunners) { - .struct_size = sizeof(FlutterCustomTaskRunners), - .platform_task_runner = &(FlutterTaskRunnerDescription) { - .struct_size = sizeof(FlutterTaskRunnerDescription), - .user_data = NULL, - .runs_task_on_current_thread_callback = &runs_platform_tasks_on_current_thread, - .post_task_callback = &flutter_post_platform_task - } + flutter.renderer_config.open_gl = (FlutterOpenGLRendererConfig) { + .struct_size = sizeof(FlutterOpenGLRendererConfig), + .make_current = on_make_current, + .clear_current = on_clear_current, + .present = on_present, + .fbo_callback = fbo_callback, + .make_resource_current = on_make_resource_current, + .gl_proc_resolver = proc_resolver, + .surface_transformation = transformation_callback, + .gl_external_texture_frame_callback = texreg_gl_external_texture_frame_callback + }; + + // configure the project + memset(&flutter.args, 0, sizeof(FlutterProjectArgs)); + flutter.args = (FlutterProjectArgs) { + .struct_size = sizeof(FlutterProjectArgs), + .assets_path = flutter.asset_bundle_path, + .icu_data_path = flutter.icu_data_path, + .isolate_snapshot_data_size = 0, + .isolate_snapshot_data = NULL, + .isolate_snapshot_instructions_size = 0, + .isolate_snapshot_instructions = NULL, + .vm_snapshot_data_size = 0, + .vm_snapshot_data = NULL, + .vm_snapshot_instructions_size = 0, + .vm_snapshot_instructions = NULL, + .command_line_argc = flutter.engine_argc, + .command_line_argv = flutter.engine_argv, + .platform_message_callback = on_platform_message, + .custom_task_runners = &(FlutterCustomTaskRunners) { + .struct_size = sizeof(FlutterCustomTaskRunners), + .platform_task_runner = &(FlutterTaskRunnerDescription) { + .struct_size = sizeof(FlutterTaskRunnerDescription), + .user_data = NULL, + .runs_task_on_current_thread_callback = &runs_platform_tasks_on_current_thread, + .post_task_callback = &flutter_post_platform_task + } + }, + .compositor = &flutter_compositor }; - flutter.args.compositor = &flutter_compositor; // only enable vsync if the kernel supplies valid vblank timestamps - uint64_t ns = 0; - ok = drmCrtcGetSequence(drm.fd, drm.crtc_id, NULL, &ns); - if (ok != 0) _errno = errno; + ns = 0; + ok = drmCrtcGetSequence(drm.drmdev->fd, drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); + _errno = errno; if ((ok == 0) && (ns != 0)) { drm.disable_vsync = false; @@ -1350,60 +1245,60 @@ bool init_application(void) { } else { drm.disable_vsync = true; if (ok != 0) { - fprintf(stderr, - "WARNING: Could not get last vblank timestamp. %s\n", strerror(_errno)); + fprintf( + stderr, + "WARNING: Could not get last vblank timestamp. %s\n", + strerror(_errno) + ); } else { - fprintf(stderr, - "WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n"); + fprintf( + stderr, + "WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n" + ); } - fprintf(stderr, - " VSync will be disabled.\n" - " See https://github.com/ardera/flutter-pi/issues/38 for more info.\n"); + fprintf( + stderr, + " VSync will be disabled.\n" + " See https://github.com/ardera/flutter-pi/issues/38 for more info.\n" + ); } // spin up the engine - FlutterEngineResult _result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &flutter.renderer_config, &flutter.args, NULL, &engine); - if (_result != kSuccess) { - fprintf(stderr, "Could not run the flutter engine\n"); - return false; - } else { - printf("flutter engine successfully started up.\n"); + engine_result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &flutter.renderer_config, &flutter.args, NULL, &engine); + if (engine_result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not run the flutter engine.\n"); + return EINVAL; } engine_running = true; // update window size - ok = FlutterEngineSendWindowMetricsEvent( + engine_result = FlutterEngineSendWindowMetricsEvent( engine, - &(FlutterWindowMetricsEvent) {.struct_size = sizeof(FlutterWindowMetricsEvent), .width=width, .height=height, .pixel_ratio = pixel_ratio} - ) == kSuccess; - - if (!ok) { - fprintf(stderr, "Could not update Flutter application size.\n"); - return false; + &(FlutterWindowMetricsEvent) { + .struct_size = sizeof(FlutterWindowMetricsEvent), + .width=width, + .height=height, + .pixel_ratio = pixel_ratio + } + ); + if (engine_result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not send window metrics to flutter engine.\n"); + return EINVAL; } - return true; + return 0; } -void destroy_application(void) { - int ok; - - if (engine != NULL) { - if (FlutterEngineShutdown(engine) != kSuccess) - fprintf(stderr, "Could not shutdown the flutter engine.\n"); - engine = NULL; - } - - if ((ok = plugin_registry_deinit()) != 0) { - fprintf(stderr, "Could not deinitialize plugin registry: %s\n", strerror(ok)); - } +static void destroy_application(void) { + FlutterEngineShutdown(engine); + plugin_registry_deinit(); } /**************** * Input-Output * ****************/ -void init_io(void) { +static void init_io(void) { int ok; int n_flutter_slots = 0; FlutterPointerEvent flutterevents[64] = {0}; @@ -1564,7 +1459,8 @@ void init_io(void) { ok = kSuccess == FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent); if (!ok) fprintf(stderr, "error while sending initial mousepointer / multitouch slot information to flutter\n"); } -void on_evdev_input(fd_set fds, size_t n_ready_fds) { + +static void on_evdev_input(fd_set fds, size_t n_ready_fds) { struct input_event linuxevents[64]; size_t n_linuxevents; struct input_device *device; @@ -1760,7 +1656,8 @@ void on_evdev_input(fd_set fds, size_t n_ready_fds) { fprintf(stderr, "could not send pointer events to flutter engine\n"); } } -void on_console_input(void) { + +static void on_console_input(void) { static char buffer[4096]; glfw_key key; char *cursor; @@ -1791,7 +1688,8 @@ void on_console_input(void) { } } } -void *io_loop(void *userdata) { + +static void *io_loop(void *userdata) { fd_set read_fds; fd_set write_fds; fd_set except_fds; @@ -1809,8 +1707,8 @@ void *io_loop(void *userdata) { if (input_devices[i].fd + 1 > nfds) nfds = input_devices[i].fd + 1; } - FD_SET(drm.fd, &read_fds); - if (drm.fd + 1 > nfds) nfds = drm.fd + 1; + FD_SET(drm.drmdev->fd, &read_fds); + if (drm.drmdev->fd + 1 > nfds) nfds = drm.drmdev->fd + 1; FD_SET(STDIN_FILENO, &read_fds); @@ -1830,9 +1728,9 @@ void *io_loop(void *userdata) { continue; } - if (FD_ISSET(drm.fd, &read_fds)) { - drmHandleEvent(drm.fd, &drm.evctx); - FD_CLR(drm.fd, &read_fds); + if (FD_ISSET(drm.drmdev->fd, &read_fds)) { + drmHandleEvent(drm.drmdev->fd, &drm.evctx); + FD_CLR(drm.drmdev->fd, &read_fds); n_ready_fds--; } @@ -1853,7 +1751,8 @@ void *io_loop(void *userdata) { return NULL; } -bool run_io_thread(void) { + +static bool run_io_thread(void) { int ok = pthread_create(&io_thread_id, NULL, &io_loop, NULL); if (ok != 0) { fprintf(stderr, "couldn't create flutter-pi io thread: [%s]", strerror(ok)); @@ -1870,7 +1769,7 @@ bool run_io_thread(void) { } -bool parse_cmd_args(int argc, char **argv) { +static bool parse_cmd_args(int argc, char **argv) { bool input_specified = false; int ok, opt, index = 0; input_devices_glob = (glob_t) {0}; @@ -1912,6 +1811,7 @@ bool parse_cmd_args(int argc, char **argv) { return true; } int main(int argc, char **argv) { + int ok; if (!parse_cmd_args(argc, argv)) { return EXIT_FAILURE; } @@ -1926,14 +1826,15 @@ int main(int argc, char **argv) { } // initialize display - printf("initializing display...\n"); - if (!init_display()) { + ok = init_display(); + if (ok != 0) { return EXIT_FAILURE; } // initialize application printf("Initializing Application...\n"); - if (!init_application()) { + ok = init_application(); + if (ok != 0) { return EXIT_FAILURE; } diff --git a/src/modesetting.c b/src/modesetting.c new file mode 100644 index 00000000..29119d79 --- /dev/null +++ b/src/modesetting.c @@ -0,0 +1,768 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static int drmdev_lock(struct drmdev *drmdev) { + return pthread_mutex_lock(&drmdev->mutex); +} + +static int drmdev_unlock(struct drmdev *drmdev) { + return pthread_mutex_unlock(&drmdev->mutex); +} + +static int fetch_connectors(struct drmdev *drmdev, struct drm_connector **connectors_out, size_t *n_connectors_out) { + struct drm_connector *connectors; + int n_allocated_connectors; + int ok; + + connectors = calloc(drmdev->res->count_connectors, sizeof *connectors); + if (connectors == NULL) { + *connectors_out = NULL; + return ENOMEM; + } + + n_allocated_connectors = 0; + for (int i = 0; i < drmdev->res->count_connectors; i++, n_allocated_connectors++) { + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + drmModeConnector *connector; + + connector = drmModeGetConnector(drmdev->fd, drmdev->res->connectors[i]); + if (connector == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device connector. drmModeGetConnector"); + goto fail_free_connectors; + } + + props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->connectors[i], DRM_MODE_OBJECT_CONNECTOR); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device connectors properties. drmModeObjectGetProperties"); + drmModeFreeConnector(connector); + goto fail_free_connectors; + } + + props_info = calloc(props->count_props, sizeof *props_info); + if (props_info == NULL) { + ok = ENOMEM; + drmModeFreeObjectProperties(props); + drmModeFreeConnector(connector); + goto fail_free_connectors; + } + + for (int j = 0; j < props->count_props; j++) { + props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); + if (props_info[j] == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device connector properties' info. drmModeGetProperty"); + for (int k = 0; k < (j-1); k++) + drmModeFreeProperty(props_info[j]); + free(props_info); + drmModeFreeObjectProperties(props); + drmModeFreeConnector(connector); + goto fail_free_connectors; + } + } + + connectors[i].connector = connector; + connectors[i].props = props; + connectors[i].props_info = props_info; + } + + *connectors_out = connectors; + *n_connectors_out = drmdev->res->count_connectors; + + return 0; + + fail_free_connectors: + for (int i = 0; i < n_allocated_connectors; i++) { + for (int j = 0; j < connectors[i].props->count_props; j++) + drmModeFreeProperty(connectors[i].props_info[j]); + free(connectors[i].props_info); + drmModeFreeObjectProperties(connectors[i].props); + drmModeFreeConnector(connectors[i].connector); + } + + fail_free_result: + free(connectors); + + *connectors_out = NULL; + *n_connectors_out = 0; + return ok; +} + +static int free_connectors(struct drm_connector *connectors, size_t n_connectors) { + for (int i = 0; i < n_connectors; i++) { + for (int j = 0; j < connectors[i].props->count_props; j++) + drmModeFreeProperty(connectors[i].props_info[j]); + free(connectors[i].props_info); + drmModeFreeObjectProperties(connectors[i].props); + drmModeFreeConnector(connectors[i].connector); + } + + free(connectors); + + return 0; +} + +static int fetch_encoders(struct drmdev *drmdev, struct drm_encoder **encoders_out, size_t *n_encoders_out) { + struct drm_encoder *encoders; + int n_allocated_encoders; + int ok; + + encoders = calloc(drmdev->res->count_encoders, sizeof *encoders); + if (encoders == NULL) { + *encoders_out = NULL; + *n_encoders_out = 0; + return ENOMEM; + } + + n_allocated_encoders = 0; + for (int i = 0; i < drmdev->res->count_encoders; i++, n_allocated_encoders++) { + drmModeEncoder *encoder; + + encoder = drmModeGetEncoder(drmdev->fd, drmdev->res->encoders[i]); + if (encoder == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device encoder. drmModeGetEncoder"); + goto fail_free_encoders; + } + + encoders[i].encoder = encoder; + } + + *encoders_out = encoders; + *n_encoders_out = drmdev->res->count_encoders; + + return 0; + + fail_free_encoders: + for (int i = 0; i < n_allocated_encoders; i++) { + drmModeFreeEncoder(encoders[i].encoder); + } + + fail_free_result: + free(encoders); + + *encoders_out = NULL; + *n_encoders_out = 0; + return ok; +} + +static int free_encoders(struct drm_encoder *encoders, size_t n_encoders) { + for (int i = 0; i < n_encoders; i++) { + drmModeFreeEncoder(encoders[i].encoder); + } + + free(encoders); + + return 0; +} + +static int fetch_crtcs(struct drmdev *drmdev, struct drm_crtc **crtcs_out, size_t *n_crtcs_out) { + struct drm_crtc *crtcs; + int n_allocated_crtcs; + int ok; + + crtcs = calloc(drmdev->res->count_crtcs, sizeof *crtcs); + if (crtcs == NULL) { + *crtcs_out = NULL; + return ENOMEM; + } + + n_allocated_crtcs = 0; + for (int i = 0; i < drmdev->res->count_crtcs; i++, n_allocated_crtcs++) { + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + drmModeCrtc *crtc; + + crtc = drmModeGetCrtc(drmdev->fd, drmdev->res->crtcs[i]); + if (crtc == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTC. drmModeGetCrtc"); + goto fail_free_crtcs; + } + + props = drmModeObjectGetProperties(drmdev->fd, drmdev->res->crtcs[i], DRM_MODE_OBJECT_CRTC); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties. drmModeObjectGetProperties"); + drmModeFreeCrtc(crtc); + goto fail_free_crtcs; + } + + props_info = calloc(props->count_props, sizeof *props_info); + if (props_info == NULL) { + ok = ENOMEM; + drmModeFreeObjectProperties(props); + drmModeFreeCrtc(crtc); + goto fail_free_crtcs; + } + + for (int j = 0; j < props->count_props; j++) { + props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); + if (props_info[j] == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device CRTCs properties' info. drmModeGetProperty"); + for (int k = 0; k < (j-1); k++) + drmModeFreeProperty(props_info[j]); + free(props_info); + drmModeFreeObjectProperties(props); + drmModeFreeCrtc(crtc); + goto fail_free_crtcs; + } + } + + crtcs[i].crtc = crtc; + crtcs[i].props = props; + crtcs[i].props_info = props_info; + } + + *crtcs_out = crtcs; + *n_crtcs_out = drmdev->res->count_crtcs; + + return 0; + + + fail_free_crtcs: + for (int i = 0; i < n_allocated_crtcs; i++) { + for (int j = 0; j < crtcs[i].props->count_props; j++) + drmModeFreeProperty(crtcs[i].props_info[j]); + free(crtcs[i].props_info); + drmModeFreeObjectProperties(crtcs[i].props); + drmModeFreeCrtc(crtcs[i].crtc); + } + + fail_free_result: + free(crtcs); + + *crtcs_out = NULL; + *n_crtcs_out = 0; + return ok; +} + +static int free_crtcs(struct drm_crtc *crtcs, size_t n_crtcs) { + for (int i = 0; i < n_crtcs; i++) { + for (int j = 0; j < crtcs[i].props->count_props; j++) + drmModeFreeProperty(crtcs[i].props_info[j]); + free(crtcs[i].props_info); + drmModeFreeObjectProperties(crtcs[i].props); + drmModeFreeCrtc(crtcs[i].crtc); + } + + free(crtcs); + + return 0; +} + +static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, size_t *n_planes_out) { + struct drm_plane *planes; + int n_allocated_planes; + int ok; + + planes = calloc(drmdev->plane_res->count_planes, sizeof *planes); + if (planes == NULL) { + *planes_out = NULL; + return ENOMEM; + } + + n_allocated_planes = 0; + for (int i = 0; i < drmdev->plane_res->count_planes; i++, n_allocated_planes++) { + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + drmModePlane *plane; + + plane = drmModeGetPlane(drmdev->fd, drmdev->plane_res->planes[i]); + if (plane == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device plane. drmModeGetPlane"); + goto fail_free_planes; + } + + props = drmModeObjectGetProperties(drmdev->fd, drmdev->plane_res->planes[i], DRM_MODE_OBJECT_PLANE); + if (props == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device planes' properties. drmModeObjectGetProperties"); + drmModeFreePlane(plane); + goto fail_free_planes; + } + + props_info = calloc(props->count_props, sizeof *props_info); + if (props_info == NULL) { + ok = ENOMEM; + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); + goto fail_free_planes; + } + + for (int j = 0; j < props->count_props; j++) { + props_info[j] = drmModeGetProperty(drmdev->fd, props->props[j]); + if (props_info[j] == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device planes' properties' info. drmModeGetProperty"); + for (int k = 0; k < (j-1); k++) + drmModeFreeProperty(props_info[j]); + free(props_info); + drmModeFreeObjectProperties(props); + drmModeFreePlane(plane); + goto fail_free_planes; + } + } + + planes[i].plane = plane; + planes[i].props = props; + planes[i].props_info = props_info; + } + + *planes_out = planes; + *n_planes_out = drmdev->plane_res->count_planes; + + return 0; + + + fail_free_planes: + for (int i = 0; i < n_allocated_planes; i++) { + for (int j = 0; j < planes[i].props->count_props; j++) + drmModeFreeProperty(planes[i].props_info[j]); + free(planes[i].props_info); + drmModeFreeObjectProperties(planes[i].props); + drmModeFreePlane(planes[i].plane); + } + + fail_free_result: + free(planes); + + *planes_out = NULL; + *n_planes_out = 0; + return ok; +} + +static int free_planes(struct drm_plane *planes, size_t n_planes) { + for (int i = 0; i < n_planes; i++) { + for (int j = 0; j < planes[i].props->count_props; j++) + drmModeFreeProperty(planes[i].props_info[j]); + free(planes[i].props_info); + drmModeFreeObjectProperties(planes[i].props); + drmModeFreePlane(planes[i].plane); + } + + free(planes); + + return 0; +} + + +int drmdev_new_from_fd( + struct drmdev **drmdev_out, + int fd +) { + struct drmdev *drmdev; + int ok; + + drmdev = calloc(1, sizeof *drmdev); + if (drmdev == NULL) { + return ENOMEM; + } + + drmdev->fd = fd; + + ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set DRM client universal planes capable. drmSetClientCap"); + goto fail_free_drmdev; + } + + ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set DRM client atomic capable. drmSetClientCap"); + goto fail_free_drmdev; + } + + drmdev->res = drmModeGetResources(drmdev->fd); + if (drmdev->res == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device resources. drmModeGetResources"); + goto fail_free_drmdev; + } + + drmdev->plane_res = drmModeGetPlaneResources(drmdev->fd); + if (drmdev->plane_res == NULL) { + ok = errno; + perror("[modesetting] Could not get DRM device planes resources. drmModeGetPlaneResources"); + goto fail_free_resources; + } + + ok = fetch_connectors(drmdev, &drmdev->connectors, &drmdev->n_connectors); + if (ok != 0) { + goto fail_free_plane_resources; + } + + ok = fetch_encoders(drmdev, &drmdev->encoders, &drmdev->n_encoders); + if (ok != 0) { + goto fail_free_connectors; + } + + ok = fetch_crtcs(drmdev, &drmdev->crtcs, &drmdev->n_crtcs); + if (ok != 0) { + goto fail_free_encoders; + } + + ok = fetch_planes(drmdev, &drmdev->planes, &drmdev->n_planes); + if (ok != 0) { + goto fail_free_crtcs; + } + + *drmdev_out = drmdev; + + return 0; + + + fail_free_crtcs: + free_crtcs(drmdev->crtcs, drmdev->n_crtcs); + + fail_free_encoders: + free_encoders(drmdev->encoders, drmdev->n_encoders); + + fail_free_connectors: + free_connectors(drmdev->connectors, drmdev->n_connectors); + + fail_free_plane_resources: + drmModeFreePlaneResources(drmdev->plane_res); + + fail_free_resources: + drmModeFreeResources(drmdev->res); + + fail_free_drmdev: + free(drmdev); + + return ok; +} + +int drmdev_new_from_path( + struct drmdev **drmdev_out, + const char *path +) { + int ok, fd; + + fd = open(path, O_RDWR); + if (fd < 0) { + perror("[modesetting] Could not open DRM device. open"); + return errno; + } + + ok = drmdev_new_from_fd(drmdev_out, fd); + if (ok != 0) { + close(fd); + return ok; + } + + return 0; +} + +int drmdev_configure( + struct drmdev *drmdev, + uint32_t connector_id, + uint32_t encoder_id, + uint32_t crtc_id, + const drmModeModeInfo *mode +) { + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + uint32_t mode_id; + int ok; + + drmdev_lock(drmdev); + + for_each_connector_in_drmdev(drmdev, connector) { + if (connector->connector->connector_id == connector_id) { + break; + } + } + + if (connector == NULL) { + drmdev_unlock(drmdev); + return EINVAL; + } + + for_each_encoder_in_drmdev(drmdev, encoder) { + if (encoder->encoder->encoder_id == encoder_id) { + break; + } + } + + if (encoder == NULL) { + drmdev_unlock(drmdev); + return EINVAL; + } + + for_each_crtc_in_drmdev(drmdev, crtc) { + if (crtc->crtc->crtc_id == crtc_id) { + break; + } + } + + if (crtc == NULL) { + drmdev_unlock(drmdev); + return EINVAL; + } + + ok = drmModeCreatePropertyBlob(drmdev->fd, mode, sizeof(*mode), &mode_id); + if (ok < 0) { + perror("[modesetting] Could not create property blob for DRM mode. drmModeCreatePropertyBlob"); + drmdev_unlock(drmdev); + return errno; + } + + if (drmdev->selected_mode != NULL) { + ok = drmModeDestroyPropertyBlob(drmdev->fd, drmdev->selected_mode_blob_id); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not destroy old DRM mode property blob. drmModeDestroyPropertyBlob"); + drmModeDestroyPropertyBlob(drmdev->fd, mode_id); + drmdev_unlock(drmdev); + return ok; + } + } + + drmdev->selected_connector = connector; + drmdev->selected_encoder = encoder; + drmdev->selected_crtc = crtc; + drmdev->selected_mode = mode; + drmdev->selected_mode_blob_id = mode_id; + + drmdev->is_configured = true; + + drmdev_unlock(drmdev); + + return 0; +} + +int drmdev_new_atomic_req( + struct drmdev *drmdev, + struct drmdev_atomic_req **req_out +) { + struct drmdev_atomic_req *req; + int ok; + + req = calloc(1, sizeof *req); + if (req == NULL) { + return ENOMEM; + } + + req->drmdev = drmdev; + + req->atomic_req = drmModeAtomicAlloc(); + if (req->atomic_req == NULL) { + free(req); + return ENOMEM; + } + + *req_out = req; + + return 0; +} + +void drmdev_destroy_atomic_req( + struct drmdev_atomic_req *req +) { + drmModeAtomicFree(req->atomic_req); + free(req); +} + +int drmdev_atomic_req_put_connector_property( + struct drmdev_atomic_req *req, + const char *name, + uint64_t value +) { + int ok; + + drmdev_lock(req->drmdev); + + for (int i = 0; i < req->drmdev->selected_connector->props->count_props; i++) { + drmModePropertyRes *prop = req->drmdev->selected_connector->props_info[i]; + if (strcmp(prop->name, name) == 0) { + ok = drmModeAtomicAddProperty( + req->atomic_req, + req->drmdev->selected_connector->connector->connector_id, + prop->prop_id, value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not add connector property to atomic request. drmModeAtomicAddProperty"); + drmdev_unlock(req->drmdev); + return ok; + } + + drmdev_unlock(req->drmdev); + return 0; + } + } + + drmdev_unlock(req->drmdev); + return EINVAL; +} + +int drmdev_atomic_req_put_crtc_property( + struct drmdev_atomic_req *req, + const char *name, + uint64_t value +) { + int ok; + + drmdev_lock(req->drmdev); + + for (int i = 0; i < req->drmdev->selected_crtc->props->count_props; i++) { + drmModePropertyRes *prop = req->drmdev->selected_crtc->props_info[i]; + if (strcmp(prop->name, name) == 0) { + ok = drmModeAtomicAddProperty( + req->atomic_req, + req->drmdev->selected_crtc->crtc->crtc_id, + prop->prop_id, + value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not add crtc property to atomic request. drmModeAtomicAddProperty"); + drmdev_unlock(req->drmdev); + return ok; + } + + drmdev_unlock(req->drmdev); + return 0; + } + } + + drmdev_unlock(req->drmdev); + return EINVAL; +} + +int drmdev_atomic_req_put_plane_property( + struct drmdev_atomic_req *req, + uint32_t plane_id, + const char *name, + uint64_t value +) { + struct drm_plane *plane; + int ok; + + drmdev_lock(req->drmdev); + + plane = NULL; + for (int i = 0; i < req->drmdev->n_planes; i++) { + if (req->drmdev->planes[i].plane->plane_id == plane_id) { + plane = req->drmdev->planes + i; + break; + } + } + + if (plane == NULL) { + drmdev_unlock(req->drmdev); + return EINVAL; + } + + for (int i = 0; i < plane->props->count_props; i++) { + drmModePropertyRes *prop; + + prop = plane->props_info[i]; + + if (strcmp(prop->name, name) == 0) { + ok = drmModeAtomicAddProperty( + req->atomic_req, + plane_id, + prop->prop_id, + value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not add plane property to atomic request. drmModeAtomicAddProperty"); + drmdev_unlock(req->drmdev); + return ok; + } + + drmdev_unlock(req->drmdev); + return 0; + } + } + + drmdev_unlock(req->drmdev); + return EINVAL; +} + +int drmdev_atomic_req_put_modeset_props( + struct drmdev_atomic_req *req, + uint32_t *flags +) { + int ok; + + drmModeAtomicReq *new_req = drmModeAtomicAlloc(); + if (new_req == NULL) { + return ENOMEM; + } + + ok = drmdev_atomic_req_put_connector_property(req, "CRTC_ID", req->drmdev->selected_crtc->crtc->crtc_id); + if (ok != 0) { + drmModeAtomicFree(new_req); + return ok; + } + + ok = drmdev_atomic_req_put_crtc_property(req, "MODE_ID", req->drmdev->selected_mode_blob_id); + if (ok != 0) { + drmModeAtomicFree(new_req); + return ok; + } + + ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); + if (ok != 0) { + drmModeAtomicFree(new_req); + return ok; + } + + ok = drmModeAtomicMerge(req->atomic_req, new_req); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not apply modesetting properties to atomic request. drmModeAtomicMerge"); + drmModeAtomicFree(new_req); + return errno; + } + + drmModeAtomicFree(new_req); + + if (flags != NULL) { + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + + return 0; +} + +int drmdev_atomic_req_commit( + struct drmdev_atomic_req *req, + uint32_t flags, + void *userdata +) { + int ok; + + drmdev_lock(req->drmdev); + + ok = drmModeAtomicCommit(req->drmdev->fd, req->atomic_req, flags, userdata); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not commit atomic request. drmModeAtomicCommit"); + drmdev_unlock(req->drmdev); + return ok; + } + + drmdev_unlock(req->drmdev); + return 0; +} \ No newline at end of file diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c index cbf8614a..b9fd7006 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/video_player.c @@ -167,6 +167,7 @@ static int get_player_from_map_arg( /// Should be presented. static int on_present( int64_t view_id, + struct drmdev_atomic_req *req, const FlutterPlatformViewMutation **mutations, size_t num_mutations, int offset_x, @@ -349,6 +350,11 @@ static void *omxplayer_mgr_entry(void *userdata) { current_zpos = -1; pid_t me = fork(); if (me == 0) { + char orientation_str[16] = {0}; + snprintf(orientation_str, sizeof orientation_str, "%d", task.orientation); + + printf("orientation_str: %s\n", orientation_str); + // I'm the child! prctl(PR_SET_PDEATHSIG, SIGKILL); int _ok = execvp( @@ -361,6 +367,7 @@ static void *omxplayer_mgr_entry(void *userdata) { "--loop", "--layer", "-1", "--win", "0,0,1,1", + "--orientation", orientation_str, "--dbus_name", dbus_name, mgr->player->video_uri, NULL @@ -1259,7 +1266,8 @@ static int on_create( ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { .type = kCreate, - .responsehandle = responsehandle + .responsehandle = responsehandle, + .orientation = rotation }); if (ok != 0) { From 50cb76111fb329a41e62c9ae54b626c16f72e29d Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 18 Jun 2020 03:37:59 +0200 Subject: [PATCH 06/14] stability & bug fixes - move memdup to collection - add platform view mount/unmount/update_view callbacks - delete threading.h, uploaded it by mistake - make plugin registry use collection (concurrent pointer set) - fixes in modesetting - when creating backing stores, use crtc size/width - make video_player use mount/unmount/update_view callbacks - pause omxplayer after first frame more accurately - let omxplayer_video_player initialization fail if omxplayer is not installed - no longer throw an error when seeking on a live-stream - don't compile with UBSan anymore - set compiler optimization level to 2 --- Makefile | 6 +- include/collection.h | 11 ++ include/compositor.h | 35 +++++ include/flutter-pi.h | 13 -- include/pluginregistry.h | 13 +- include/plugins/video_player.h | 2 +- include/threading.h | 233 --------------------------------- src/compositor.c | 187 ++++++++++++++++++++------ src/flutter-pi.c | 9 +- src/modesetting.c | 23 ++-- src/platformchannel.c | 11 +- src/pluginregistry.c | 213 ++++++++++++++++++------------ src/plugins/services.c | 30 +++-- src/plugins/video_player.c | 226 +++++++++++++++++++++----------- 14 files changed, 534 insertions(+), 478 deletions(-) delete mode 100644 include/threading.h diff --git a/Makefile b/Makefile index c13c390f..16ba9aa3 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,12 @@ REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libs -DBUILD_SPIDEV_PLUGIN \ -DBUILD_TEST_PLUGIN \ -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ - -ggdb -fsanitize=undefined \ + -ggdb \ + -O2 \ $(CFLAGS) -REAL_LDFLAGS = $(shell pkg-config --libs gbm libdrm glesv2 egl) \ +REAL_LDFLAGS = \ + $(shell pkg-config --libs gbm libdrm glesv2 egl) \ -lrt \ -lflutter_engine \ -lpthread \ diff --git a/include/collection.h b/include/collection.h index 34b54822..5a9f73b2 100644 --- a/include/collection.h +++ b/include/collection.h @@ -451,4 +451,15 @@ static inline void *__cpset_next_pointer( #define for_each_pointer_in_cpset(set, pointer) for ((pointer) = __cpset_next_pointer(set, NULL); (pointer) != NULL; (pointer) = __cpset_next_pointer(set, (pointer))) +static inline void *memdup(const void *restrict src, const size_t n) { + void *__restrict__ dest; + + if ((src == NULL) || (n == 0)) return NULL; + + dest = malloc(n); + if (dest == NULL) return NULL; + + return memcpy(dest, src, n); +} + #endif \ No newline at end of file diff --git a/include/compositor.h b/include/compositor.h index 0f0a777d..d1e02df0 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -9,6 +9,38 @@ #include #include +typedef int (*platform_view_mount_cb)( + int64_t view_id, + struct drmdev_atomic_req *req, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +); + +typedef int (*platform_view_unmount_cb)( + int64_t view_id, + struct drmdev_atomic_req *req, + void *userdata +); + +typedef int (*platform_view_update_view_cb)( + int64_t view_id, + struct drmdev_atomic_req *req, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +); + typedef int (*platform_view_present_cb)( int64_t view_id, struct drmdev_atomic_req *req, @@ -80,6 +112,9 @@ int compositor_on_page_flip( int compositor_set_view_callbacks( int64_t view_id, + platform_view_mount_cb mount, + platform_view_unmount_cb unmount, + platform_view_update_view_cb update_view, platform_view_present_cb present, void *userdata ); diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 90a4e3b7..7a4ec9c3 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -80,17 +80,6 @@ struct flutterpi_task { uint64_t target_time; }; -static inline void *memdup(const void *restrict src, const size_t n) { - void *__restrict__ dest; - - if ((src == NULL) || (n == 0)) return NULL; - - dest = malloc(n); - if (dest == NULL) return NULL; - - return memcpy(dest, src, n); -} - struct drm_fb { struct gbm_bo *bo; uint32_t fb_id; @@ -253,8 +242,6 @@ extern struct mousepointer_mtslot mousepointer; extern FlutterEngine engine; -struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo); - void post_platform_task(struct flutterpi_task *task); int flutterpi_send_platform_message(const char *channel, diff --git a/include/pluginregistry.h b/include/pluginregistry.h index c2626d8d..97fd13f7 100644 --- a/include/pluginregistry.h +++ b/include/pluginregistry.h @@ -21,7 +21,10 @@ typedef int (*init_deinit_cb)(void); /// the platform message using the codec given to plugin_registry_set_receiver. /// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec /// passed to plugin_registry_set_receiver. -typedef int (*platch_obj_recv_callback)(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle); +typedef int (*platch_obj_recv_callback)( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle); /// details of a plugin for flutter-pi. /// All plugins are initialized (i.e. get their "init" callbacks called) @@ -47,7 +50,13 @@ int plugin_registry_on_platform_message(FlutterPlatformMessage *message); /// Sets the callback that should be called when a platform message arrives on channel "channel", /// and the codec used to automatically decode the platform message. /// Call this method with NULL as the callback parameter to remove the current listener on that channel. -int plugin_registry_set_receiver(char *channel, enum platch_codec codec, platch_obj_recv_callback callback); +int plugin_registry_set_receiver( + const char *channel, + enum platch_codec codec, + platch_obj_recv_callback callback +); + +int plugin_registry_remove_receiver(const char *channel); int plugin_registry_deinit(); diff --git a/include/plugins/video_player.h b/include/plugins/video_player.h index a21152de..bbb99320 100644 --- a/include/plugins/video_player.h +++ b/include/plugins/video_player.h @@ -25,7 +25,7 @@ #define DBUS_OMXPLAYER_OBJECT "/org/mpris/MediaPlayer2" #define DBUS_OMXPLAYER_PLAYER_FACE "org.mpris.MediaPlayer2.Player" -#define DBUS_OMXPLAYER_ROOT_FACE "org.mpris.MediaPlayer2.Root" +#define DBUS_OMXPLAYER_ROOT_FACE "org.mpris.MediaPlayer2" #define DBUS_PROPERTY_FACE "org.freedesktop.DBus.Properties" #define DBUS_PROPERTY_GET "Get" #define DBUS_PROPRETY_SET "Set" diff --git a/include/threading.h b/include/threading.h deleted file mode 100644 index 461ba43b..00000000 --- a/include/threading.h +++ /dev/null @@ -1,233 +0,0 @@ -#ifndef _THREADING_H -#define _THREADING_H - -#include -#include -#include - -#include - -#define CQUEUE_DEFAULT_MAX_QUEUE_SIZE 64 - -struct concurrent_queue { - pthread_mutex_t mutex; - pthread_cond_t is_dequeueable; - pthread_cond_t is_enqueueable; - size_t start_index; - size_t length; - size_t size; - void *elements; - - size_t max_queue_size; - size_t element_size; -}; - -#define CQUEUE_INITIALIZER(element_type, _max_queue_size) \ - ((struct concurrent_queue) { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .is_dequeueable = PTHREAD_COND_INITIALIZER, \ - .is_enqueueable = PTHREAD_COND_INITIALIZER, \ - .start_index = 0, \ - .length = 0, \ - .size = 0, \ - .elements = NULL, \ - .max_queue_size = _max_queue_size, \ - .element_size = sizeof(element_type) \ - }) - -static inline int cqueue_init(struct concurrent_queue *queue, size_t element_size, size_t max_queue_size) { - memset(queue, 0, sizeof(*queue)); - - pthread_mutex_init(&queue->mutex, NULL); - pthread_cond_init(&queue->is_dequeueable, NULL); - pthread_cond_init(&queue->is_enqueueable, NULL); - - queue->start_index = 0; - queue->length = 0; - queue->size = 2; - queue->elements = calloc(2, element_size); - - queue->max_queue_size = max_queue_size; - queue->element_size = element_size; - - if (queue->elements == NULL) { - queue->size = 0; - return ENOMEM; - } - - return 0; -} - -static inline int cqueue_deinit(struct concurrent_queue *queue) { - pthread_mutex_destroy(&queue->mutex); - pthread_cond_destroy(&queue->is_dequeueable); - pthread_cond_destroy(&queue->is_enqueueable); - - if (queue->elements != NULL) { - free(queue->elements); - } - - queue->start_index = 0; - queue->length = 0; - queue->size = 0; - queue->elements = NULL; - - queue->max_queue_size = 0; - queue->element_size = 0; - - return 0; -} - -static inline int cqueue_lock(struct concurrent_queue * const queue) { - return pthread_mutex_lock(&queue->mutex); -} - -static inline int cqueue_unlock(struct concurrent_queue * const queue) { - return pthread_mutex_unlock(&queue->mutex); -} - -static inline int cqueue_try_enqueue( - struct concurrent_queue * const queue, - const void const *p_element -) { - cqueue_lock(queue); - - if (queue->size == queue->length) { - // expand the queue. - - size_t new_size = queue->size ? queue->size << 1 : 1; - - if (new_size > queue->max_queue_size) { - cqueue_unlock(queue); - return EAGAIN; - } - - void *new_elements = realloc(queue->elements, new_size * queue->element_size); - - if (new_elements == NULL) { - cqueue_unlock(queue); - return ENOMEM; - } - - if (queue->size) { - memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); - } - - queue->elements = new_elements; - queue->size = new_size; - } - - memcpy( - ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), - p_element, - queue->element_size - ); - - queue->length++; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -static inline int cqueue_try_dequeue( - struct concurrent_queue * const queue, - void const *element_out -) { - cqueue_lock(queue); - - if (queue->length == 0) { - cqueue_unlock(queue); - return EAGAIN; - } - - memcpy( - ((char*) queue->elements) + queue->element_size*queue->start_index, - element_out, - queue->element_size - ); - - queue->start_index = (queue->start_index + 1) & (queue->size - 1); - queue->length--; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_enqueueable); - - return 0; -} - -static inline int cqueue_enqueue( - struct concurrent_queue * const queue, - const void const *p_element -) { - cqueue_lock(queue); - - if (queue->size == queue->length) { - // expand the queue or wait for an element to be dequeued. - - size_t new_size = queue->size ? queue->size << 1 : 1; - if (new_size < queue->max_queue_size) { - void *new_elements = realloc(queue->elements, new_size * queue->element_size); - - if (new_elements == NULL) { - cqueue_unlock(queue); - return ENOMEM; - } - - if (queue->size) { - memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); - } - - queue->elements = new_elements; - queue->size = new_size; - } else { - do { - pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); - } while (queue->size == queue->length); - } - } - - memcpy( - ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), - p_element, - queue->element_size - ); - - queue->length++; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -static inline int cqueue_dequeue( - struct concurrent_queue *const queue, - void *const element_out -) { - cqueue_lock(queue); - - while (queue->length == 0) - pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); - - memcpy( - element_out, - ((char*) queue->elements) + queue->element_size*queue->start_index, - queue->element_size - ); - - queue->start_index = (queue->start_index + 1) & (queue->size - 1); - queue->length--; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_enqueueable); - - return 0; -} - -#endif \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c index 057ac260..1b059c1f 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -19,8 +19,17 @@ struct view_cb_data { int64_t view_id; + platform_view_mount_cb mount; + platform_view_unmount_cb unmount; + platform_view_update_view_cb update_view; platform_view_present_cb present; void *userdata; + + bool was_present_last_frame; + FlutterSize last_size; + FlutterPoint last_offset; + int last_num_mutations; + FlutterPlatformViewMutation last_mutations[16]; }; struct plane_reservation_data { @@ -313,7 +322,7 @@ static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { meta = (struct backing_store_metadata*) userdata; - printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); + //printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); eglGetError(); glGetError(); @@ -350,7 +359,8 @@ static int create_window_surface_backing_store( struct backing_store_metadata *meta; int ok; - printf("create_window_surface_backing_store()\n"); + // This should really be creating the GBM surface, + // but we need the GBM surface to be bound as an EGL display earlier. meta = calloc(1, sizeof *meta); if (meta == NULL) { @@ -393,8 +403,6 @@ static int create_drm_fb_backing_store( GLenum gl_error; int ok; - printf("create_drm_fb_backing_store\n"); - meta = calloc(1, sizeof *meta); if (meta == NULL) { perror("[compositor] Could not allocate backing store metadata, calloc"); @@ -425,8 +433,8 @@ static int create_drm_fb_backing_store( } ok = create_drm_rbo( - config->size.width, - config->size.height, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, inner->rbos + 0 ); if (ok != 0) { @@ -437,8 +445,8 @@ static int create_drm_fb_backing_store( } ok = create_drm_rbo( - config->size.width, - config->size.height, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, inner->rbos + 1 ); if (ok != 0) { @@ -563,7 +571,7 @@ static int collect_drm_fb_backing_store( ) { struct drm_fb_backing_store *inner = &meta->drm_fb; - printf("collect_drm_fb_backing_store(gl_fbo_id: %u)\n", inner->gl_fbo_id); + //printf("collect_drm_fb_backing_store(gl_fbo_id: %u)\n", inner->gl_fbo_id); // makes sense that the FlutterBackingStore collect callback is called before the // FlutterBackingStore OpenGL framebuffer destroy callback. Thanks flutter. (/s) @@ -678,8 +686,6 @@ static int present_drm_fb_backing_store( } */ - printf("present drm_fb, zpos: %d\n", zpos); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "FB_ID", backing_store->rbos[backing_store->current_front_rbo ^ 1].drm_fb_id); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_X", 0); @@ -718,6 +724,22 @@ static int present_platform_view( return EINVAL; } + if (cbs->was_present_last_frame == false) { + cbs->mount( + view_id, + req, + mutations, + num_mutations, + offset_x, + offset_y, + width, + height, + zpos, + cbs->userdata + ); + cbs->was_present_last_frame = true; + } + if (cbs->present != NULL) { return cbs->present( view_id, @@ -744,6 +766,7 @@ static bool present_layers_callback( struct plane_reservation_data *data; struct flutterpi_compositor *compositor; struct drmdev_atomic_req *req; + struct view_cb_data *cb_data; uint32_t req_flags; int ok; @@ -841,28 +864,13 @@ static bool present_layers_callback( FlutterEngineTraceEventDurationBegin("present"); + // flush GL eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); eglSwapBuffers(egl.display, egl.surface); - /* - /// find the index of the window surface. - /// the window surface's zpos can't change, so we need to - /// normalize all other backing stores' zpos around the - /// window surfaces zpos. - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypeBackingStore - && layers[i]->backing_store->type == kFlutterBackingStoreTypeOpenGL - && layers[i]->backing_store->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer - && ((struct backing_store_metadata *) layers[i]->backing_store->user_data)->type == kWindowSurface) { - - window_surface_index = i; - - break; - } - }*/ - drmdev_new_atomic_req(compositor->drmdev, &req); + // if we haven't yet set the display mode, set one req_flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; if (compositor->has_applied_modeset == false) { ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); @@ -871,6 +879,32 @@ static bool present_layers_callback( compositor->has_applied_modeset = true; } + // unmount non-present platform views + for_each_pointer_in_cpset(&compositor->cbs, cb_data) { + bool is_present = false; + for (int i = 0; i < layers_count; i++) { + if (layers[i]->type == kFlutterLayerContentTypePlatformView && + layers[i]->platform_view->identifier == cb_data->view_id) { + is_present = true; + break; + } + } + + if (!is_present && cb_data->was_present_last_frame) { + if (cb_data->unmount != NULL) { + ok = cb_data->unmount( + cb_data->view_id, + req, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not unmount platform view. %s\n", strerror(ok)); + } + } + } + } + + // present all layers, invoke the mount/update_view/present callbacks of platform views for (int i = 0; i < layers_count; i++) { const FlutterLayer *layer = layers[i]; @@ -899,18 +933,86 @@ static bool present_layers_callback( 1 ); } + } else if (layer->type == kFlutterLayerContentTypePlatformView) { - ok = present_platform_view( - layer->platform_view->identifier, - req, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - (int) round(layer->offset.x), - (int) round(layer->offset.y), - (int) round(layer->size.width), - (int) round(layer->size.height), - 0 - ); + cb_data = get_cbs_for_view_id(layer->platform_view->identifier); + + if (cb_data != NULL) { + if (cb_data->was_present_last_frame == false) { + if (cb_data->mount != NULL) { + ok = cb_data->mount( + layer->platform_view->identifier, + req, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + 0, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not mount platform view. %s\n", strerror(ok)); + } + } + } else { + bool did_update_view = false; + + did_update_view = did_update_view || memcmp(&cb_data->last_size, &layer->size, sizeof(FlutterSize)); + did_update_view = did_update_view || memcmp(&cb_data->last_offset, &layer->offset, sizeof(FlutterPoint)); + did_update_view = did_update_view || (cb_data->last_num_mutations != layer->platform_view->mutations_count); + for (int i = 0; (i < layer->platform_view->mutations_count) && !did_update_view; i++) { + did_update_view = did_update_view || memcmp(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + } + + if (did_update_view) { + if (cb_data->update_view != NULL) { + ok = cb_data->update_view( + cb_data->view_id, + req, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + 0, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not update platform views' view. %s\n", strerror(ok)); + } + } + } + } + + if (cb_data->present) { + ok = cb_data->present( + layer->platform_view->identifier, + req, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + 0, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not present platform view. %s\n", strerror(ok)); + } + } + + cb_data->was_present_last_frame = true; + cb_data->last_size = layer->size; + cb_data->last_offset = layer->offset; + cb_data->last_num_mutations = layer->platform_view->mutations_count; + for (int i = 0; i < layer->platform_view->mutations_count; i++) { + memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + } + } } else { fprintf(stderr, "[compositor] Unsupported flutter layer type: %d\n", layer->type); } @@ -918,6 +1020,7 @@ static bool present_layers_callback( eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + // all unused planes will be set inactive for_each_pointer_in_cpset(&compositor->planes, data) { if (data->is_reserved == false) { drmdev_atomic_req_put_plane_property(req, data->plane->plane->plane_id, "FB_ID", 0); @@ -936,6 +1039,9 @@ static bool present_layers_callback( /// PLATFORM VIEW CALLBACKS int compositor_set_view_callbacks( int64_t view_id, + platform_view_mount_cb mount, + platform_view_unmount_cb unmount, + platform_view_update_view_cb update_view, platform_view_present_cb present, void *userdata ) { @@ -956,6 +1062,9 @@ int compositor_set_view_callbacks( } entry->view_id = view_id; + entry->mount = mount; + entry->unmount = unmount; + entry->update_view = update_view; entry->present = present; entry->userdata = userdata; diff --git a/src/flutter-pi.c b/src/flutter-pi.c index b60c9096..5bbb30d9 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -353,8 +353,11 @@ static void on_platform_message( void* userdata ) { int ok; - if ((ok = plugin_registry_on_platform_message((FlutterPlatformMessage *)message)) != 0) - fprintf(stderr, "plugin_registry_on_platform_message failed: %s\n", strerror(ok)); + + ok = plugin_registry_on_platform_message((FlutterPlatformMessage *) message); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Error handling platform message. plugin_registry_on_platform_message: %s\n", strerror(ok)); + } } static void vsync_callback( @@ -610,7 +613,7 @@ int flutterpi_send_platform_message( return 0; } -int flutterpi_respond_to_platform_message( +int flutterpi_respond_to_platform_message( FlutterPlatformMessageResponseHandle *handle, const uint8_t *restrict message, size_t message_size diff --git a/src/modesetting.c b/src/modesetting.c index 29119d79..91edf0ea 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -587,7 +587,7 @@ int drmdev_atomic_req_put_connector_property( uint64_t value ) { int ok; - + drmdev_lock(req->drmdev); for (int i = 0; i < req->drmdev->selected_connector->props->count_props; i++) { @@ -704,40 +704,41 @@ int drmdev_atomic_req_put_modeset_props( struct drmdev_atomic_req *req, uint32_t *flags ) { + struct drmdev_atomic_req *augment; int ok; - drmModeAtomicReq *new_req = drmModeAtomicAlloc(); - if (new_req == NULL) { - return ENOMEM; + ok = drmdev_new_atomic_req(req->drmdev, &augment); + if (ok != 0) { + return ok; } ok = drmdev_atomic_req_put_connector_property(req, "CRTC_ID", req->drmdev->selected_crtc->crtc->crtc_id); if (ok != 0) { - drmModeAtomicFree(new_req); + drmdev_destroy_atomic_req(augment); return ok; } ok = drmdev_atomic_req_put_crtc_property(req, "MODE_ID", req->drmdev->selected_mode_blob_id); if (ok != 0) { - drmModeAtomicFree(new_req); + drmdev_destroy_atomic_req(augment); return ok; } ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); if (ok != 0) { - drmModeAtomicFree(new_req); + drmdev_destroy_atomic_req(augment); return ok; } - ok = drmModeAtomicMerge(req->atomic_req, new_req); + ok = drmModeAtomicMerge(req->atomic_req, augment->atomic_req); if (ok < 0) { ok = errno; perror("[modesetting] Could not apply modesetting properties to atomic request. drmModeAtomicMerge"); - drmModeAtomicFree(new_req); - return errno; + drmdev_destroy_atomic_req(augment); + return ok; } - drmModeAtomicFree(new_req); + drmdev_destroy_atomic_req(augment); if (flags != NULL) { *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; diff --git a/src/platformchannel.c b/src/platformchannel.c index 384f038a..53eae5a8 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1081,9 +1081,14 @@ int platch_respond(FlutterPlatformMessageResponseHandle *handle, struct platch_o if (ok != 0) return ok; result = FlutterEngineSendPlatformMessageResponse(engine, (const FlutterPlatformMessageResponseHandle*) handle, (const uint8_t*) buffer, size); - - if (buffer != NULL) free(buffer); - + if (result != kSuccess) { + fprintf(stderr, "[platformchannel] Could not send platform message response. FlutterEngineSendPlatformMessageResponse: %d\n", result); + } + + if (buffer != NULL) { + free(buffer); + } + return (result == kSuccess) ? 0 : EINVAL; } diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 2d4f23ce..8e4c08bd 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -1,12 +1,18 @@ +#ifdef __STDC_ALLOC_LIB__ +#define __STDC_WANT_LIB_EXT2__ 1 +#else +#define _POSIX_C_SOURCE 200809L +#endif + #include #include #include #include #include +#include #include - #include #ifdef BUILD_TEXT_INPUT_PLUGIN @@ -29,19 +35,18 @@ #endif -struct platch_obj_recv_data { +struct platch_obj_cb_data { char *channel; enum platch_codec codec; platch_obj_recv_callback callback; + void *userdata; }; struct { + size_t n_plugins; struct flutterpi_plugin *plugins; - size_t plugin_count; // platch_obj callbacks - struct platch_obj_recv_data *platch_obj_cbs; - size_t platch_obj_cbs_size; - + struct concurrent_pointer_set platch_obj_cbs; } pluginregistry; /// array of plugins that are statically included in flutter-pi. @@ -74,123 +79,163 @@ struct flutterpi_plugin hardcoded_plugins[] = { #endif }; +static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel_locked(const char *channel) { + struct platch_obj_cb_data *data; -int plugin_registry_init() { - int ok; + for_each_pointer_in_cpset(&pluginregistry.platch_obj_cbs, data) { + if (strcmp(data->channel, channel) == 0) { + return data; + } + } - memset(&pluginregistry, 0, sizeof(pluginregistry)); + return NULL; +} - pluginregistry.platch_obj_cbs_size = 20; - pluginregistry.platch_obj_cbs = calloc(pluginregistry.platch_obj_cbs_size, sizeof(struct platch_obj_recv_data)); +static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel(const char *channel) { + struct platch_obj_cb_data *data; - if (!pluginregistry.platch_obj_cbs) { - fprintf(stderr, "[plugin-registry] Could not allocate memory for platform channel message callbacks.\n"); - return ENOMEM; - } - - pluginregistry.plugins = hardcoded_plugins; - pluginregistry.plugin_count = sizeof(hardcoded_plugins) / sizeof(struct flutterpi_plugin); + cpset_lock(&pluginregistry.platch_obj_cbs); + data = plugin_registry_get_cb_data_by_channel_locked(channel); + cpset_unlock(&pluginregistry.platch_obj_cbs); - // insert code for dynamically loading plugins here + return data; +} + +int plugin_registry_init() { + int ok; - // call all the init methods for all plugins - for (int i = 0; i < pluginregistry.plugin_count; i++) { - if (pluginregistry.plugins[i].init) { + pluginregistry.n_plugins = sizeof(hardcoded_plugins) / sizeof(*hardcoded_plugins); + pluginregistry.plugins = hardcoded_plugins; + pluginregistry.platch_obj_cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE); + + for (int i = 0; i < pluginregistry.n_plugins; i++) { + if (pluginregistry.plugins[i].init != NULL) { ok = pluginregistry.plugins[i].init(); - if (ok != 0) return ok; + if (ok != 0) { + fprintf(stderr, "[plugin registry] Could not initialize plugin %s. init: %s\n", pluginregistry.plugins[i].name, strerror(ok)); + return ok; + } } } return 0; } + int plugin_registry_on_platform_message(FlutterPlatformMessage *message) { + struct platch_obj_cb_data *data, data_copy; struct platch_obj object; int ok; - for (int i = 0; i < pluginregistry.platch_obj_cbs_size; i++) { - if ((pluginregistry.platch_obj_cbs[i].callback) && (strcmp(pluginregistry.platch_obj_cbs[i].channel, message->channel) == 0)) { - ok = platch_decode((uint8_t*) message->message, message->message_size, pluginregistry.platch_obj_cbs[i].codec, &object); - if (ok != 0) return ok; + cpset_lock(&pluginregistry.platch_obj_cbs); - pluginregistry.platch_obj_cbs[i].callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); + data = plugin_registry_get_cb_data_by_channel_locked(message->channel); + if (data == NULL || data->callback == NULL) { + cpset_unlock(&pluginregistry.platch_obj_cbs); + return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle*) message->response_handle); + } - platch_free_obj(&object); - return 0; - } + data_copy = *data; + cpset_unlock(&pluginregistry.platch_obj_cbs); + + ok = platch_decode((uint8_t*) message->message, message->message_size, data_copy.codec, &object); + if (ok != 0) { + return ok; + } + + ok = data_copy.callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); //, data->userdata); + if (ok != 0) { + platch_free_obj(&object); + return ok; } - // we didn't find a callback for the specified channel. - // just respond with a null buffer to tell the VM-side - // that the feature is not implemented. + platch_free_obj(&object); - return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); + return 0; } -int plugin_registry_set_receiver(char *channel, enum platch_codec codec, platch_obj_recv_callback callback) { - /// the index in 'callback' of the platch_obj_recv_data that will be added / updated. - int index = -1; - - /// find the index with channel name 'channel', or else, the first unoccupied index. - for (int i = 0; i < pluginregistry.platch_obj_cbs_size; i++) { - if (pluginregistry.platch_obj_cbs[i].channel == NULL) { - if (index == -1) { - index = i; - } - } else if (strcmp(channel, pluginregistry.platch_obj_cbs[i].channel) == 0) { - index = i; - break; - } - } + +int plugin_registry_set_receiver( + const char *channel, + enum platch_codec codec, + platch_obj_recv_callback callback + //void *userdata +) { + struct platch_obj_cb_data *data; + char *channel_dup; + + cpset_lock(&pluginregistry.platch_obj_cbs); - /// no matching or unoccupied index found. - if (index == -1) { - if (!callback) return 0; - - /// expand array - size_t currentsize = pluginregistry.platch_obj_cbs_size * sizeof(struct platch_obj_recv_data); - - pluginregistry.platch_obj_cbs = realloc(pluginregistry.platch_obj_cbs, 2 * currentsize); - memset(&pluginregistry.platch_obj_cbs[pluginregistry.platch_obj_cbs_size], currentsize, 0); - - index = pluginregistry.platch_obj_cbs_size; - pluginregistry.platch_obj_cbs_size = 2*pluginregistry.platch_obj_cbs_size; + channel_dup = strdup(channel); + if (channel_dup == NULL) { + cpset_unlock(&pluginregistry.platch_obj_cbs); + return ENOMEM; } - if (callback) { - char *channelCopy = malloc(strlen(channel) +1); - if (!channelCopy) return ENOMEM; - strcpy(channelCopy, channel); - - pluginregistry.platch_obj_cbs[index].channel = channelCopy; - pluginregistry.platch_obj_cbs[index].codec = codec; - pluginregistry.platch_obj_cbs[index].callback = callback; - } else if (pluginregistry.platch_obj_cbs[index].callback) { - free(pluginregistry.platch_obj_cbs[index].channel); - pluginregistry.platch_obj_cbs[index].channel = NULL; - pluginregistry.platch_obj_cbs[index].callback = NULL; + data = plugin_registry_get_cb_data_by_channel_locked(channel); + if (data == NULL) { + data = calloc(1, sizeof *data); + if (data == NULL) { + free(channel_dup); + cpset_unlock(&pluginregistry.platch_obj_cbs); + return ENOMEM; + } + + cpset_put_locked(&pluginregistry.platch_obj_cbs, data); } + data->channel = channel_dup; + data->codec = codec; + data->callback = callback; + //data->userdata = userdata; + + cpset_unlock(&pluginregistry.platch_obj_cbs); + + return 0; +} + +int plugin_registry_remove_receiver(const char *channel) { + struct platch_obj_cb_data *data; + + cpset_lock(&pluginregistry.platch_obj_cbs); + + data = plugin_registry_get_cb_data_by_channel_locked(channel); + if (data == NULL) { + cpset_unlock(&pluginregistry.platch_obj_cbs); + return EINVAL; + } + + cpset_remove_locked(&pluginregistry.platch_obj_cbs, data); + + free(data->channel); + free(data); + + cpset_unlock(&pluginregistry.platch_obj_cbs); + return 0; - } + int plugin_registry_deinit() { - int i, ok; + struct platch_obj_cb_data *data; + int ok; /// call each plugins 'deinit' - for (i = 0; i < pluginregistry.plugin_count; i++) { + for (int i = 0; i < pluginregistry.n_plugins; i++) { if (pluginregistry.plugins[i].deinit) { ok = pluginregistry.plugins[i].deinit(); - if (ok != 0) return ok; + if (ok != 0) { + fprintf(stderr, "[plugin registry] Could not deinitialize plugin %s. deinit: %s\n", pluginregistry.plugins[i].name, strerror(ok)); + } } } - /// free all the channel names from the callback list. - for (int i=0; i < pluginregistry.platch_obj_cbs_size; i++) { - if (pluginregistry.platch_obj_cbs[i].channel) - free(pluginregistry.platch_obj_cbs[i].channel); + for_each_pointer_in_cpset(&pluginregistry.platch_obj_cbs, data) { + cpset_remove_locked(&pluginregistry.platch_obj_cbs, data); + if (data != NULL) { + free(data->channel); + free(data); + } } - /// free the rest - free(pluginregistry.platch_obj_cbs); + cpset_deinit(&pluginregistry.platch_obj_cbs); return 0; } diff --git a/src/plugins/services.c b/src/plugins/services.c index e2d16ca1..28a524b7 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -12,18 +12,18 @@ struct { } services = {0}; -int services_on_receive_navigation(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_navigation(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { return platch_respond_not_implemented(responsehandle); } -int services_on_receive_isolate(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_isolate(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { memset(&(services.isolate_id), sizeof(services.isolate_id), 0); memcpy(services.isolate_id, object->binarydata, object->binarydata_size); return platch_respond_not_implemented(responsehandle); } -int services_on_receive_platform(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_platform(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct json_value *value; struct json_value *arg = &(object->json_arg); int ok; @@ -63,6 +63,8 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt * portraitUp, landscapeLeft, portraitDown, landscapeRight * } */ + + printf("setPreferredOrientations\n"); value = &object->json_arg; @@ -111,9 +113,15 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt .orientation = i, .target_time = 0 }); - return 0; + + return platch_respond_success_json(responsehandle, NULL); } } + + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg` to contain at least one element." + ); } else if (strcmp(object->method, "SystemChrome.setApplicationSwitcherDescription") == 0) { /* * SystemChrome.setApplicationSwitcherDescription(Map description) @@ -168,11 +176,11 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt return platch_respond_not_implemented(responsehandle); } -int services_on_receive_accessibility(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_accessibility(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { return platch_respond_not_implemented(responsehandle); } -int services_on_receive_platform_views(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_platform_views(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { struct json_value *value; struct json_value *arg = &(object->json_arg); int ok; @@ -192,31 +200,31 @@ int services_init(void) { printf("[services] Initializing...\n"); - ok = plugin_registry_set_receiver("flutter/navigation", kJSONMethodCall, services_on_receive_navigation); + ok = plugin_registry_set_receiver("flutter/navigation", kJSONMethodCall, on_receive_navigation); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/navigation\" ChannelObject receiver: %s\n", strerror(ok)); return ok; } - ok = plugin_registry_set_receiver("flutter/isolate", kBinaryCodec, services_on_receive_isolate); + ok = plugin_registry_set_receiver("flutter/isolate", kBinaryCodec, on_receive_isolate); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/isolate\" ChannelObject receiver: %s\n", strerror(ok)); return ok; } - ok = plugin_registry_set_receiver("flutter/platform", kJSONMethodCall, services_on_receive_platform); + ok = plugin_registry_set_receiver("flutter/platform", kJSONMethodCall, on_receive_platform); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/platform\" ChannelObject receiver: %s\n", strerror(ok)); return ok; } - ok = plugin_registry_set_receiver("flutter/accessibility", kBinaryCodec, services_on_receive_accessibility); + ok = plugin_registry_set_receiver("flutter/accessibility", kBinaryCodec, on_receive_accessibility); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/accessibility\" ChannelObject receiver: %s\n", strerror(ok)); return ok; } - ok = plugin_registry_set_receiver("flutter/platform_views", kStandardMethodCall, services_on_receive_platform_views); + ok = plugin_registry_set_receiver("flutter/platform_views", kStandardMethodCall, on_receive_platform_views); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/platform_views\" ChannelObject receiver: %s\n", strerror(ok)); return ok; diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c index b9fd7006..ff7b88f2 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/video_player.c @@ -55,12 +55,12 @@ static struct { struct libsystemd libsystemd = {0}; /// Add a player instance to the player collection. -static int add_player(struct omxplayer_video_player *player) { +int add_player(struct omxplayer_video_player *player) { return cpset_put(&omxpvidpp.players, player); } /// Get a player instance by its id. -static struct omxplayer_video_player *get_player_by_id(int64_t player_id) { +struct omxplayer_video_player *get_player_by_id(int64_t player_id) { struct omxplayer_video_player *player; cpset_lock(&omxpvidpp.players); @@ -76,7 +76,7 @@ static struct omxplayer_video_player *get_player_by_id(int64_t player_id) { } /// Get a player instance by its event channel name. -static struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { +struct omxplayer_video_player *get_player_by_evch(const char *const event_channel_name) { struct omxplayer_video_player *player; cpset_lock(&omxpvidpp.players); @@ -163,9 +163,60 @@ static int get_player_from_map_arg( return 0; } -/// Called on the flutter rasterizer thread when a players platform view -/// Should be presented. -static int on_present( +/// Called on the flutter rasterizer thread when a players platform view is presented +/// for the first time after it was unmounted or initialized. +static int on_mount( + int64_t view_id, + struct drmdev_atomic_req *req, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +) { + struct omxplayer_video_player *player = userdata; + + return cqueue_enqueue( + &player->mgr->task_queue, + &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .offset_x = offset_x, + .offset_y = offset_y, + .width = width, + .height = height, + .zpos = zpos + } + ); +} + +/// Called on the flutter rasterizer thread when a players platform view is not present +/// in the currently being drawn frame after it was present in the previous frame. +static int on_unmount( + int64_t view_id, + struct drmdev_atomic_req *req, + void *userdata +) { + struct omxplayer_video_player *player = userdata; + + return cqueue_enqueue( + &player->mgr->task_queue, + &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .offset_x = 0, + .offset_y = 0, + .width = 1, + .height = 1, + .zpos = -128 + } + ); +} + +/// Called on the flutter rasterizer thread when the presentation details (offset, mutations, dimensions, zpos) +/// changed from the previous frame. +static int on_update_view( int64_t view_id, struct drmdev_atomic_req *req, const FlutterPlatformViewMutation **mutations, @@ -238,7 +289,7 @@ static int get_dbus_property( /// Callback to be called when the omxplayer manager receives /// a DBus message. (Currently only used for listening to NameOwnerChanged messages, /// to find out when omxplayer registers to the dbus.) -static int omxplayer_mgr_on_dbus_message( +static int mgr_on_dbus_message( sd_bus_message *m, void *userdata, sd_bus_error *ret_error @@ -265,13 +316,13 @@ static int omxplayer_mgr_on_dbus_message( } } - libsystemd.sd_bus_message_unref(m); - return 0; } /// The entry function of the manager thread. -static void *omxplayer_mgr_entry(void *userdata) { +/// Manager thread has the ownership over the player / manager / task queue objects +/// and must free them when it quits. +static void *mgr_entry(void *userdata) { struct omxplayer_mgr_task task; struct concurrent_queue *q; struct omxplayer_mgr *mgr; @@ -292,7 +343,7 @@ static void *omxplayer_mgr_entry(void *userdata) { mgr = userdata; q = &mgr->task_queue; - + // dequeue the first task of the queue (creation task) ok = cqueue_dequeue(q, &task); if (ok != 0) { @@ -342,10 +393,10 @@ static void *omxplayer_mgr_entry(void *userdata) { "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", - omxplayer_mgr_on_dbus_message, + mgr_on_dbus_message, &task ); - + // spawn the omxplayer process current_zpos = -1; pid_t me = fork(); @@ -353,8 +404,6 @@ static void *omxplayer_mgr_entry(void *userdata) { char orientation_str[16] = {0}; snprintf(orientation_str, sizeof orientation_str, "%d", task.orientation); - printf("orientation_str: %s\n", orientation_str); - // I'm the child! prctl(PR_SET_PDEATHSIG, SIGKILL); int _ok = execvp( @@ -365,7 +414,7 @@ static void *omxplayer_mgr_entry(void *userdata) { "--no-osd", "--no-keys", "--loop", - "--layer", "-1", + "--layer", "-128", "--win", "0,0,1,1", "--orientation", orientation_str, "--dbus_name", dbus_name, @@ -375,9 +424,9 @@ static void *omxplayer_mgr_entry(void *userdata) { ); if (_ok != 0) { - _exit(_ok); + exit(_ok); } - _exit(0); + exit(0); } else if (me > 0) { // I'm the parent! omxplayer_pid = me; @@ -392,7 +441,6 @@ static void *omxplayer_mgr_entry(void *userdata) { return (void*) EXIT_FAILURE; } - // wait for omxplayer to register to the dbus while (!task.omxplayer_online) { ok = libsystemd.sd_bus_wait(bus, 1000*1000*5); if (ok < 0) { @@ -408,11 +456,26 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_slot_unref(slot); slot = NULL; + duration_us = 0; + ok = get_dbus_property( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Duration", + &err, + 'x', + &duration_us + ); + if (ok != 0) { + return (void*) EXIT_FAILURE; + } + // wait for the first frame to appear { struct timespec delta = { .tv_sec = 0, - .tv_nsec = 350*1000*1000 + .tv_nsec = 300*1000*1000 }; while (nanosleep(&delta, &delta)); } @@ -423,7 +486,7 @@ static void *omxplayer_mgr_entry(void *userdata) { dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, - "Pause", + "Play", &err, &msg, "" @@ -499,7 +562,7 @@ static void *omxplayer_mgr_entry(void *userdata) { printf("[omxplayer_video_player plugin] Omxplayer manager got a creation task, even though the player is already running.\n"); } else if (task.type == kDispose) { if (mgr->player->has_view) { - printf("[omxplayer_video_player plugin] flutter attempted to dispose the video player before its view was disposed.\n"); + fprintf(stderr, "[omxplayer_video_player plugin] flutter attempted to dispose the video player before its view was disposed.\n"); compositor_remove_view_callbacks(mgr->player->view_id); @@ -532,10 +595,14 @@ static void *omxplayer_mgr_entry(void *userdata) { // close the bus libsystemd.sd_bus_unref(bus); + plugin_registry_remove_receiver(mgr->player->event_channel_name); + // remove the player from the set of players remove_player(mgr->player); platch_respond_success_std(task.responsehandle, NULL); + + break; } else if (task.type == kListen) { platch_respond_success_std(task.responsehandle, NULL); @@ -639,8 +706,6 @@ static void *omxplayer_mgr_entry(void *userdata) { libsystemd.sd_bus_message_unref(msg); if (current_zpos != task.zpos) { - printf("setting layer to %d\n", task.zpos); - ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -684,38 +749,49 @@ static void *omxplayer_mgr_entry(void *userdata) { platch_respond_success_std(task.responsehandle, &STDINT64(position)); } else if (task.type == kSetPosition) { if (is_stream) { - // Don't allow flutter to seek on a stream. - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream).\n"); - platch_respond_error_std( - task.responsehandle, - "state-error", - "Attempted to seek on non-seekable video (a stream)", - NULL + if (task.position == -1) { + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek to end on non-seekable video (a stream) to %lldms\n", task.position); + + // TODO: implement seek-to-end + + platch_respond_success_std( + task.responsehandle, + NULL + ); + } else { + // Don't allow flutter to seek to anything other than the end on a stream. + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream) to %lldms\n", task.position); + + platch_respond_error_std( + task.responsehandle, + "state-error", + "Attempted to seek on non-seekable video (a stream)", + NULL + ); + } + } else { + ok = libsystemd.sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetPosition", + &err, + &msg, + "ox", + "/path/not/used", + (int64_t) (task.position * 1000) ); - continue; - } - - ok = libsystemd.sd_bus_call_method( - bus, - dbus_name, - DBUS_OMXPLAYER_OBJECT, - DBUS_OMXPLAYER_PLAYER_FACE, - "SetPosition", - &err, - &msg, - "ox", - "/path/not/used", - (int64_t) (task.position * 1000) - ); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, ok); - continue; - } + if (ok != 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s\n", err.name, err.message); + platch_respond_native_error_std(task.responsehandle, -ok); + continue; + } - libsystemd.sd_bus_message_unref(msg); + libsystemd.sd_bus_message_unref(msg); - platch_respond_success_std(task.responsehandle, NULL); + platch_respond_success_std(task.responsehandle, NULL); + } } else if (task.type == kSetLooping) { pause_on_end = false; platch_respond_success_std(task.responsehandle, NULL); @@ -735,7 +811,7 @@ static void *omxplayer_mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer volume: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, ok); + platch_respond_native_error_std(task.responsehandle, -ok); continue; } @@ -754,6 +830,12 @@ static int ensure_binding_initialized(void) { if (omxpvidpp.initialized) return 0; + ok = access("/usr/bin/omxplayer.bin", X_OK); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] omxplayer doesn't seem to be installed. Please install using 'sudo apt install omxplayer'. access: %s\n", strerror(errno)); + return errno; + } + libsystemd.handle = dlopen("libsystemd.so", RTLD_NOW | RTLD_LOCAL); LOAD_LIBSYSTEMD_PROC(sd_bus_default); @@ -1105,7 +1187,6 @@ static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { ); } - /******************************************************* * CHANNEL HANDLERS * * handle method calls on the method and event channel * @@ -1311,14 +1392,10 @@ static int on_create( // so we can immediately pause omxplayer once it has registered // to dbus. - ok = pthread_create(&mgr->thread, NULL, omxplayer_mgr_entry, mgr); + ok = pthread_create(&mgr->thread, NULL, mgr_entry, mgr); if (ok != 0) { //remove_player(player); - plugin_registry_set_receiver( - player->event_channel_name, - kStandardMethodCall, - NULL - ); + plugin_registry_remove_receiver(player->event_channel_name); free(mgr); cqueue_deinit(&mgr->task_queue); free(player); @@ -1502,22 +1579,10 @@ static int on_create_platform_view( "Expected `arg['platformViewId']` to be an integer." ); } + + printf("on_create_platform_view(%lld)\n", view_id); if (player->has_view) { - /* don't change the view id anymore. Let the platform-side handle multiple omxplayer views for one player instance. - - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to register more than one platform view for this player instance. flutter-pi will dispose the currently registered view.\n"); - - ok = compositor_hack_change_view_id(player->view_id, view_id); - if (ok != 0) { - return platch_respond_native_error_std(responsehandle, ok); - } - - player->view_id = view_id; - - return platch_respond_success_std(responsehandle, ok); - */ - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to register more than one platform view for one player instance.\n"); return platch_respond_illegal_arg_std( @@ -1525,7 +1590,14 @@ static int on_create_platform_view( "Attempted to register more than one platform view for this player instance." ); } else { - ok = compositor_set_view_callbacks(view_id, on_present, player); + ok = compositor_set_view_callbacks( + view_id, + on_mount, + on_unmount, + on_update_view, + NULL, + player + ); if (ok != 0) { return platch_respond_native_error_std(responsehandle, ok); } @@ -1559,6 +1631,8 @@ static int on_dispose_platform_view( ); } + printf("on_dispose_platform_view(%lld)\n", view_id); + if (player->view_id != view_id) { fprintf( stderr, From de225c429b01dd078ba657df452ca62fbe1f87d6 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 20 Jun 2020 13:55:52 +0200 Subject: [PATCH 07/14] Some plugin refactoring - remove elm327 plugin - less logging --- .gitignore | 1 + CMakeLists.txt | 2 - Makefile | 14 +- include/flutter-pi.h | 3 +- include/plugins/elm327plugin.h | 141 ------- src/compositor.c | 55 ++- src/flutter-pi.c | 99 ++--- src/pluginregistry.c | 7 - src/plugins/elm327plugin.c | 648 --------------------------------- src/plugins/gpiod.c | 198 +++++----- src/plugins/raw_keyboard.c | 12 +- src/plugins/services.c | 62 +++- src/plugins/spidev.c | 3 - src/plugins/testplugin.c | 53 +-- src/plugins/text_input.c | 6 +- src/plugins/video_player.c | 206 +++++++---- 16 files changed, 415 insertions(+), 1095 deletions(-) delete mode 100644 include/plugins/elm327plugin.h delete mode 100644 src/plugins/elm327plugin.c diff --git a/.gitignore b/.gitignore index edbaec96..a83095b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ out .vscode build.sh +build_all.sh compile_commands.json .clang-format build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 77c510c6..064f7d7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,6 @@ set(FLUTTER_PI_SRC src/platformchannel.c src/pluginregistry.c src/console_keyboard.c - src/plugins/elm327plugin.c src/plugins/services.c src/plugins/testplugin.c src/plugins/text_input.c @@ -143,7 +142,6 @@ target_compile_options(flutter-pi PRIVATE ${GLESV2_CFLAGS} ${EGL_CFLAGS} ${GPIOD_CFLAGS} -ggdb -DBUILD_TEXT_INPUT_PLUGIN - -DBUILD_ELM327_PLUGIN -DBUILD_SPIDEV_PLUGIN -DBUILD_TEST_PLUGIN -DBUILD_VIDEO_PLAYER_PLUGIN diff --git a/Makefile b/Makefile index 16ba9aa3..afc9c32e 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,9 @@ -CC = cc -LD = cc REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd) \ -DBUILD_TEXT_INPUT_PLUGIN \ - -DBUILD_ELM327_PLUGIN \ -DBUILD_GPIOD_PLUGIN \ -DBUILD_SPIDEV_PLUGIN \ -DBUILD_TEST_PLUGIN \ -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ - -ggdb \ -O2 \ $(CFLAGS) @@ -27,8 +23,14 @@ SOURCES = src/flutter-pi.c \ src/texture_registry.c \ src/compositor.c \ src/modesetting.c \ - src/plugins/elm327plugin.c src/plugins/services.c src/plugins/testplugin.c src/plugins/text_input.c \ - src/plugins/raw_keyboard.c src/plugins/gpiod.c src/plugins/spidev.c src/plugins/video_player.c + src/plugins/services.c \ + src/plugins/testplugin.c \ + src/plugins/text_input.c \ + src/plugins/raw_keyboard.c \ + src/plugins/gpiod.c \ + src/plugins/spidev.c \ + src/plugins/video_player.c + OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) all: out/flutter-pi diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 7a4ec9c3..2de46847 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -52,7 +52,8 @@ typedef enum { kRegisterExternalTexture, kUnregisterExternalTexture, kMarkExternalTextureFrameAvailable, - kGeneric + kGeneric, + kExit } flutterpi_task_type; struct flutterpi_task { diff --git a/include/plugins/elm327plugin.h b/include/plugins/elm327plugin.h deleted file mode 100644 index eae0e410..00000000 --- a/include/plugins/elm327plugin.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef _ELM327_PLUGIN_H -#define _ELM327_PLUGIN_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ELM327_RESET "ATWS" -#define ELM327_VERSION "ATI" -#define ELM327_ECHO_OFF "AT E0" -#define ELM327_LINEFEEDS_OFF "AT L0" -#define ELM327_EOC "\r" - -#define ELM327_OK "OK" // command successfully executed -#define ELM327_INVALID "?" // ELM could not understand the command -#define ELM327_ACT_ALERT "ACT ALERT" // no activity for the last 19 minutes, going into low power mode soon -#define ELM327_BUFFER_FULL "BUFFER FULL" // ELM TX Buffer full -#define ELM327_BUS_BUSY "BUS BUSY" // CAN bus was to busy to request PID -#define ELM327_BUS_ERROR "BUS ERROR" // Generic bus error. -#define ELM327_CAN_ERROR "CAN ERROR" // Generic CAN error. (Incorrect CAN baudrate, etc) -#define ELM327_DATA_ERROR "DATA ERROR" // Vehicle replied with invalid data (maybe checksum error) -#define ELM327_LINE_DATA_ERROR "drm_plane_id, "zpos", &inner->current_zpos); - if (ok != 0) { - return false; - } - - // Reflect Y because GL draws to its buffers that upside-down. - ok = set_plane_property_value(inner->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - if (ok == -1) { - perror("[compositor] Could not set rotation & reflection of hardware plane. drmModeObjectSetProperty"); - return false; - } - - // We don't scan out anything yet. Just attach the FB to this plane to reserve it. - // Compositing details (offset, size, zpos) are set in the present - // procedure. - ok = drmModeSetPlane( - drm.fd, - inner->drm_plane_id, - drm.crtc_id, - inner->rbos[1 ^ inner->current_front_rbo].drm_fb_id, - 0, - 0, 0, 0, 0, - 0, 0, ((uint32_t) config->size.width) << 16, ((uint32_t) config->size.height) << 16 - ); - if (ok == -1) { - perror("[compositor] Could not attach DRM framebuffer to hardware plane. drmModeSetPlane"); - return false; - } - - get_plane_property_value(inner->drm_plane_id, "zpos", (uint64_t*) &inner->current_zpos); - */ - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; @@ -539,11 +505,14 @@ static bool create_backing_store( // backing stores, which have a FBO, that have a // color-attached RBO, that has a DRM EGLImage as the storage, // which in turn has a DRM FB associated with it. + + FlutterEngineTraceEventDurationBegin("create_drm_fb_backing_store"); ok = create_drm_fb_backing_store( config, backing_store_out, user_data ); + FlutterEngineTraceEventDurationEnd("create_drm_fb_backing_store"); if (ok != 0) { return false; @@ -865,10 +834,14 @@ static bool present_layers_callback( FlutterEngineTraceEventDurationBegin("present"); // flush GL + FlutterEngineTraceEventDurationBegin("eglSwapBuffers"); eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); eglSwapBuffers(egl.display, egl.surface); + FlutterEngineTraceEventDurationEnd("eglSwapBuffers"); + FlutterEngineTraceEventDurationBegin("drmdev_new_atomic_req"); drmdev_new_atomic_req(compositor->drmdev, &req); + FlutterEngineTraceEventDurationEnd("drmdev_new_atomic_req"); // if we haven't yet set the display mode, set one req_flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; @@ -880,6 +853,7 @@ static bool present_layers_callback( } // unmount non-present platform views + FlutterEngineTraceEventDurationBegin("unmount non-present platform views"); for_each_pointer_in_cpset(&compositor->cbs, cb_data) { bool is_present = false; for (int i = 0; i < layers_count; i++) { @@ -903,6 +877,7 @@ static bool present_layers_callback( } } } + FlutterEngineTraceEventDurationEnd("unmount non-present platform views"); // present all layers, invoke the mount/update_view/present callbacks of platform views for (int i = 0; i < layers_count; i++) { @@ -913,6 +888,7 @@ static bool present_layers_callback( struct backing_store_metadata *meta = backing_store->user_data; if (meta->type == kWindowSurface) { + FlutterEngineTraceEventDurationBegin("present_window_surface_backing_store"); ok = present_window_surface_backing_store( &meta->window_surface, req, @@ -922,7 +898,9 @@ static bool present_layers_callback( (int) layer->size.height, 0 ); + FlutterEngineTraceEventDurationEnd("present_window_surface_backing_store"); } else if (meta->type == kDrmFb) { + FlutterEngineTraceEventDurationBegin("present_drm_fb_backing_store"); ok = present_drm_fb_backing_store( &meta->drm_fb, req, @@ -932,6 +910,7 @@ static bool present_layers_callback( (int) layer->size.height, 1 ); + FlutterEngineTraceEventDurationEnd("present_drm_fb_backing_store"); } } else if (layer->type == kFlutterLayerContentTypePlatformView) { @@ -940,6 +919,7 @@ static bool present_layers_callback( if (cb_data != NULL) { if (cb_data->was_present_last_frame == false) { if (cb_data->mount != NULL) { + FlutterEngineTraceEventDurationBegin("mount platform view"); ok = cb_data->mount( layer->platform_view->identifier, req, @@ -952,6 +932,7 @@ static bool present_layers_callback( 0, cb_data->userdata ); + FlutterEngineTraceEventDurationEnd("mount platform view"); if (ok != 0) { fprintf(stderr, "[compositor] Could not mount platform view. %s\n", strerror(ok)); } @@ -968,6 +949,7 @@ static bool present_layers_callback( if (did_update_view) { if (cb_data->update_view != NULL) { + FlutterEngineTraceEventDurationBegin("update platform view"); ok = cb_data->update_view( cb_data->view_id, req, @@ -980,6 +962,7 @@ static bool present_layers_callback( 0, cb_data->userdata ); + FlutterEngineTraceEventDurationEnd("update platform view"); if (ok != 0) { fprintf(stderr, "[compositor] Could not update platform views' view. %s\n", strerror(ok)); } @@ -988,6 +971,7 @@ static bool present_layers_callback( } if (cb_data->present) { + FlutterEngineTraceEventDurationBegin("present platform view"); ok = cb_data->present( layer->platform_view->identifier, req, @@ -1000,6 +984,7 @@ static bool present_layers_callback( 0, cb_data->userdata ); + FlutterEngineTraceEventDurationEnd("present platform view"); if (ok != 0) { fprintf(stderr, "[compositor] Could not present platform view. %s\n", strerror(ok)); } @@ -1027,7 +1012,9 @@ static bool present_layers_callback( } } + FlutterEngineTraceEventDurationBegin("drmdev_atomic_req_commit"); drmdev_atomic_req_commit(req, req_flags, NULL); + FlutterEngineTraceEventDurationEnd("drmdev_atomic_req_commit"); drmdev_destroy_atomic_req(req); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 5bbb30d9..5d7b3d36 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -518,6 +518,8 @@ static bool message_loop(void) { fprintf(stderr, "Error running platform task\n"); return false; } + } else if (task->type == kExit) { + printf("[flutter-pi] flutter requested application shutdown. not yet implemented\n"); } else if (task->type == kGeneric) { task->callback(task->userdata); } @@ -706,7 +708,7 @@ static int init_display() { const struct drm_connector *connector; const struct drm_encoder *encoder; const struct drm_crtc *crtc; - const drmModeModeInfo *mode; + const drmModeModeInfo *mode, *mode_iter; struct drmdev *drmdev; drmDevicePtr devices[64]; EGLint egl_error; @@ -719,6 +721,7 @@ static int init_display() { return -num_devices; } + // find a GPU that has a primary node for (int i = 0; i < num_devices; i++) { drmDevicePtr device; @@ -735,7 +738,6 @@ static int init_display() { continue; } - printf("[flutter-pi] Chose \"%s\" as the DRM device.\n", device->nodes[DRM_NODE_PRIMARY]); break; } @@ -746,25 +748,23 @@ static int init_display() { return false; } + // find a connected connector for_each_connector_in_drmdev(drmdev, connector) { if (connector->connector->connection == DRM_MODE_CONNECTED) { // only update the physical size of the display if the values // are not yet initialized / not set with a commandline option if ((width_mm == 0) && (height_mm == 0)) { - if ((connector->connector->mmWidth == 160) && - (connector->connector->mmHeight == 90)) - { - // if width and height is exactly 160mm x 90mm, the values are probably bogus. - width_mm = 0; - height_mm = 0; - } else if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && - (connector->connector->mmWidth == 0) && - (connector->connector->mmHeight == 0)) + if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && + (connector->connector->mmWidth == 0) && + (connector->connector->mmHeight == 0)) { // if it's connected via DSI, and the width & height are 0, // it's probably the official 7 inch touchscreen. width_mm = 155; height_mm = 86; + } else if ((connector->connector->mmHeight % 10 == 0) && + (connector->connector->mmWidth % 10 == 0)) { + // don't change anything. } else { width_mm = connector->connector->mmWidth; height_mm = connector->connector->mmHeight; @@ -780,23 +780,23 @@ static int init_display() { return EINVAL; } - { - drmModeModeInfo *mode_iter; - for_each_mode_in_connector(connector, mode_iter) { - if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { - mode = mode_iter; - break; - } else if (mode == NULL) { + // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) + // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, + // prefer higher refresh rates. After that, prefer progressive scanout modes. + for_each_mode_in_connector(connector, mode_iter) { + if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { + mode = mode_iter; + break; + } else if (mode == NULL) { + mode = mode_iter; + } else { + int area = mode_iter->hdisplay * mode_iter->vdisplay; + int old_area = mode->hdisplay * mode->vdisplay; + + if ((area > old_area) || + ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || + ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { mode = mode_iter; - } else { - int area = mode_iter->hdisplay * mode_iter->vdisplay; - int old_area = mode->hdisplay * mode->vdisplay; - - if ((area > old_area) || - ((area == old_area) && (mode_iter->vrefresh > mode->vrefresh)) || - ((area == old_area) && (mode_iter->vrefresh == mode->vrefresh) && ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0))) { - mode = mode_iter; - } } } } @@ -813,11 +813,14 @@ static int init_display() { if (pixel_ratio == 0.0) { if ((width_mm == 0) || (height_mm == 0)) { + fprintf( + stderr, + "[flutter-pi] WARNING: display didn't provide valid physical dimensions.\n" + " The device-pixel ratio will default to 1.0, which may not be the fitting device-pixel ratio for your display.\n" + ); pixel_ratio = 1.0; } else { pixel_ratio = (10.0 * width) / (width_mm * 38.0); - // lets try if this works. - // if (pixel_ratio < 1.0) pixel_ratio = 1.0; } } @@ -846,12 +849,23 @@ static int init_display() { ok = drmdev_configure(drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); if (ok != 0) return ok; - printf("[flutter-pi] 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( + "===================================\n" + "display mode:\n" + " resolution: %u x %u\n" + " refresh rate: %uHz\n" + " physical size: %umm x %umm\n" + " flutter device pixel ratio: %f\n" + "===================================\n", + width, height, + refresh_rate, + width_mm, height_mm, + pixel_ratio + ); /********************** * GBM INITIALIZATION * **********************/ - printf("Creating GBM device\n"); gbm.device = gbm_create_device(drmdev->fd); gbm.format = DRM_FORMAT_ARGB8888; gbm.surface = NULL; @@ -914,8 +928,7 @@ static int init_display() { egl_exts_dpy = eglQueryString(egl.display, EGL_EXTENSIONS); - printf("Using display %p with EGL version %d.%d\n", egl.display, major, minor); - printf("===================================\n"); + //printf("===================================\n"); printf("EGL information:\n"); printf(" version: %s\n", eglQueryString(egl.display, EGL_VERSION)); printf(" vendor: \"%s\"\n", eglQueryString(egl.display, EGL_VENDOR)); @@ -1107,7 +1120,7 @@ static int init_display() { egl.renderer = (char*) glGetString(GL_RENDERER); gl_exts = (char*) glGetString(GL_EXTENSIONS); - printf("===================================\n"); + //printf("===================================\n"); printf("OpenGL ES information:\n"); printf(" version: \"%s\"\n", glGetString(GL_VERSION)); printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); @@ -1339,8 +1352,7 @@ static void init_io(void) { uint32_t props[(INPUT_PROP_CNT+31) /32] = {0}; snprintf(dev.path, sizeof(dev.path), "%s", input_devices_glob.gl_pathv[i]); - printf(" input device %i: path=\"%s\"\n", i, dev.path); - + // first, try to open the event device. dev.fd = open(dev.path, O_RDONLY); if (dev.fd < 0) { @@ -1356,9 +1368,6 @@ static void init_io(void) { goto close_continue; } - printf(" %s, connected via %s. vendor: 0x%04X, product: 0x%04X, version: 0x%04X\n", dev.name, - INPUT_BUSTYPE_FRIENDLY_NAME(dev.input_id.bustype), dev.input_id.vendor, dev.input_id.product, dev.input_id.version); - // query supported event codes (for EV_ABS, EV_REL and EV_KEY event types) ok = ioctl(dev.fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits); if (ok != -1) ok = ioctl(dev.fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits); @@ -1448,7 +1457,7 @@ static void init_io(void) { } if (n_input_devices == 0) - printf("Warning: No evdev input devices configured.\n"); + printf("[flutter-pi] Warning: No input devices configured.\n"); // configure the console ok = console_make_raw(); @@ -1460,7 +1469,7 @@ static void init_io(void) { // now send all the kAdd events to flutter. ok = kSuccess == FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent); - if (!ok) fprintf(stderr, "error while sending initial mousepointer / multitouch slot information to flutter\n"); + if (!ok) fprintf(stderr, "[flutter-pi] error while sending initial mousepointer / multitouch slot information to flutter\n"); } static void on_evdev_input(fd_set fds, size_t n_ready_fds) { @@ -1808,11 +1817,9 @@ static bool parse_cmd_args(int argc, char **argv) { flutter.engine_argc = argc-optind; flutter.engine_argv = (const char* const*) &(argv[optind]); - for (int i=0; i #endif -#ifdef BUILD_ELM327_PLUGIN -# include -#endif #ifdef BUILD_GPIOD_PLUGIN # include #endif @@ -62,10 +59,6 @@ struct flutterpi_plugin hardcoded_plugins[] = { {.name = "testplugin", .init = testp_init, .deinit = testp_deinit}, #endif -#ifdef BUILD_ELM327_PLUGIN - {.name = "elm327plugin", .init = ELM327Plugin_init, .deinit = ELM327Plugin_deinit}, -#endif - #ifdef BUILD_GPIOD_PLUGIN {.name = "flutter_gpiod", .init = gpiodp_init, .deinit = gpiodp_deinit}, #endif diff --git a/src/plugins/elm327plugin.c b/src/plugins/elm327plugin.c deleted file mode 100644 index 29155e9d..00000000 --- a/src/plugins/elm327plugin.c +++ /dev/null @@ -1,648 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - - -struct elm327 elm; -pthread_mutex_t pidqq_lock = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t pidqq_added = PTHREAD_COND_INITIALIZER; -struct pidqq_element *pidqq = NULL; -size_t pidqq_size = 0; -size_t pidqq_nelements = 0; -atomic_bool pidqq_processor_shouldrun = true; -pthread_t pidqq_processor_thread; - -/***************************** - * Communication with ELM327 * - *****************************/ - -/// send a raw command to the ELM327. -/// the contents of the cmd string and an end-of-command marker are -/// send to the ELM. -/// if response is not NULL and length is larger than 0, the ELM327's response -/// is stored in "response". If the buffer given by response and length is to small -/// to fit the ELMs response, elm_command will start writing at the start of the -/// buffer again. -/// if no response buffer is given (the response parameter is NULL or length is 0) -/// elm_command will still wait for a response, it just won't store anything. -int elm_command(char *cmd, char *response, size_t length) { - int ok = 0, count = 0, i = 0, ntransmit; - char charbuff = 0; - elm.elm_errno = ELM_ERRNO_OK; - - if (!elm.is_online) { - fprintf(stderr, "[elm327plugin] elm_command: ELM327 must be online\n"); - return EINVAL; - } - if (!cmd) { - fprintf(stderr, "[elm327plugin] elm_command: command can't be NULL\n"); - return EINVAL; - } - - FlutterEngineTraceEventDurationBegin("elm_command"); - FlutterEngineTraceEventDurationBegin("elm_command write"); - - // write cmd to line - ok = pselect(elm.fd+1, NULL, &elm.fdset, NULL, &elm.timeout, NULL); - if (ok > 0) { - tcflush(elm.fd, TCIOFLUSH); - - // why do we write byte per byte here? - count += write(elm.fd, (const void *) cmd, sizeof(char)*strlen(cmd)); - count += write(elm.fd, (const void *) ELM327_EOC, sizeof(char)*strlen(ELM327_EOC)); - /*for (i=0; i < strlen(cmd); i++) - count += write(elm.fd, (const void *) cmd+i, sizeof(char)); - - for (i=0; i < strlen(ELM327_EOC); i++) - count += write(elm.fd, (const void *) ELM327_EOC+i, sizeof(char)); - */ - - if (count != (strlen(cmd) + strlen(ELM327_EOC))) { - fprintf(stderr, "[elm327plugin] elm_command: could not write command to serial, written %d bytes, should be %ld\n", count, (strlen(cmd) + strlen(ELM327_EOC))); - elm.elm_errno = ELM_ERRNO_NOCONN; - return EIO; - } - } else { - fprintf(stderr, "[elm327plugin] elm_command: elm connection timed out while writing, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); - elm.elm_errno = ELM_ERRNO_NOCONN; - return EIO; - } - - FlutterEngineTraceEventDurationEnd("elm_command write"); - FlutterEngineTraceEventDurationBegin("elm_command read"); - - // read response - i = 0; - while (1) { - ok = read(elm.fd, &charbuff, sizeof(char)); - - if (ok == 0) { - fprintf(stderr, "[elm327plugin] elm_command: ELM327 connection timed out while reading, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); - elm.elm_errno = ELM_ERRNO_NOCONN; - return EIO; - } if (charbuff == '>') { - if (response) response[i] = '\0'; - break; - } else if (response && isprint(charbuff)) { - response[i] = charbuff; - i = (i+1) % length; - } - } - - FlutterEngineTraceEventDurationEnd("elm_command read"); - FlutterEngineTraceEventDurationEnd("elm_command"); - - return 0; -} - -/// queries the value of a pid -/// (uses elm_command for execution) -int elm_query(uint8_t pid, uint32_t* response) { - char txt_response[32], command[6]; - size_t response_length = 0; - uint8_t bytes[6] = {0}; - int ok; - - elm.elm_errno = ELM_ERRNO_OK; - - sprintf(command, "01%02X1", pid); - printf("[elm327plugin] query string: %s\n", command); - - ok = elm_command(command, txt_response, 32); - if (ok != 0) return ok; - - // scan reply for errors - elm.elm_errno = ELM_ERRNO_OK; - if (strstr(txt_response, "ERROR")) { - if (strstr(txt_response, ELM327_BUS_ERROR)) - elm.elm_errno = ELM_ERRNO_BUS_ERROR; - else if (strstr(txt_response, ELM327_CAN_ERROR)) - elm.elm_errno = ELM_ERRNO_CAN_ERROR; - else if (strstr(txt_response, ELM327_LINE_DATA_ERROR)) - elm.elm_errno = ELM_ERRNO_LINE_DATA_ERROR; - else if (strstr(txt_response, ELM327_DATA_ERROR)) - elm.elm_errno = ELM_ERRNO_DATA_ERROR; - else if (strstr(txt_response, ELM327_FEEDBACK_ERROR)) - elm.elm_errno = ELM_ERRNO_FEEDBACK_ERROR; - else if (strstr(txt_response, ELM327_LINE_RX_ERROR)) - elm.elm_errno = ELM_ERRNO_LINE_RX_ERROR; - } else if (strstr(txt_response, ELM327_OK)) - elm.elm_errno = ELM_ERRNO_OK; - else if (strstr(txt_response, ELM327_INVALID)) - elm.elm_errno = ELM_ERRNO_INVALID; - else if (strstr(txt_response, ELM327_ACT_ALERT)) - elm.elm_errno = ELM_ERRNO_ACT_ALERT; - else if (strstr(txt_response, ELM327_BUFFER_FULL)) - elm.elm_errno = ELM_ERRNO_BUFFER_FULL; - else if (strstr(txt_response, ELM327_BUS_BUSY)) - elm.elm_errno = ELM_ERRNO_BUS_BUSY; - else if (strstr(txt_response, ELM327_LOW_POWER_ALERT)) - elm.elm_errno = ELM_ERRNO_LOW_POWER_ALERT; - else if (strstr(txt_response, ELM327_LOW_VOLTAGE_RESET)) - elm.elm_errno = ELM_ERRNO_LOW_VOLTAGE_RESET; - else if (strstr(txt_response, ELM327_NO_DATA)) - elm.elm_errno = ELM_ERRNO_NO_DATA; - else if (strstr(txt_response, ELM327_STOPPED)) - elm.elm_errno = ELM_ERRNO_STOPPED; - else if (strstr(txt_response, ELM327_NOCONN)) - elm.elm_errno = ELM_ERRNO_NOCONN; - else if (strstr(txt_response, ELM327_SEARCHING)) - elm.elm_errno = ELM_ERRNO_SEARCHING; - - if (elm.elm_errno != ELM_ERRNO_OK) { - fprintf(stderr, "[elm327plugin] elm_query: query was not successful. ELM_ERRNO: %d\n", elm.elm_errno); - return EIO; - } - - /* - response_length = strlen(txt_response); - printf("asked for \"%s\", response: \"", command); - for (int i=0; i < response_length; i++) { - if (isprint(txt_response[i])) printf("%c", txt_response[i]); - else printf("\\x%02X", txt_response[i]); - } - printf("\"\n"); - */ - - // parse the response - int res = sscanf(txt_response, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", - bytes, bytes+1, bytes+2, bytes+3, bytes+4, bytes+5); - - if (res == EOF) { - fprintf(stderr, "[elm327plugin] elm_query: string matching error ocurred\n"); - elm.elm_errno = ELM_ERRNO_INVALID; - return EIO; - } else if ((res >= 0) && (res <= 2)) { - fprintf(stderr, "[elm327plugin] elm_query: unexpected ELM327 reply\n"); - elm.elm_errno = ELM_ERRNO_INVALID; - return EIO; - } - - // put the values returned from the ELM327 into the response - *response = 0; - for (int i = 2; i < res; i++) - *response = (*response << 8) | bytes[i]; - - return 0; -} - -/// if the given pid is supported by the vehicle, returns true. -/// otherwise, returns false. -/// the supported PIDs are tested in elm_open, so this won't send anything -/// to the ELM327. -bool elm_pid_supported(uint8_t pid) { - if (pid == 0x00) return true; - - uint8_t pid_bank = (pid-1) >> 5; - uint8_t pid_index = (pid-1) & 0x1F; - pid_index = 0x1f - pid_index; - - return (elm.supported_pids[pid_bank] & (1 << pid_index)) && 1; -} - -/// closes the underlying serial device -void elm_destroy() { - if (elm.is_online) { - cfsetispeed(&elm.tty, B0); - cfsetospeed(&elm.tty, B0); - - close(elm.fd); - } -} - -/// Opens the serial device given through "serial_path" with the given baudrate, -/// and sets up the ELM327 at the other end for communication. -/// elm_command, elm_query, elm_pid_supported and elm_destroy can't be used -/// before elm_open was called. -int elm_open(char *serial_path, int baudrate) { - int ok; - - elm.timeout.tv_sec = 10; - elm.timeout.tv_nsec = 0; - elm.is_online = false; - - ok = access(serial_path, R_OK | W_OK); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: process doesn't have access to serial device \"%s\": %s\n", serial_path, strerror(errno)); - return errno; - } - - // Open serial device at given path - elm.fd = open(serial_path, O_RDWR | O_NOCTTY | O_SYNC); - if (elm.fd < 0) { - fprintf(stderr, "[elm327plugin] elm_open: could not open serial device at \"%s\": %s\n", serial_path, strerror(errno)); - return errno; - } - - // set the serial line attributes - ok = tcgetattr(elm.fd, &elm.tty); - if (ok != 0) goto error; - - /* - elm.tty.c_cflag |= (CLOCAL|CREAD); - elm.tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG|IEXTEN); - elm.tty.c_iflag &= ~(INLCR|IGNCR|ICRNL|IGNBRK|IUCLC|PARMRK| - INPCK|ISTRIP|IXON|IXOFF|IXANY); - elm.tty.c_oflag &= ~(OPOST); - - elm.tty.c_cc[VMIN] = 0; - elm.tty.c_cc[VTIME]= 0; - */ - - // make raw terminal - elm.tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); - elm.tty.c_oflag &= ~OPOST; - elm.tty.c_cflag &= ~(CSIZE | PARENB); - elm.tty.c_cflag |= CS8; - elm.tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - - elm.tty.c_cc[VMIN] = 1; - elm.tty.c_cc[VTIME] = 100; - - - // set the baudrate - speed_t serial_speed; - switch (baudrate) { - case 0: serial_speed = B0; break; - case 50: serial_speed = B50; break; - case 75: serial_speed = B75; break; - case 110: serial_speed = B110; break; - case 134: serial_speed = B134; break; - case 150: serial_speed = B150; break; - case 200: serial_speed = B200; break; - case 300: serial_speed = B300; break; - case 600: serial_speed = B600; break; - case 1200: serial_speed = B1200; break; - case 1800: serial_speed = B1800; break; - case 2400: serial_speed = B2400; break; - case 4800: serial_speed = B4800; break; - case 9600: serial_speed = B9600; break; - case 19200: serial_speed = B19200; break; - case 38400: serial_speed = B38400; break; - case 57600: serial_speed = B57600; break; - case 115200: serial_speed = B115200; break; - case 230400: serial_speed = B230400; break; - default: serial_speed = B0; break; - } - if (serial_speed == B0) { - fprintf(stderr, "[elm327plugin] elm_open: not a valid baudrate: %d\n", baudrate); - return EINVAL; - } - - ok = cfsetispeed(&(elm.tty), serial_speed); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: could not set serial input speed: %s\n", strerror(ok)); - goto error; - } - - ok = cfsetospeed(&(elm.tty), serial_speed); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: could not set serial output speed: %s\n", strerror(ok)); - goto error; - } - - ok = tcsetattr(elm.fd, TCSANOW, &(elm.tty)); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: could not set serial tty-config: %s\n", strerror(ok)); - goto error; - } - - // create an fdset containing the serial device fd - FD_ZERO(&elm.fdset); - FD_SET(elm.fd, &elm.fdset); - - memset(elm.supported_pids, 0, sizeof(elm.supported_pids)); - - // ELM327 is now connected. - elm.is_online = true; - - // completely reset the ELM327 - ok = elm_command(ELM327_RESET, NULL, 0); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: error resetting ELM327 using AT WS: %d\n", ok); - goto error; - } - - ok = elm_command(ELM327_ECHO_OFF, NULL, 0); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: error setting ELM327 echo off using AT E0: %d\n", ok); - goto error; - } - - ok = elm_command(ELM327_LINEFEEDS_OFF, NULL, 0); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: error setting ELM327 linefeeds off using AT L0\n"); - goto error; - } - - ok = elm_command(ELM327_VERSION, elm.version, sizeof(elm.version)); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: error fetching ELM327 version using AT I: %d\n", ok); - goto error; - } - - // send a dummy message so the ELM can determine the cars protocol - ok = elm_query(0x00, elm.supported_pids); - if ((ok != 0) && (elm.elm_errno != ELM_ERRNO_SEARCHING)) { - fprintf(stderr, "[elm327plugin] elm_open: error while querying PID 00 / searching for OBDII bus signal: %d\n", ok); - ok = EIO; - goto error; - } - - // query supported pids - for (int i = 0; i < 8; i++) { - if (!elm_pid_supported(i*0x20)) break; - ok = elm_query(i*0x20, &elm.supported_pids[i]); - if (ok != 0) { - fprintf(stderr, "[elm327plugin] elm_open: error while querying PID %02X: %d\n", i*0x20, ok); - goto error; - } - } - - // output a list of supported PIDs - printf("[elm327plugin] list of supported PIDs: "); - for (uint8_t pid = 0;; pid++) { - if (elm_pid_supported(pid)) printf("0x%02X, ", pid); - if (pid == 0xFF) break; - } - printf("\n"); - - return 0; - - error: - elm.is_online = false; - if (elm.fd >= 0) close(elm.fd); - return ok; -} - -/* - * pid-query priority queue - * - * when the ELM327 plugin wants to know about a pid, - * it has to queue this query in the pid-query queue (pidqq) - * the queries are processed by the pidqq_processor, which runs on it's own thread. - * pid queries with higher priorities are executed first. - */ -void pidqq_add(struct pidqq_element *element) { - int index; - - // make queue larger if necessary - if (pidqq_nelements == pidqq_size) { - size_t before = pidqq_size*sizeof(struct pidqq_element); - pidqq_size = pidqq_size*2; - size_t after = pidqq_size*sizeof(struct pidqq_element); - pidqq = realloc(pidqq, after); - memset(pidqq + before, 0, after-before); - } - - // find a nice place to insert the element - for (index = 0; (pidqq[index].priority >= element->priority) && (index < pidqq_nelements); index++); - - // shift all elements above index one up - for (int i = pidqq_nelements+1; i > index; i--) - pidqq[i] = pidqq[i-1]; - - pidqq[index] = *element; - - pidqq_nelements++; -} -void pidqq_remove(int index) { - for (int i = index+1; (i < pidqq_nelements); i++) - pidqq[i-1] = pidqq[i]; - pidqq[pidqq_nelements-1].priority = 0; - - pidqq_nelements--; -} -int pidqq_findWithPid(uint8_t pid) { - for (int i = 0; i < pidqq_nelements; i++) - if (pidqq[i].pid == pid) - return i; - - return -1; -} -void *run_pidqq_processor(void* arg) { - ELM327PluginPIDQueryCompletionCallback completionCallback = NULL; - struct pidqq_element element; - uint8_t pid; - uint32_t result; - - printf("[elm327plugin] running pid query queue processor\n"); - - while (pidqq_processor_shouldrun) { - result = 0; - - pthread_mutex_lock(&pidqq_lock); - while (!(pidqq[0].priority)) - pthread_cond_wait(&pidqq_added, &pidqq_lock); - - FlutterEngineTraceEventDurationBegin("pidqq_process"); - - pid = pidqq[0].pid; - pthread_mutex_unlock(&pidqq_lock); - - int ok = elm_query(pid, &result); - - pthread_mutex_lock(&pidqq_lock); - element = pidqq[0]; - if ((element.priority) && (element.pid == pid)) { - pidqq_remove(0); - if (element.repeat) pidqq_add(&element); - } - pthread_mutex_unlock(&pidqq_lock); - - if ((element.priority) && (element.pid == pid) && (element.completionCallback)) { - FlutterEngineTraceEventDurationBegin("pidqq completionCallback"); - element.completionCallback(element, result, elm.elm_errno); - FlutterEngineTraceEventDurationEnd("pidqq completionCallback"); - } - - FlutterEngineTraceEventDurationEnd("pidqq_process"); - } - - return NULL; -} - -/***************** - * ELM327 plugin * - *****************/ -void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t result, enum elm327plugin_errno elm_errno) { - // channel object that will be returned to flutter. - struct platch_obj obj = { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = { - .type = kStdFloat64, - .float64_value = 0.0 - } - }; - - if (elm_errno == ELM_ERRNO_OK) { - uint8_t pid = query.pid; - - switch (pid) { - case OBDII_PID_ENGINE_RPM: - obj.std_result.float64_value = (double) result / (double) 4.0; - break; - case OBDII_PID_ENGINE_LOAD: - case OBDII_PID_THROTTLE_POSITION: - obj.std_result.float64_value = result * 100.0 / 255.0; - break; - case OBDII_PID_ENGINE_COOLANT_TEMP: - case OBDII_PID_INTAKE_AIR_TEMP: - obj.std_result.type = kStdInt32; - obj.std_result.int32_value = (int32_t) result - 40; - break; - case OBDII_PID_VEHICLE_SPEED: - obj.std_result.type = kStdInt32; - obj.std_result.int32_value = (int32_t) result; - break; - default: - break; - } - } else { - obj.success = false; - obj.error_code = "queryfailed"; - obj.error_msg = "ELM327 PID query failed. Reason could be a timeout on the connection between Pi and ELM or between ELM and ECU, or something else."; - obj.std_error_details.type = kStdNull; - } - - platch_send(query.channel, &obj, kBinaryCodec, NULL, NULL); -} -int ELM327Plugin_onEventChannelListen(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { - printf("[elm327plugin] listener registered on event channel %s with pid 0x%02X\n", channel, pid); - - // check if pid is supported, if not, respond with an error envelope - if (!elm_pid_supported(pid)) { - return platch_respond_error_std( - handle, - "notsupported", - "The vehicle doesn't support the PID used for this channel.", - NULL - ); - } - - // copy the channel string. - char *channel_copy = malloc(strlen(channel)+1); - if (channel_copy == NULL) { - fprintf(stderr, "[elm327plugin] could not allocate memory.\n"); - return ENOMEM; - } - strcpy(channel_copy, channel); - - // insert a new query into pidqq - pthread_mutex_lock(&pidqq_lock); - pidqq_add(&(struct pidqq_element) { - .channel = channel_copy, - .priority = 1, - .pid = pid, - .repeat = true, - .completionCallback = ELM327Plugin_onPidQueryCompletion - }); - pthread_mutex_unlock(&pidqq_lock); - pthread_cond_signal(&pidqq_added); - - // respond with null - return platch_respond(handle, &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = {.type = kStdNull} - }); -} -int ELM327Plugin_onEventChannelCancel(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { - // remove query from pidqq - pthread_mutex_lock(&pidqq_lock); - int index = pidqq_findWithPid(OBDII_PID_ENGINE_RPM); - free(pidqq[index].channel); - pidqq_remove(index); - pthread_mutex_unlock(&pidqq_lock); - - // respond with null. - return platch_respond(handle, &(struct platch_obj) { - .codec = kStandardMethodCallResponse, - .success = true, - .std_result = {.type = kStdNull} - }); -} -int ELM327Plugin_onReceive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *handle) { - bool isListen = false; - if ((object->codec == kStandardMethodCall) && ((isListen = (strcmp(object->method, "listen") == 0)) || (strcmp(object->method, "cancel") == 0))) { - uint8_t pid = (strcmp(channel, ELM327PLUGIN_RPM_CHANNEL) == 0) ? OBDII_PID_ENGINE_RPM : - (strcmp(channel, ELM327PLUGIN_ENGINELOAD_CHANNEL) == 0) ? OBDII_PID_ENGINE_LOAD : - (strcmp(channel, ELM327PLUGIN_COOLANTTEMP_CHANNEL) == 0) ? OBDII_PID_ENGINE_COOLANT_TEMP : - (strcmp(channel, ELM327PLUGIN_SPEED_CHANNEL) == 0) ? OBDII_PID_VEHICLE_SPEED : - (strcmp(channel, ELM327PLUGIN_THROTTLE_CHANNEL) == 0) ? OBDII_PID_THROTTLE_POSITION : 0; - if (pid == 0) { - fprintf(stderr, "[elm327plugin] ELM327Plugin_onReceive: unexpected event channel: \"%s\"\n", channel); - return EINVAL; - } - - if (elm.is_online) { - if (isListen) ELM327Plugin_onEventChannelListen(channel, pid, handle); - else ELM327Plugin_onEventChannelCancel(channel, pid, handle); - } else { - return platch_respond_error_std( - handle, - "noelm", - "elm.is_online == false. No communication to ELM327 possible, or initialization failed.", - NULL - ); - } - } else { - return platch_respond_not_implemented(handle); - } - return 0; -} - -int ELM327Plugin_init(void) { - int r = 0; - - // init the elm327 - r = elm_open(ELM327PLUGIN_DEVICE_PATH, ELM327PLUGIN_BAUDRATE); - if (r != 0) { - fprintf(stderr, "[elm327plugin] ELM327Plugin_init: ELM327 communication was not initialized successfully. elm327plugin won't supply any OBDII data. error code: %s\n", strerror(r)); - } - - // init pidquery queue - pthread_mutex_init(&pidqq_lock, NULL); - pidqq_size = 50; - pidqq = calloc(pidqq_size, sizeof(struct pidqq_element)); - - pidqq_processor_shouldrun = true; - pthread_create(&pidqq_processor_thread, NULL, run_pidqq_processor, NULL); - - plugin_registry_set_receiver(ELM327PLUGIN_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - plugin_registry_set_receiver(ELM327PLUGIN_RPM_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - plugin_registry_set_receiver(ELM327PLUGIN_ENGINELOAD_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - plugin_registry_set_receiver(ELM327PLUGIN_COOLANTTEMP_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - plugin_registry_set_receiver(ELM327PLUGIN_SPEED_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - plugin_registry_set_receiver(ELM327PLUGIN_THROTTLE_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); - - return 0; -} -int ELM327Plugin_deinit(void) { - pidqq_processor_shouldrun = false; - pthread_join(pidqq_processor_thread, NULL); - - elm_destroy(); - - free(pidqq); - - return 0; -} diff --git a/src/plugins/gpiod.c b/src/plugins/gpiod.c index eb17a7d7..9b5f87c2 100644 --- a/src/plugins/gpiod.c +++ b/src/plugins/gpiod.c @@ -8,9 +8,7 @@ #include #include - - -struct { +static struct { bool initialized; bool line_event_listener_should_run; @@ -29,7 +27,7 @@ struct { bool should_emit_events; } gpio_plugin; -struct { +static struct { void *handle; // GPIO chips @@ -84,7 +82,7 @@ struct line_config { }; // because libgpiod doesn't provide it, but it's useful -static inline void gpiod_line_bulk_remove(struct gpiod_line_bulk *bulk, struct gpiod_line *line) { +static inline void line_bulk_remove(struct gpiod_line_bulk *bulk, struct gpiod_line *line) { struct gpiod_line *linetemp, **cursor; struct gpiod_line_bulk new_bulk = GPIOD_LINE_BULK_INITIALIZER; @@ -96,10 +94,10 @@ static inline void gpiod_line_bulk_remove(struct gpiod_line_bulk *bulk, struct g memcpy(bulk, &new_bulk, sizeof(struct gpiod_line_bulk)); } -static void *gpiodp_io_loop(void *userdata); +static void *line_event_listener_entry(void *userdata); /// ensures the libgpiod binding and the `gpio_plugin` chips list and line map is initialized. -static int gpiodp_ensure_gpiod_initialized(void) { +static int ensure_binding_initialized(void) { struct gpiod_chip_iter *chipiter; struct gpiod_line_iter *lineiter; struct gpiod_chip *chip; @@ -204,7 +202,7 @@ static int gpiodp_ensure_gpiod_initialized(void) { ok = pthread_create( &gpio_plugin.line_event_listener_thread, NULL, - gpiodp_io_loop, + line_event_listener_entry, NULL ); if (ok == -1) { @@ -218,7 +216,7 @@ static int gpiodp_ensure_gpiod_initialized(void) { /// Sends a platform message to `handle` saying that the libgpiod binding has failed to initialize. /// Should be called when `gpiodp_ensure_gpiod_initialized()` has failed. -static int gpiodp_respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { +static int respond_init_failed(FlutterPlatformMessageResponseHandle *handle) { return platch_respond_error_std( handle, "couldnotinit", @@ -229,17 +227,19 @@ static int gpiodp_respond_init_failed(FlutterPlatformMessageResponseHandle *hand /// Sends a platform message to `handle` with error code "illegalargument" /// and error messsage "supplied line handle is not valid". -static int gpiodp_respond_illegal_line_handle(FlutterPlatformMessageResponseHandle *handle) { +static int respond_illegal_line_handle(FlutterPlatformMessageResponseHandle *handle) { return platch_respond_illegal_arg_std(handle, "supplied line handle is not valid"); } -static int gpiodp_respond_not_supported(FlutterPlatformMessageResponseHandle *handle, char *msg) { +static int respond_not_supported(FlutterPlatformMessageResponseHandle *handle, char *msg) { return platch_respond_error_std(handle, "notsupported", msg, NULL); } -static int gpiodp_get_config(struct std_value *value, - struct line_config *conf_out, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int get_config_from_map_arg( + struct std_value *value, + struct line_config *conf_out, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct std_value *temp; unsigned int line_handle; bool has_bias; @@ -277,7 +277,7 @@ static int gpiodp_get_config(struct std_value *value, if (line_handle < gpio_plugin.n_lines) { conf_out->line = gpio_plugin.lines[line_handle]; } else { - ok = gpiodp_respond_illegal_line_handle(responsehandle); + ok = respond_illegal_line_handle(responsehandle); if (ok != 0) return ok; return EINVAL; @@ -372,7 +372,7 @@ static int gpiodp_get_config(struct std_value *value, } if (has_bias && !libgpiod.line_bias) { - ok = gpiodp_respond_not_supported( + ok = respond_not_supported( responsehandle, "Setting line bias is not supported on this platform. " "Expected `arg['bias']` to be null." @@ -439,7 +439,7 @@ static int gpiodp_get_config(struct std_value *value, /// on any of the lines in `gpio_plugin.listening_lines` /// and sends them on to the event channel, if someone /// is listening on it. -static void *gpiodp_io_loop(void *userdata) { +static void *line_event_listener_entry(void *userdata) { struct gpiod_line_event event; struct gpiod_line *line, **cursor; struct gpiod_chip *chip; @@ -526,20 +526,24 @@ static void *gpiodp_io_loop(void *userdata) { } -static int gpiodp_get_num_chips(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_get_num_chips( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { int ok; - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } return platch_respond_success_std(responsehandle, &STDINT32(gpio_plugin.n_chips)); } -static int gpiodp_get_chip_details(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_get_chip_details( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct gpiod_chip *chip; unsigned int chip_index; int ok; @@ -555,9 +559,9 @@ static int gpiodp_get_chip_details(struct platch_obj *object, } // init GPIO - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } // get the chip index @@ -590,8 +594,10 @@ static int gpiodp_get_chip_details(struct platch_obj *object, ); } -static int gpiodp_get_line_handle(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_get_line_handle( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct gpiod_chip *chip; unsigned int chip_index, line_index; int ok; @@ -623,9 +629,9 @@ static int gpiodp_get_line_handle(struct platch_obj *object, } // try to init GPIO - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } // try to get the chip correspondig to the chip index @@ -653,8 +659,10 @@ static int gpiodp_get_line_handle(struct platch_obj *object, return platch_respond_success_std(responsehandle, &STDINT32(line_index)); } -static int gpiodp_get_line_details(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_get_line_details( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct gpiod_line *line; unsigned int line_handle; char *name, *consumer; @@ -674,16 +682,16 @@ static int gpiodp_get_line_details(struct platch_obj *object, } // init GPIO - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } // try to get the gpiod line corresponding to the line handle if (line_handle < gpio_plugin.n_lines) { line = gpio_plugin.lines[line_handle]; } else { - return gpiodp_respond_illegal_line_handle(responsehandle); + return respond_illegal_line_handle(responsehandle); } // if we don't own the line, update it @@ -766,8 +774,10 @@ static int gpiodp_get_line_details(struct platch_obj *object, ); } -static int gpiodp_request_line(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_request_line( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct line_config config; struct std_value *temp; bool is_event_line = false; @@ -783,9 +793,9 @@ static int gpiodp_request_line(struct platch_obj *object, } // ensure GPIO is initialized - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } temp = stdmap_get_str(&object->std_arg, "consumer"); @@ -801,7 +811,7 @@ static int gpiodp_request_line(struct platch_obj *object, } // get the line config - ok = gpiodp_get_config(&object->std_arg, &config, responsehandle); + ok = get_config_from_map_arg(&object->std_arg, &config, responsehandle); if (ok != 0) return ok; // get the triggers @@ -901,8 +911,10 @@ static int gpiodp_request_line(struct platch_obj *object, return platch_respond_success_std(responsehandle, NULL); } -static int gpiodp_release_line(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_release_line( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct gpiod_line *line; unsigned int line_handle; int ok, fd; @@ -921,7 +933,7 @@ static int gpiodp_release_line(struct platch_obj *object, if (line_handle < gpio_plugin.n_lines) { line = gpio_plugin.lines[line_handle]; } else { - return gpiodp_respond_illegal_line_handle(responsehandle); + return respond_illegal_line_handle(responsehandle); } // Try to get the line associated fd and remove @@ -930,7 +942,7 @@ static int gpiodp_release_line(struct platch_obj *object, if (fd != -1) { pthread_mutex_lock(&gpio_plugin.listening_lines_mutex); - gpiod_line_bulk_remove(&gpio_plugin.listening_lines, line); + line_bulk_remove(&gpio_plugin.listening_lines, line); ok = epoll_ctl(gpio_plugin.epollfd, EPOLL_CTL_DEL, fd, NULL); if (ok == -1) { @@ -950,22 +962,24 @@ static int gpiodp_release_line(struct platch_obj *object, return platch_respond_success_std(responsehandle, NULL); } -static int gpiodp_reconfigure_line(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_reconfigure_line( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct line_config config; int ok; // ensure GPIO is initialized - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } - ok = gpiodp_get_config(&object->std_arg, &config, responsehandle); + ok = get_config_from_map_arg(&object->std_arg, &config, responsehandle); if (ok != 0) return ok; if (!libgpiod.line_set_config) { - return gpiodp_respond_not_supported( + return respond_not_supported( responsehandle, "Line reconfiguration is not supported on this platform." ); @@ -985,8 +999,10 @@ static int gpiodp_reconfigure_line(struct platch_obj *object, return platch_respond_success_std(responsehandle, NULL); } -static int gpiodp_get_line_value(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_get_line_value( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct gpiod_line *line; unsigned int line_handle; int ok; @@ -1005,7 +1021,7 @@ static int gpiodp_get_line_value(struct platch_obj *object, if (line_handle < gpio_plugin.n_lines) { line = gpio_plugin.lines[line_handle]; } else { - return gpiodp_respond_illegal_line_handle(responsehandle); + return respond_illegal_line_handle(responsehandle); } // get the line value @@ -1017,8 +1033,10 @@ static int gpiodp_get_line_value(struct platch_obj *object, return platch_respond_success_std(responsehandle, &STDBOOL(ok)); } -static int gpiodp_set_line_value(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_set_line_value( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { struct std_value *temp; struct gpiod_line *line; unsigned int line_handle; @@ -1054,7 +1072,7 @@ static int gpiodp_set_line_value(struct platch_obj *object, if (line_handle < gpio_plugin.n_lines) { line = gpio_plugin.lines[line_handle]; } else { - return gpiodp_respond_illegal_line_handle(responsehandle); + return respond_illegal_line_handle(responsehandle); } // get the line value @@ -1066,67 +1084,79 @@ static int gpiodp_set_line_value(struct platch_obj *object, return platch_respond_success_std(responsehandle, NULL); } -static int gpiodp_supports_bias(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_supports_bias( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { int ok; // ensure GPIO is initialized - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } return platch_respond_success_std(responsehandle, &STDBOOL(libgpiod.line_bias)); } -static int gpiodp_supports_reconfiguration(struct platch_obj *object, - FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_supports_reconfiguration( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { int ok; // ensure GPIO is initialized - ok = gpiodp_ensure_gpiod_initialized(); + ok = ensure_binding_initialized(); if (ok != 0) { - return gpiodp_respond_init_failed(responsehandle); + return respond_init_failed(responsehandle); } return platch_respond_success_std(responsehandle, &STDBOOL(libgpiod.line_set_config)); } /// Handles incoming platform messages. Calls the above methods. -int gpiodp_on_receive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_mch( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { unsigned int chip_index, line_index; bool is_legal_arg; int ok; if STREQ("getNumChips", object->method) { - return gpiodp_get_num_chips(object, responsehandle); + return on_get_num_chips(object, responsehandle); } else if STREQ("getChipDetails", object->method) { - return gpiodp_get_chip_details(object, responsehandle); + return on_get_chip_details(object, responsehandle); } else if STREQ("getLineHandle", object->method) { - return gpiodp_get_line_handle(object, responsehandle); + return on_get_line_handle(object, responsehandle); } else if STREQ("getLineDetails", object->method) { - return gpiodp_get_line_details(object, responsehandle); + return on_get_line_details(object, responsehandle); } else if STREQ("requestLine", object->method) { - return gpiodp_request_line(object, responsehandle); + return on_request_line(object, responsehandle); } else if STREQ("releaseLine", object->method) { - return gpiodp_release_line(object, responsehandle); + return on_release_line(object, responsehandle); } else if STREQ("reconfigureLine", object->method) { - return gpiodp_reconfigure_line(object, responsehandle); + return on_reconfigure_line(object, responsehandle); } else if STREQ("getLineValue", object->method) { - return gpiodp_get_line_value(object, responsehandle); + return on_get_line_value(object, responsehandle); } else if STREQ("setLineValue", object->method) { - return gpiodp_set_line_value(object, responsehandle); + return on_set_line_value(object, responsehandle); } else if STREQ("supportsBias", object->method) { - return gpiodp_supports_bias(object, responsehandle); + return on_supports_bias(object, responsehandle); } else if STREQ("supportsLineReconfiguration", object->method) { - return gpiodp_supports_reconfiguration(object, responsehandle); + return on_supports_reconfiguration(object, responsehandle); } return platch_respond_not_implemented(responsehandle); } -int gpiodp_on_receive_evch(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +static int on_receive_evch( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { if STREQ("listen", object->method) { gpio_plugin.should_emit_events = true; return platch_respond_success_std(responsehandle, NULL); @@ -1142,22 +1172,22 @@ int gpiodp_on_receive_evch(char *channel, struct platch_obj *object, FlutterPlat int gpiodp_init(void) { int ok; - printf("[flutter_gpiod] Initializing...\n"); - gpio_plugin.initialized = false; - ok = plugin_registry_set_receiver(GPIOD_PLUGIN_METHOD_CHANNEL, kStandardMethodCall, gpiodp_on_receive); + ok = plugin_registry_set_receiver(GPIOD_PLUGIN_METHOD_CHANNEL, kStandardMethodCall, on_receive_mch); if (ok != 0) return ok; - plugin_registry_set_receiver(GPIOD_PLUGIN_EVENT_CHANNEL, kStandardMethodCall, gpiodp_on_receive_evch); - if (ok != 0) return ok; - - printf("[flutter_gpiod] Done.\n"); + ok = plugin_registry_set_receiver(GPIOD_PLUGIN_EVENT_CHANNEL, kStandardMethodCall, on_receive_evch); + if (ok != 0) { + plugin_registry_remove_receiver(GPIOD_PLUGIN_METHOD_CHANNEL); + return ok; + } return 0; } int gpiodp_deinit(void) { - printf("[flutter_gpiod] deinit.\n"); + plugin_registry_remove_receiver(GPIOD_PLUGIN_METHOD_CHANNEL); + plugin_registry_remove_receiver(GPIOD_PLUGIN_EVENT_CHANNEL); return 0; } \ No newline at end of file diff --git a/src/plugins/raw_keyboard.c b/src/plugins/raw_keyboard.c index e32490c5..3649641a 100644 --- a/src/plugins/raw_keyboard.c +++ b/src/plugins/raw_keyboard.c @@ -10,14 +10,14 @@ #include -struct { +static struct { // same as mods, just that it differentiates between left and right-sided modifiers. uint16_t leftright_mods; glfw_keymod_map mods; bool initialized; -} raw_keyboard = {.initialized = false}; +} raw_keyboard = {0}; -int rawkb_send_glfw_keyevent(uint32_t code_point, glfw_key key_code, uint32_t scan_code, glfw_keymod_map mods, bool is_down) { +static int send_glfw_keyevent(uint32_t code_point, glfw_key key_code, uint32_t scan_code, glfw_keymod_map mods, bool is_down) { return platch_send( KEY_EVENT_CHANNEL, &(struct platch_obj) { @@ -109,7 +109,7 @@ int rawkb_on_keyevent(glfw_key key, uint32_t scan_code, glfw_key_action action) } if (send) { - rawkb_send_glfw_keyevent(0, key, scan_code, raw_keyboard.mods, action != GLFW_RELEASE); + send_glfw_keyevent(0, key, scan_code, raw_keyboard.mods, action != GLFW_RELEASE); } raw_keyboard.leftright_mods = lrmods_after; @@ -119,19 +119,15 @@ int rawkb_on_keyevent(glfw_key key, uint32_t scan_code, glfw_key_action action) } int rawkb_init(void) { - printf("[raw_keyboard] Initializing...\n"); - raw_keyboard.leftright_mods = 0; raw_keyboard.mods = 0; raw_keyboard.initialized = true; - printf("[raw_keyboard] Done.\n"); return 0; } int rawkb_deinit(void) { raw_keyboard.initialized = false; - printf("[raw_keyboard] deinit.\n"); return 0; } \ No newline at end of file diff --git a/src/plugins/services.c b/src/plugins/services.c index 28a524b7..321fb864 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -5,7 +5,7 @@ #include #include -struct { +static struct { char label[256]; uint32_t primary_color; // ARGB8888 (blue is the lowest byte) char isolate_id[32]; @@ -17,8 +17,11 @@ static int on_receive_navigation(char *channel, struct platch_obj *object, Flutt } static int on_receive_isolate(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - memset(&(services.isolate_id), sizeof(services.isolate_id), 0); - memcpy(services.isolate_id, object->binarydata, object->binarydata_size); + if (object->binarydata_size > sizeof(services.isolate_id)) { + return EINVAL; + } else { + memcpy(services.isolate_id, object->binarydata, object->binarydata_size); + } return platch_respond_not_implemented(responsehandle); } @@ -27,7 +30,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter struct json_value *value; struct json_value *arg = &(object->json_arg); int ok; - + if (strcmp(object->method, "Clipboard.setData") == 0) { /* * Clipboard.setData(Map data) @@ -63,8 +66,6 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * portraitUp, landscapeLeft, portraitDown, landscapeRight * } */ - - printf("setPreferredOrientations\n"); value = &object->json_arg; @@ -169,8 +170,11 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * statusBarBrightness: null / Brightness * systemNavigationBarIconBrightness: null / Brightness */ - } else if (strcmp(object->method, "SystemNavigator.pop")) { - printf("flutter requested application exit\n"); + } else if (strcmp(object->method, "SystemNavigator.pop") == 0) { + post_platform_task(&(struct flutterpi_task) { + .type = kExit, + .target_time = 0, + }); } return platch_respond_not_implemented(responsehandle); @@ -198,44 +202,64 @@ static int on_receive_platform_views(char *channel, struct platch_obj *object, F int services_init(void) { int ok; - printf("[services] Initializing...\n"); - ok = plugin_registry_set_receiver("flutter/navigation", kJSONMethodCall, on_receive_navigation); if (ok != 0) { - fprintf(stderr, "[services-plugin] could not set \"flutter/navigation\" ChannelObject receiver: %s\n", strerror(ok)); - return ok; + fprintf(stderr, "[services-plugin] could not set \"flutter/navigation\" platform message receiver: %s\n", strerror(ok)); + goto fail_return_ok; } ok = plugin_registry_set_receiver("flutter/isolate", kBinaryCodec, on_receive_isolate); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/isolate\" ChannelObject receiver: %s\n", strerror(ok)); - return ok; + goto fail_remove_navigation_receiver; } ok = plugin_registry_set_receiver("flutter/platform", kJSONMethodCall, on_receive_platform); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/platform\" ChannelObject receiver: %s\n", strerror(ok)); - return ok; + goto fail_remove_isolate_receiver; } ok = plugin_registry_set_receiver("flutter/accessibility", kBinaryCodec, on_receive_accessibility); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/accessibility\" ChannelObject receiver: %s\n", strerror(ok)); - return ok; + goto fail_remove_platform_receiver; } ok = plugin_registry_set_receiver("flutter/platform_views", kStandardMethodCall, on_receive_platform_views); if (ok != 0) { fprintf(stderr, "[services-plugin] could not set \"flutter/platform_views\" ChannelObject receiver: %s\n", strerror(ok)); - return ok; + goto fail_remove_accessibility_receiver; } - printf("[services] Done.\n"); - return 0; + + + fail_remove_platform_views_receiver: + plugin_registry_remove_receiver("flutter/platform_views"); + + fail_remove_accessibility_receiver: + plugin_registry_remove_receiver("flutter/accessibility"); + + fail_remove_platform_receiver: + plugin_registry_remove_receiver("flutter/platform"); + + fail_remove_isolate_receiver: + plugin_registry_remove_receiver("flutter/isolate"); + + fail_remove_navigation_receiver: + plugin_registry_remove_receiver("flutter/navigation"); + + fail_return_ok: + return ok; } int services_deinit(void) { - printf("[services] deinit.\n"); + plugin_registry_remove_receiver("flutter/navigation"); + plugin_registry_remove_receiver("flutter/isolate"); + plugin_registry_remove_receiver("flutter/platform"); + plugin_registry_remove_receiver("flutter/accessibility"); + plugin_registry_remove_receiver("flutter/platform_views"); + return 0; } diff --git a/src/plugins/spidev.c b/src/plugins/spidev.c index 60b81c9b..dc0d3181 100644 --- a/src/plugins/spidev.c +++ b/src/plugins/spidev.c @@ -531,8 +531,6 @@ static int spidevp_on_receive(char *channel, struct platch_obj *object, FlutterP } int spidevp_init(void) { - printf("[flutter_spidev] Initializing...\n"); - spi_plugin.size_threads = 1; spi_plugin.threads = malloc(spi_plugin.size_threads * sizeof(struct spidevp_thread *)); @@ -543,7 +541,6 @@ int spidevp_init(void) { plugin_registry_set_receiver(SPI_PLUGIN_METHOD_CHANNEL, kStandardMethodCall, spidevp_on_receive); - printf("[flutter_spidev] Done.\n"); return 0; } diff --git a/src/plugins/testplugin.c b/src/plugins/testplugin.c index 0bbb9276..a137a74a 100644 --- a/src/plugins/testplugin.c +++ b/src/plugins/testplugin.c @@ -146,7 +146,7 @@ int printStd(struct std_value *value, int indent) { uint64_t testplugin_time_offset; -int testp_on_response_json(struct platch_obj *object, void *userdata) { +int on_response_json(struct platch_obj *object, void *userdata) { uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); free(userdata); @@ -156,7 +156,7 @@ int testp_on_response_json(struct platch_obj *object, void *userdata) { } if (object->success) { - printf("testp_on_response_json(dt: %lu)\n" + printf("on_response_json(dt: %lu)\n" " success\n" " result:\n", dt); printJSON(&object->json_result, 4); @@ -171,7 +171,7 @@ int testp_on_response_json(struct platch_obj *object, void *userdata) { return 0; } -int testp_send_json() { +int send_json(void) { uint64_t* time = malloc(sizeof(uint64_t)); *time = FlutterEngineGetCurrentTime(); @@ -198,13 +198,13 @@ int testp_send_json() { }, }; - int ok = platch_call_json(TESTPLUGIN_CHANNEL_JSON, method, &argument, testp_on_response_json, time); + int ok = platch_call_json(TESTPLUGIN_CHANNEL_JSON, method, &argument, on_response_json, time); if (ok != 0) { printf("Could not MethodCall JSON: %s\n", strerror(ok)); } return 0; } -int testp_on_response_std(struct platch_obj *object, void *userdata) { +int on_response_std(struct platch_obj *object, void *userdata) { uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); free(userdata); @@ -229,7 +229,7 @@ int testp_on_response_std(struct platch_obj *object, void *userdata) { return 0; } -int testp_send_std() { +int send_std() { uint64_t *time = malloc(sizeof(uint64_t)); *time = FlutterEngineGetCurrentTime(); @@ -260,18 +260,18 @@ int testp_send_std() { }, }; - platch_call_std(TESTPLUGIN_CHANNEL_STD, method, &argument, testp_on_response_std, time); + platch_call_std(TESTPLUGIN_CHANNEL_STD, method, &argument, on_response_std, time); return 0; } -int testp_on_receive_json(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - printf("testp_on_receive_json(channel: %s)\n" +int on_receive_json(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + printf("[test plugin] on_receive_json(channel: %s)\n" " method: %s\n" " args: \n", channel, object->method); printJSON(&(object->json_arg), 4); - testp_send_json(); + send_json(); return platch_respond(responsehandle, &(struct platch_obj) { .codec = kJSONMethodCallResponse, @@ -281,14 +281,14 @@ int testp_on_receive_json(char *channel, struct platch_obj *object, FlutterPlatf } }); } -int testp_on_receive_std(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - printf("TestPlugin_onReceiveStd(channel: %s)\n" +int on_receive_std(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + printf("[test plugin] on_receive_std(channel: %s)\n" " method: %s\n" " args: \n", channel, object->method); printStd(&(object->std_arg), 4); - testp_send_std(); + send_std(); return platch_respond( responsehandle, @@ -301,7 +301,7 @@ int testp_on_receive_std(char *channel, struct platch_obj *object, FlutterPlatfo } ); } -int testp_on_receive_ping(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { +int on_receive_ping(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { return platch_respond( responsehandle, &(struct platch_obj) { @@ -315,21 +315,28 @@ int testp_on_receive_ping(char *channel, struct platch_obj *object, FlutterPlatf int testp_init(void) { int ok; - printf("[test_plugin] Initializing...\n"); - - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_JSON, kJSONMethodCall, testp_on_receive_json); + ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_JSON, kJSONMethodCall, on_receive_json); if (ok != 0) return ok; - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_STD, kStandardMethodCall, testp_on_receive_std); - if (ok != 0) return ok; + ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_STD, kStandardMethodCall, on_receive_std); + if (ok != 0) { + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_JSON); + return ok; + } - ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_PING, kStringCodec, testp_on_receive_ping); - if (ok != 0) return ok; + ok = plugin_registry_set_receiver(TESTPLUGIN_CHANNEL_PING, kStringCodec, on_receive_ping); + if (ok != 0) { + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_STD); + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_JSON); + return ok; + } - printf("[test_plugin] Done.\n"); return 0; } int testp_deinit(void) { - printf("[test_plugin] deinit.\n"); + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_PING); + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_STD); + plugin_registry_remove_receiver(TESTPLUGIN_CHANNEL_JSON); + return 0; } \ No newline at end of file diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 27af8ebe..755f610c 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -616,21 +616,17 @@ int textin_on_key(glfw_key key) { int textin_init(void) { int ok; - printf("[test_input] Initializing...\n"); - text_input.text[0] = '\0'; text_input.warned_about_autocorrect = false; ok = plugin_registry_set_receiver(TEXT_INPUT_CHANNEL, kJSONMethodCall, textin_on_receive); if (ok != 0) return ok; - printf("[text_input] Done.\n"); - return 0; } int textin_deinit(void) { - printf("[text_input] deinit.\n"); + plugin_registry_remove_receiver(TEXT_INPUT_CHANNEL); return 0; } \ No newline at end of file diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c index ff7b88f2..fb3800cd 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/video_player.c @@ -183,6 +183,7 @@ static int on_mount( &player->mgr->task_queue, &(struct omxplayer_mgr_task) { .type = kUpdateView, + .responsehandle = NULL, .offset_x = offset_x, .offset_y = offset_y, .width = width, @@ -234,6 +235,7 @@ static int on_update_view( &player->mgr->task_queue, &(struct omxplayer_mgr_task) { .type = kUpdateView, + .responsehandle = NULL, .offset_x = offset_x, .offset_y = offset_y, .width = width, @@ -243,6 +245,22 @@ static int on_update_view( ); } +static int respond_sd_bus_error( + FlutterPlatformMessageResponseHandle *handle, + sd_bus_error *err +) { + char str[256]; + + snprintf(str, sizeof(str), "%s: %s", err->name, err->message); + + return platch_respond_error_std( + handle, + "dbus-error", + str, + NULL + ); +} + /// Unfortunately, we can't use sd_bus for this, because it /// wraps some things in containers. static int get_dbus_property( @@ -278,6 +296,7 @@ static int get_dbus_property( ok = libsystemd.sd_bus_message_read_basic(msg, type, ret_ptr); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s\n", strerror(-ok)); + libsystemd.sd_bus_message_unref(msg); return -ok; } @@ -348,13 +367,25 @@ static void *mgr_entry(void *userdata) { ok = cqueue_dequeue(q, &task); if (ok != 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not dequeue creation task in manager thread. cqueue_dequeue: %s\n", strerror(ok)); - return (void*) EXIT_FAILURE; + platch_respond_error_std( + task.responsehandle, + "internal-error", + "Could not dequeue creation task in manager thread.", + NULL + ); + goto fail_remove_evch_listener; } // check that it really is a creation task if (task.type != kCreate || task.responsehandle == NULL) { fprintf(stderr, "[omxplayer_video_player plugin] First task of manager thread is not a creation task.\n"); - return (void*) EXIT_FAILURE; + platch_respond_error_std( + task.responsehandle, + "internal-error", + "First task of manager thread is not a creation task.", + NULL + ); + goto fail_remove_evch_listener; } // determine whether we're watching a stream or not. @@ -379,7 +410,8 @@ static void *mgr_entry(void *userdata) { ok = libsystemd.sd_bus_open_user(&bus); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); - return (void*) EXIT_FAILURE; + platch_respond_native_error_std(task.responsehandle, -ok); + goto fail_remove_evch_listener; } // register a callbacks that tells us when @@ -396,6 +428,11 @@ static void *mgr_entry(void *userdata) { mgr_on_dbus_message, &task ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for omxplayer DBus registration in manager thread. sd_bus_match_signal: %s\n", strerror(-ok)); + platch_respond_native_error_std(task.responsehandle, -ok); + goto fail_close_dbus; + } // spawn the omxplayer process current_zpos = -1; @@ -432,24 +469,27 @@ static void *mgr_entry(void *userdata) { omxplayer_pid = me; } else if (me < 0) { // something went wrong. + ok = errno; perror("[omxplayer_video_player plugin] Could not spawn omxplayer subprocess. fork"); - platch_respond_native_error_std( - task.responsehandle, - errno - ); - - return (void*) EXIT_FAILURE; + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_unref_slot; } while (!task.omxplayer_online) { ok = libsystemd.sd_bus_wait(bus, 1000*1000*5); if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(-ok)); + ok = -ok; + fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_kill_unregistered_player; } ok = libsystemd.sd_bus_process(bus, NULL); if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(-ok)); + ok = -ok; + fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_kill_unregistered_player; } } @@ -468,7 +508,8 @@ static void *mgr_entry(void *userdata) { &duration_us ); if (ok != 0) { - return (void*) EXIT_FAILURE; + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; } // wait for the first frame to appear @@ -493,7 +534,8 @@ static void *mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not send initial pause message: %s, %s\n", err.name, err.message); - return (void*) EXIT_FAILURE; + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; } libsystemd.sd_bus_message_unref(msg); @@ -512,7 +554,8 @@ static void *mgr_entry(void *userdata) { &duration_us ); if (ok != 0) { - return (void*) EXIT_FAILURE; + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; } // get the video width @@ -528,8 +571,8 @@ static void *mgr_entry(void *userdata) { &video_width ); if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not query video width: %s, %s\n", err.name, err.message); - return (void*) EXIT_FAILURE; + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; } // get the video width @@ -545,8 +588,8 @@ static void *mgr_entry(void *userdata) { &video_height ); if (ok < 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not query video height: %s, %s\n", err.name, err.message); - return (void*) EXIT_FAILURE; + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; } // creation was a success! respond to the dart-side with our player id. @@ -583,22 +626,30 @@ static void *mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not send Quit message to omxplayer: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, -ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } libsystemd.sd_bus_message_unref(msg); - // wait for omxplayer to quit - waitpid(omxplayer_pid, NULL, 0); + ok = (int) waitpid(omxplayer_pid, NULL, 0); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] omxplayer quit with exit code %d\n", ok); + } - // close the bus libsystemd.sd_bus_unref(bus); - + plugin_registry_remove_receiver(mgr->player->event_channel_name); - - // remove the player from the set of players + remove_player(mgr->player); + + free(mgr->player); + mgr->player = NULL; + + cqueue_deinit(&mgr->task_queue); + + free(mgr); + mgr = NULL; platch_respond_success_std(task.responsehandle, NULL); @@ -644,7 +695,7 @@ static void *mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not send play message: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, -ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } @@ -666,7 +717,7 @@ static void *mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not send pause message: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, -ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } @@ -699,7 +750,7 @@ static void *mgr_entry(void *userdata) { video_pos_str ); if (ok < 0) { - platch_respond_native_error_std(task.responsehandle, -ok); + fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer viewport. %s, %s\n", err.name, err.message); continue; } @@ -718,7 +769,8 @@ static void *mgr_entry(void *userdata) { (int64_t) task.zpos ); if (ok < 0) { - platch_respond_native_error_std(task.responsehandle, -ok); + fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer layer. %s, %s\n", err.name, err.message); + continue; } libsystemd.sd_bus_message_unref(msg); @@ -740,7 +792,7 @@ static void *mgr_entry(void *userdata) { ); if (ok != 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not get omxplayer position: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } @@ -750,8 +802,6 @@ static void *mgr_entry(void *userdata) { } else if (task.type == kSetPosition) { if (is_stream) { if (task.position == -1) { - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek to end on non-seekable video (a stream) to %lldms\n", task.position); - // TODO: implement seek-to-end platch_respond_success_std( @@ -760,7 +810,7 @@ static void *mgr_entry(void *userdata) { ); } else { // Don't allow flutter to seek to anything other than the end on a stream. - fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream) to %lldms\n", task.position); + fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to seek on non-seekable video (a stream).\n"); platch_respond_error_std( task.responsehandle, @@ -784,7 +834,7 @@ static void *mgr_entry(void *userdata) { ); if (ok != 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, -ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } @@ -811,7 +861,7 @@ static void *mgr_entry(void *userdata) { ); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer volume: %s, %s\n", err.name, err.message); - platch_respond_native_error_std(task.responsehandle, -ok); + respond_sd_bus_error(task.responsehandle, &err); continue; } @@ -822,6 +872,34 @@ static void *mgr_entry(void *userdata) { } return (void*) EXIT_SUCCESS; + + + fail_kill_registered_player: + kill(omxplayer_pid, SIGKILL); + waitpid(omxplayer_pid, NULL, 0); + goto fail_close_dbus; + + fail_kill_unregistered_player: + kill(omxplayer_pid, SIGKILL); + waitpid(omxplayer_pid, NULL, 0); + + fail_unref_slot: + libsystemd.sd_bus_slot_unref(slot); + slot = NULL; + + fail_close_dbus: + libsystemd.sd_bus_unref(bus); + + fail_remove_evch_listener: + plugin_registry_remove_receiver(mgr->player->event_channel_name); + remove_player(mgr->player); + free(mgr->player); + cqueue_deinit(&mgr->task_queue); + free(mgr); + mgr = NULL; + + fail_return_ok: + return (void*) EXIT_FAILURE; } /// Ensures the bindings to libsystemd are initialized. @@ -1328,15 +1406,13 @@ static int on_create( ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_QUEUE_SIZE); if (ok != 0) { - free(mgr); - return platch_respond_native_error_std(responsehandle, ok); + goto fail_free_mgr; } // Allocate the player metadata player = calloc(1, sizeof(*player)); if (player == NULL) { - cqueue_deinit(&mgr->task_queue); - return platch_respond_native_error_std(responsehandle, ENOMEM); + goto fail_deinit_task_queue; } player->player_id = omxpvidpp.next_unused_player_id++; @@ -1352,10 +1428,7 @@ static int on_create( }); if (ok != 0) { - free(mgr); - cqueue_deinit(&mgr->task_queue); - free(player); - return platch_respond_native_error_std(responsehandle, ok); + goto fail_free_player; } snprintf( @@ -1368,10 +1441,7 @@ static int on_create( // add it to our player collection ok = add_player(player); if (ok != 0) { - free(mgr); - cqueue_deinit(&mgr->task_queue); - free(player); - return platch_respond_native_error_std(responsehandle, ok); + goto fail_free_player; } // set a receiver on the videoEvents event channel @@ -1381,28 +1451,35 @@ static int on_create( on_receive_evch ); if (ok != 0) { - //remove_player(player); - free(mgr); - cqueue_deinit(&mgr->task_queue); - free(player); - return platch_respond_native_error_std(responsehandle, ok); + goto fail_remove_player; } - // before we fork, wait for DBus NameOwnerChanged signals, - // so we can immediately pause omxplayer once it has registered - // to dbus. - ok = pthread_create(&mgr->thread, NULL, mgr_entry, mgr); if (ok != 0) { - //remove_player(player); - plugin_registry_remove_receiver(player->event_channel_name); - free(mgr); - cqueue_deinit(&mgr->task_queue); - free(player); - return platch_respond_native_error_std(responsehandle, ok); + goto fail_remove_evch_listener; } return 0; + + + fail_remove_evch_listener: + plugin_registry_remove_receiver(player->event_channel_name); + + fail_remove_player: + remove_player(player); + + fail_free_player: + free(player); + player = NULL; + + fail_deinit_task_queue: + cqueue_deinit(&mgr->task_queue); + + fail_free_mgr: + free(mgr); + mgr = NULL; + + return platch_respond_native_error_std(responsehandle, ok); } static int on_dispose( @@ -1580,8 +1657,6 @@ static int on_create_platform_view( ); } - printf("on_create_platform_view(%lld)\n", view_id); - if (player->has_view) { fprintf(stderr, "[omxplayer_video_player plugin] Flutter attempted to register more than one platform view for one player instance.\n"); @@ -1631,8 +1706,6 @@ static int on_dispose_platform_view( ); } - printf("on_dispose_platform_view(%lld)\n", view_id); - if (player->view_id != view_id) { fprintf( stderr, @@ -1715,6 +1788,7 @@ int omxpvidpp_init(void) { } int omxpvidpp_deinit(void) { + plugin_registry_remove_receiver("flutter.io/omxplayerVideoPlayer"); return 0; } \ No newline at end of file From 737b4dd3401c3b3bb1f2a2f06ab2a4e110616c5e Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 8 Jul 2020 18:10:43 +0200 Subject: [PATCH 08/14] fixes, migrations - use sd_event for main event loop - use libinput for touchscreen, mouse, keyboard input - move global state into a single global struct - migrate the frame queue to self-made concurrent queue - libudev is required for now, will be optional in the future --- Makefile | 9 +- include/collection.h | 328 ++--- include/compositor.h | 13 +- include/flutter-pi.h | 496 +++++--- src/collection.c | 276 ++++ src/compositor.c | 135 +- src/flutter-pi.c | 2474 ++++++++++++++++++++---------------- src/platformchannel.c | 71 +- src/pluginregistry.c | 63 +- src/plugins/services.c | 25 +- src/plugins/video_player.c | 4 +- src/texture_registry.c | 6 +- 12 files changed, 2281 insertions(+), 1619 deletions(-) create mode 100644 src/collection.c diff --git a/Makefile b/Makefile index afc9c32e..620e5020 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ -REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd) \ +REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd libinput libudev) \ -DBUILD_TEXT_INPUT_PLUGIN \ -DBUILD_GPIOD_PLUGIN \ -DBUILD_SPIDEV_PLUGIN \ -DBUILD_TEST_PLUGIN \ -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ - -O2 \ + -O0 -ggdb \ $(CFLAGS) REAL_LDFLAGS = \ - $(shell pkg-config --libs gbm libdrm glesv2 egl) \ + $(shell pkg-config --libs gbm libdrm glesv2 egl libsystemd libinput libudev) \ -lrt \ -lflutter_engine \ -lpthread \ @@ -23,6 +23,7 @@ SOURCES = src/flutter-pi.c \ src/texture_registry.c \ src/compositor.c \ src/modesetting.c \ + src/collection.c \ src/plugins/services.c \ src/plugins/testplugin.c \ src/plugins/text_input.c \ @@ -37,7 +38,7 @@ all: out/flutter-pi out/obj/%.o: src/%.c @mkdir -p $(@D) - $(CC) -c $(REAL_CFLAGS) $(REAL_LDFLAGS) $< -o $@ + $(CC) -c $(REAL_CFLAGS) $< -o $@ out/flutter-pi: $(OBJECTS) @mkdir -p $(@D) diff --git a/include/collection.h b/include/collection.h index 5a9f73b2..2731f5d8 100644 --- a/include/collection.h +++ b/include/collection.h @@ -4,15 +4,11 @@ #include #include #include +#include #include -#define CQUEUE_DEFAULT_MAX_QUEUE_SIZE 64 - -struct concurrent_queue { - pthread_mutex_t mutex; - pthread_cond_t is_dequeueable; - pthread_cond_t is_enqueueable; +struct queue { size_t start_index; size_t length; size_t size; @@ -22,11 +18,26 @@ struct concurrent_queue { size_t element_size; }; -#define CQUEUE_INITIALIZER(element_type, _max_queue_size) \ - ((struct concurrent_queue) { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .is_dequeueable = PTHREAD_COND_INITIALIZER, \ - .is_enqueueable = PTHREAD_COND_INITIALIZER, \ +struct concurrent_queue { + pthread_mutex_t mutex; + pthread_cond_t is_dequeueable; + pthread_cond_t is_enqueueable; + struct queue queue; +}; + +struct concurrent_pointer_set { + pthread_mutex_t mutex; + size_t count_pointers; + size_t size; + void **pointers; + + size_t max_size; +}; + +#define QUEUE_DEFAULT_MAX_SIZE 64 + +#define QUEUE_INITIALIZER(element_type, _max_size) \ + ((struct queue) { \ .start_index = 0, \ .length = 0, \ .size = 0, \ @@ -35,48 +46,63 @@ struct concurrent_queue { .element_size = sizeof(element_type) \ }) -static inline int cqueue_init(struct concurrent_queue *queue, size_t element_size, size_t max_queue_size) { - memset(queue, 0, sizeof(*queue)); +#define CQUEUE_DEFAULT_MAX_SIZE 64 - pthread_mutex_init(&queue->mutex, NULL); - pthread_cond_init(&queue->is_dequeueable, NULL); - pthread_cond_init(&queue->is_enqueueable, NULL); +#define CQUEUE_INITIALIZER(element_type, _max_size) \ + ((struct concurrent_queue) { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .is_dequeueable = PTHREAD_COND_INITIALIZER, \ + .is_enqueueable = PTHREAD_COND_INITIALIZER, \ + .queue = QUEUE_INITIALIZER(element_type, _max_queue_size) \ + }) - queue->start_index = 0; - queue->length = 0; - queue->size = 2; - queue->elements = calloc(2, element_size); +#define CPSET_DEFAULT_MAX_SIZE 64 - queue->max_queue_size = max_queue_size; - queue->element_size = element_size; +#define CPSET_INITIALIZER(_max_size) \ + ((struct concurrent_pointer_set) { \ + .mutex = PTHREAD_MUTEX_INITIALIZER, \ + .count_pointers = 0, \ + .size = 0, \ + .pointers = NULL, \ + .max_size = _max_size \ + }) - if (queue->elements == NULL) { - queue->size = 0; - return ENOMEM; - } - return 0; -} +int queue_init( + struct queue *queue, + size_t element_size, + size_t max_queue_size +); -static inline int cqueue_deinit(struct concurrent_queue *queue) { - pthread_mutex_destroy(&queue->mutex); - pthread_cond_destroy(&queue->is_dequeueable); - pthread_cond_destroy(&queue->is_enqueueable); +int queue_deinit( + struct queue *queue +); - if (queue->elements != NULL) { - free(queue->elements); - } +int queue_enqueue( + struct queue *queue, + const void *p_element +); - queue->start_index = 0; - queue->length = 0; - queue->size = 0; - queue->elements = NULL; +int queue_dequeue( + struct queue *queue, + void *element_out +); - queue->max_queue_size = 0; - queue->element_size = 0; +int queue_peek( + struct queue *queue, + void **pelement_out +); - return 0; -} + +int cqueue_init( + struct concurrent_queue *queue, + size_t element_size, + size_t max_queue_size +); + +int cqueue_deinit( + struct concurrent_queue *queue +); static inline int cqueue_lock(struct concurrent_queue * const queue) { return pthread_mutex_lock(&queue->mutex); @@ -86,172 +112,50 @@ static inline int cqueue_unlock(struct concurrent_queue * const queue) { return pthread_mutex_unlock(&queue->mutex); } -static inline int cqueue_try_enqueue( - struct concurrent_queue * const queue, - const void const *p_element -) { - cqueue_lock(queue); - - if (queue->size == queue->length) { - // expand the queue. - - size_t new_size = queue->size ? queue->size << 1 : 1; - - if (new_size > queue->max_queue_size) { - cqueue_unlock(queue); - return EAGAIN; - } - - void *new_elements = realloc(queue->elements, new_size * queue->element_size); - - if (new_elements == NULL) { - cqueue_unlock(queue); - return ENOMEM; - } - - if (queue->size) { - memcpy(((char*)new_elements) + queue->element_size * queue->size, new_elements, queue->element_size * queue->size); - } - - queue->elements = new_elements; - queue->size = new_size; - } - - memcpy( - ((char*) queue->elements) + queue->element_size*(queue->start_index + queue->length), - p_element, - queue->element_size - ); - - queue->length++; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -static inline int cqueue_try_dequeue( - struct concurrent_queue * const queue, - void const *element_out -) { - cqueue_lock(queue); - - if (queue->length == 0) { - cqueue_unlock(queue); - return EAGAIN; - } - - memcpy( - ((char*) queue->elements) + queue->element_size*queue->start_index, - element_out, - queue->element_size - ); - - queue->start_index = (queue->start_index + 1) & (queue->size - 1); - queue->length--; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_enqueueable); - - return 0; -} - -static inline int cqueue_enqueue( - struct concurrent_queue * const queue, - const void const *p_element -) { - size_t new_size; - cqueue_lock(queue); - - if (queue->size == queue->length) { - // expand the queue or wait for an element to be dequeued. - - new_size = queue->size ? (queue->size << 1) : 1; - - if (new_size < queue->max_queue_size) { - void *new_elements = realloc(queue->elements, new_size * queue->element_size); - - if (new_elements == NULL) { - cqueue_unlock(queue); - return ENOMEM; - } - - if (queue->size) { - memcpy(((char*)new_elements) + (queue->element_size*queue->size), new_elements, queue->element_size*queue->size); - } - - queue->elements = new_elements; - queue->size = new_size; - } else { - do { - pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); - } while (queue->size == queue->length); - } - } - - memcpy( - ((char*) queue->elements) + (queue->element_size*((queue->start_index + queue->length) & (queue->size - 1))), - p_element, - queue->element_size - ); - - queue->length++; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_dequeueable); - - return 0; -} - -static inline int cqueue_dequeue( - struct concurrent_queue *const queue, - void *const element_out -) { - cqueue_lock(queue); - - while (queue->length == 0) - pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); - - memcpy( - element_out, - ((char*) queue->elements) + (queue->element_size*queue->start_index), - queue->element_size - ); - - queue->start_index = (queue->start_index + 1) & (queue->size - 1); - queue->length--; - - cqueue_unlock(queue); - - pthread_cond_signal(&queue->is_enqueueable); - - return 0; -} - - -struct concurrent_pointer_set { - pthread_mutex_t mutex; - size_t count_pointers; - size_t size; - void **pointers; - - size_t max_size; -}; - -#define CPSET_DEFAULT_MAX_SIZE 64 - -#define CPSET_INITIALIZER(_max_size) \ - ((struct concurrent_pointer_set) { \ - .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .count_pointers = 0, \ - .size = 0, \ - .pointers = NULL, \ - .max_size = _max_size \ - }) +int cqueue_try_enqueue_locked( + struct concurrent_queue *queue, + const void *p_element +); + +int cqueue_enqueue_locked( + struct concurrent_queue *queue, + const void *p_element +); + +int cqueue_try_enqueue( + struct concurrent_queue *queue, + const void *p_element +); + +int cqueue_enqueue( + struct concurrent_queue *queue, + const void *p_element +); + +int cqueue_try_dequeue_locked( + struct concurrent_queue *queue, + void *element_out +); + +int cqueue_dequeue_locked( + struct concurrent_queue *queue, + void *element_out +); + +int cqueue_try_dequeue( + struct concurrent_queue *queue, + void *element_out +); + +int cqueue_dequeue( + struct concurrent_queue *queue, + void *element_out +); + +int cqueue_peek_locked( + struct concurrent_queue *queue, + void **pelement_out +); static inline int cpset_init( struct concurrent_pointer_set *const set, @@ -462,4 +366,10 @@ static inline void *memdup(const void *restrict src, const size_t n) { return memcpy(dest, src, n); } +#define BMAP_DECLARATION(name, n_bits) uint8_t name[(((n_bits) - 1) / 8) + 1] +#define BMAP_IS_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] & (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) +#define BMAP_SET(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] |= (1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) +#define BMAP_CLEAR(p_bmap, i_bit) ((p_bmap)[(i_bit) / sizeof(*(p_bmap))] &= ~(1 << ((i_bit) & (sizeof(*(p_bmap)) - 1)))) +#define BMAP_ZERO(p_bmap, n_bits) (memset((p_bmap), 0, (((n_bits) - 1) / 8) + 1)) + #endif \ No newline at end of file diff --git a/include/compositor.h b/include/compositor.h index d1e02df0..8c4df569 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -54,16 +54,17 @@ typedef int (*platform_view_present_cb)( void *userdata ); -struct flutterpi_compositor { +struct compositor { struct drmdev *drmdev; struct concurrent_pointer_set cbs; struct concurrent_pointer_set planes; bool should_create_window_surface_backing_store; bool has_applied_modeset; + FlutterCompositor flutter_compositor; }; struct window_surface_backing_store { - struct flutterpi_compositor *compositor; + struct compositor *compositor; struct gbm_surface *gbm_surface; struct gbm_bo *current_front_bo; uint32_t drm_plane_id; @@ -77,8 +78,13 @@ struct drm_rbo { uint32_t drm_fb_id; }; +struct drm_fb { + struct gbm_bo *bo; + uint32_t fb_id; +}; + struct drm_fb_backing_store { - struct flutterpi_compositor *compositor; + struct compositor *compositor; GLuint gl_fbo_id; struct drm_rbo rbos[2]; @@ -135,4 +141,5 @@ int compositor_initialize( struct drmdev *drmdev ); + #endif \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 2de46847..240e5328 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -10,18 +10,46 @@ #include #include #include +#include #include #include +#include +#include #include -#define EGL_EGLEXT_PROTOTYPES +//#define EGL_EGLEXT_PROTOTYPES #include #include -#define GL_GLEXT_PROTOTYPES +//#define GL_GLEXT_PROTOTYPES #include #include #include +#include + +#define LOAD_EGL_PROC(flutterpi_struct, name) \ + do { \ + char proc[256]; \ + snprintf(proc, 256, "%s", "egl" #name); \ + proc[3] = toupper(proc[3]); \ + (flutterpi_struct).egl.name = (void*) eglGetProcAddress(proc); \ + if ((flutterpi_struct).egl.name == NULL) { \ + fprintf(stderr, "[flutter-pi] FATAL: Could not resolve EGL procedure " #name "\n"); \ + return EINVAL; \ + } \ + } while (false) + +#define LOAD_GL_PROC(flutterpi_struct, name) \ + do { \ + char proc_name[256]; \ + snprintf(proc_name, 256, "gl" #name); \ + proc_name[2] = toupper(proc_name[2]); \ + (flutterpi_struct).gl.name = (void*) eglGetProcAddress(proc_name); \ + if ((flutterpi_struct).gl.name == NULL) { \ + fprintf(stderr, "[flutter-pi] FATAL: Could not resolve GL procedure " #name "\n"); \ + return EINVAL; \ + } \ + } while (false) enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight @@ -32,226 +60,278 @@ enum device_orientation { (o) == kLandscapeLeft ? 90 : \ (o) == kPortraitDown ? 180 : \ (o) == kLandscapeRight ? 270 : 0) - -#define FLUTTER_ROTATION_TRANSFORMATION(deg) ((FlutterTransformation) \ - {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = -sin(((double) (deg))/180.0*M_PI), .transX = 0, \ - .skewY = sin(((double) (deg))/180.0*M_PI), .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = 0, \ - .pers0 = 0, .pers1 = 0, .pers2 = 1}) - - -extern enum device_orientation orientation; -extern int rotation; - -typedef enum { - kVBlankRequest, - kVBlankReply, - kUpdateOrientation, - kSendPlatformMessage, - kRespondToPlatformMessage, - kFlutterTask, - kRegisterExternalTexture, - kUnregisterExternalTexture, - kMarkExternalTextureFrameAvailable, - kGeneric, - kExit -} flutterpi_task_type; - -struct flutterpi_task { - struct flutterpi_task* next; - flutterpi_task_type type; - union { - FlutterTask task; - struct { - uint64_t vblank_ns; - intptr_t baton; - }; - enum device_orientation orientation; - struct { - char *channel; - const FlutterPlatformMessageResponseHandle *responsehandle; - size_t message_size; - uint8_t *message; - }; - int64_t texture_id; - struct { - void (*callback)(void *userdata); - void *userdata; - }; - }; - uint64_t target_time; + +#define ANGLE_BETWEEN_ORIENTATIONS(o_start, o_end) \ + (ANGLE_FROM_ORIENTATION(o_end) \ + - ANGLE_FROM_ORIENTATION(o_start) \ + + (ANGLE_FROM_ORIENTATION(o_start) > ANGLE_FROM_ORIENTATION(o_end) ? 360 : 0)) + +#define FLUTTER_TRANSLATION_TRANSFORMATION(translate_x, translate_y) ((FlutterTransformation) \ + {.scaleX = 1, .skewX = 0, .transX = translate_x, \ + .skewY = 0, .scaleY = 1, .transY = translate_y, \ + .pers0 = 0, .pers1 = 0, .pers2 = 1}) + +#define FLUTTER_ROTX_TRANSFORMATION(deg) ((FlutterTransformation) \ + {.scaleX = 1, .skewX = 0, .transX = 0, \ + .skewY = 0, .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = -sin(((double) (deg))/180.0*M_PI), \ + .pers0 = 0, .pers1 = sin(((double) (deg))/180.0*M_PI), .pers2 = cos(((double) (deg))/180.0*M_PI)}) + +#define FLUTTER_ROTY_TRANSFORMATION(deg) ((FlutterTransformation) \ + {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = 0, .transX = sin(((double) (deg))/180.0*M_PI), \ + .skewY = 0, .scaleY = 1, .transY = 0, \ + .pers0 = -sin(((double) (deg))/180.0*M_PI), .pers1 = 0, .pers2 = cos(((double) (deg))/180.0*M_PI)}) + +#define FLUTTER_ROTZ_TRANSFORMATION(deg) ((FlutterTransformation) \ + {.scaleX = cos(((double) (deg))/180.0*M_PI), .skewX = -sin(((double) (deg))/180.0*M_PI), .transX = 0, \ + .skewY = sin(((double) (deg))/180.0*M_PI), .scaleY = cos(((double) (deg))/180.0*M_PI), .transY = 0, \ + .pers0 = 0, .pers1 = 0, .pers2 = 1}) + +#define FLUTTER_MULTIPLIED_TRANSFORMATIONS(a, b) ((FlutterTransformation) \ + {.scaleX = a.scaleX * b.scaleX + a.skewX * b.skewY + a.transX * b.pers0, \ + .skewX = a.scaleX * b.skewX + a.skewX * b.scaleY + a.transX * b.pers1, \ + .transX = a.scaleX * b.transX + a.skewX * b.transY + a.transX * b.pers2, \ + .skewY = a.skewY * b.scaleX + a.scaleY * b.skewY + a.transY * b.pers0, \ + .scaleY = a.skewY * b.skewX + a.scaleY * b.scaleY + a.transY * b.pers1, \ + .transY = a.skewY * b.transX + a.scaleY * b.transY + a.transY * b.pers2, \ + .pers0 = a.pers0 * b.scaleX + a.pers1 * b.skewY + a.pers2 * b.pers0, \ + .pers1 = a.pers0 * b.skewX + a.pers1 * b.scaleY + a.pers2 * b.pers1, \ + .pers2 = a.pers0 * b.transX + a.pers1 * b.transY + a.pers2 * b.pers2}) + +static inline void apply_flutter_transformation( + const FlutterTransformation t, + double *px, + double *py +) { + double x = px != NULL ? *px : 0; + double y = py != NULL ? *py : 0; + + if (px != NULL) { + *px = t.scaleX*x + t.skewX*y + t.transX; + } + + if (py != NULL) { + *py = t.skewY*x + t.scaleY*y + t.transY; + } +} + +#define FLUTTER_RESULT_TO_STRING(result) \ + ((result) == kSuccess ? "Success." : \ + (result) == kInvalidLibraryVersion ? "Invalid library version." : \ + (result) == kInvalidArguments ? "Invalid arguments." : \ + (result) == kInternalInconsistency ? "Internal inconsistency." : "(?)") + +#define LIBINPUT_EVENT_IS_TOUCH(event_type) (\ + ((event_type) == LIBINPUT_EVENT_TOUCH_DOWN) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_UP) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_MOTION) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_CANCEL) || \ + ((event_type) == LIBINPUT_EVENT_TOUCH_FRAME)) + +#define LIBINPUT_EVENT_IS_POINTER(event_type) (\ + ((event_type) == LIBINPUT_EVENT_POINTER_MOTION) || \ + ((event_type) == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) || \ + ((event_type) == LIBINPUT_EVENT_POINTER_BUTTON) || \ + ((event_type) == LIBINPUT_EVENT_POINTER_AXIS)) + +enum frame_state { + kFramePending, + kFrameRendering, + kFrameRendered }; -struct drm_fb { - struct gbm_bo *bo; - uint32_t fb_id; -}; +struct frame { + /// The current state of the frame. + /// - Pending, when the frame was requested using the FlutterProjectArgs' vsync_callback. + /// - Rendering, when the baton was returned to the engine + /// - Rendered, when the frame has been / is visible on the display. + enum frame_state state; -struct pageflip_data { - struct gbm_bo *releaseable_bo; - intptr_t next_baton; + /// The baton to be returned to the flutter engine when the frame can be rendered. + intptr_t baton; }; -// position & pointer phase of a mouse pointer / multitouch slot -// A 10-finger multi-touch display has 10 slots and each of them have their own position, tracking id, etc. -// All mouses / touchpads share the same mouse pointer. -struct mousepointer_mtslot { - // the MT tracking ID used to track this touch. - int id; - int32_t flutter_slot_id; - double x, y; - FlutterPointerPhase phase; -}; +struct compositor; -extern struct drm { - //char device[PATH_MAX]; - //bool has_device; - //int fd; - //uint32_t connector_id; - //drmModeModeInfo *mode; - //uint32_t crtc_id; - //size_t crtc_index; - struct gbm_bo *current_bo; - drmEventContext evctx; - - bool disable_vsync; - struct drmdev *drmdev; -} drm; - -extern struct gbm { - struct gbm_device *device; - struct gbm_surface *surface; - uint32_t format; - uint64_t modifier; -} gbm; - -extern struct egl { - EGLDisplay display; - EGLConfig config; - EGLContext root_context; - EGLContext flutter_render_context; - EGLContext flutter_resource_uploading_context; - EGLContext vidpp_context; - EGLContext compositor_context; - EGLSurface surface; - - bool modifiers_supported; - char *renderer; - - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay; - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface; - PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurface; - PFNEGLCREATEDRMIMAGEMESAPROC createDRMImageMESA; - PFNEGLEXPORTDRMIMAGEMESAPROC exportDRMImageMESA; -} egl; - -extern struct gl { - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES; - PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC EGLImageTargetRenderbufferStorageOES; -} gl; - -#define LOAD_EGL_PROC(name) \ - do { \ - char proc[256]; \ - snprintf(proc, 256, "%s", "egl" #name); \ - proc[3] = toupper(proc[3]); \ - egl.name = (void*) eglGetProcAddress(proc); \ - if (!egl.name) { \ - fprintf(stderr, "could not resolve EGL procedure " #name "\n"); \ - return EINVAL; \ - } \ - } while (false) +enum flutter_runtime_mode { + kDebug, kProfile, kRelease +}; -#define LOAD_GL_PROC(name) \ - do { \ - char proc_name[256]; \ - snprintf(proc_name, 256, "gl" #name); \ - proc_name[2] = toupper(proc_name[2]); \ - gl.name = (void*) eglGetProcAddress(proc_name); \ - if (!gl.name) { \ - fprintf(stderr, "could not resolve GL procedure " #name "\n"); \ - return EINVAL; \ - } \ - } while (false) +struct flutterpi { + /// graphics stuff + struct { + struct drmdev *drmdev; + drmEventContext evctx; + sd_event_source *drm_pageflip_event_source; + bool platform_supports_get_sequence_ioctl; + } drm; + + struct { + struct gbm_device *device; + struct gbm_surface *surface; + uint32_t format; + uint64_t modifier; + } gbm; + + struct { + EGLDisplay display; + EGLConfig config; + EGLContext root_context; + EGLContext flutter_render_context; + EGLContext flutter_resource_uploading_context; + EGLContext compositor_context; + EGLSurface surface; + + char *renderer; + + PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface; + PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC createPlatformPixmapSurface; + PFNEGLCREATEDRMIMAGEMESAPROC createDRMImageMESA; + PFNEGLEXPORTDRMIMAGEMESAPROC exportDRMImageMESA; + } egl; + + struct { + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES; + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC EGLImageTargetRenderbufferStorageOES; + } gl; + + struct { + /// width & height of the display in pixels. + int width, height; + + /// physical width & height of the display in millimeters + /// the physical size can only be queried for HDMI displays (and even then, most displays will + /// probably return bogus values like 160mm x 90mm). + /// for DSI displays, the physical size of the official 7-inch display will be set in init_display. + /// init_display will only update width_mm and height_mm if they are set to zero, allowing you + /// to hardcode values for you individual display. + int width_mm, height_mm; + + int refresh_rate; + + /// The pixel ratio used by flutter. + /// This is computed inside init_display using width_mm and height_mm. + /// flutter only accepts pixel ratios >= 1.0 + double pixel_ratio; + } display; + + struct { + /// This is false when the value in the "orientation" field is + /// unset. (Since we can't just do orientation = null) + bool has_orientation; + + /// The current device orientation. + /// The initial device orientation is based on the width & height data from drm, + /// or given as command line arguments. + enum device_orientation orientation; -#define INPUT_BUSTYPE_FRIENDLY_NAME(bustype) ( \ - (bustype) == BUS_PCI ? "PCI/e" : \ - (bustype) == BUS_USB ? "USB" : \ - (bustype) == BUS_BLUETOOTH ? "Bluetooth" : \ - (bustype) == BUS_VIRTUAL ? "virtual" : \ - (bustype) == BUS_I2C ? "I2C" : \ - (bustype) == BUS_HOST ? "Host-Interface" : \ - (bustype) == BUS_SPI ? "SPI" : "other") - -#define FLUTTER_BUTTON_FROM_EVENT_CODE(code) ((uint16_t) \ - (code) == BTN_LEFT ? kFlutterPointerButtonMousePrimary : \ - (code) == BTN_RIGHT ? kFlutterPointerButtonMouseSecondary : \ - (code) == BTN_MIDDLE ? kFlutterPointerButtonMouseMiddle : \ - (code) == BTN_FORWARD ? kFlutterPointerButtonMouseForward : \ - (code) == BTN_BACK ? kFlutterPointerButtonMouseBack : \ - (code) == BTN_TOUCH ? (1 << 8) : 0) - -#define MODIFIER_KEY_FROM_EVENT_CODE(code) ((uint16_t) \ - ((code) == KEY_LEFTCTRL) || ((code) == KEY_RIGHTCTRL) ? kControlModifier : \ - ((code) == KEY_LEFTSHIFT) || ((code) == KEY_RIGHTSHIFT) ? kShiftModifier : \ - ((code) == KEY_LEFTALT) || ((code) == KEY_RIGHTALT) ? kAltModifier : \ - ((code) == KEY_LEFTMETA) || ((code) == KEY_RIGHTMETA) ? kMetaModifier : \ - ((code) == KEY_CAPSLOCK) ? kCapsLockModifier : \ - ((code) == KEY_NUMLOCK) ? kNumLockModifier : 0) - -#define POINTER_PHASE_AS_STRING(phase) ( \ - (phase) == kCancel ? "kCancel" : \ - (phase) == kUp ? "kUp" : \ - (phase) == kDown ? "kDown" : \ - (phase) == kMove ? "kMove" : \ - (phase) == kAdd ? "kAdd" : \ - (phase) == kRemove ? "kRemove" : \ - (phase) == kHover ? "kHover" : "???") - -#define ISSET(uint32bitmap, bit) (uint32bitmap[(bit)/32] & (1 << ((bit) & 0x1F))) - -struct input_device { - char path[PATH_MAX]; - char name[256]; - struct input_id input_id; - int fd; - - // the pointer device kind reported to the flutter engine - FlutterPointerDeviceKind kind; - - // this should be true for mouse and touchpad, false for touchscreens / stylus - bool is_pointer; - bool is_direct; + bool has_rotation; + + /// The angle between the initial device orientation and the current device orientation in degrees. + /// (applied as a rotation to the flutter window in transformation_callback, and also + /// is used to determine if width/height should be swapped when sending a WindowMetrics event to flutter) + int rotation; + + /// width & height of the flutter view. These are the dimensions send to flutter using + /// [FlutterEngineSendWindowMetricsEvent]. (So, for example, with rotation == 90, these + /// dimensions are swapped compared to the display dimensions) + int width, height; + + int width_mm, height_mm; + + /// Used by flutter to transform the flutter view to fill the display. + /// Matrix that transforms flutter view coordinates to display coordinates. + FlutterTransformation view_to_display_transform; + + /// Used by the touch input to transform raw display coordinates into flutter view coordinates. + /// Matrix that transforms display coordinates into flutter view coordinates + FlutterTransformation display_to_view_transform; + } view; + + struct concurrent_queue frame_queue; + + struct compositor *compositor; + + /// IO + struct { + bool use_paths; + glob_t input_devices_glob; + struct udev *udev; + struct libinput *libinput; + sd_event_source *libinput_event_source; + sd_event_source *stdin_event_source; + int64_t next_unused_flutter_device_id; + } input; + + /// flutter stuff + struct { + char *asset_bundle_path; + char *kernel_blob_path; + char *app_elf_path; + void *app_elf_handle; + char *icu_data_path; + + int engine_argc; + char **engine_argv; + enum flutter_runtime_mode runtime_mode; + FlutterEngine engine; + } flutter; - // for EV_ABS devices (touchscreens, some touchpads) - struct input_absinfo xinfo, yinfo; - - // n_slots is > 1 for Multi-Touch devices (most touchscreens) - // just because n_slots is 0 and slots is NULL, doesn't mean active_slot is NULL. - // mouse devices own 0 slots (since they all share a global slot), and still have an active_slot. - size_t n_mtslots; - size_t i_active_mtslot; - struct mousepointer_mtslot *mtslots; - - // currently pressed buttons (for mouse, touchpad, stylus) - // (active_buttons & 0xFF) will be the value of the "buttons" field - // of the FlutterPointerEvent being sent to flutter - uint16_t active_buttons; + /// main event loop + pthread_t event_loop_thread; + pthread_mutex_t event_loop_mutex; + sd_event *event_loop; + int wakeup_event_loop_fd; + + /// flutter-pi internal stuff + struct plugin_registry *plugin_registry; }; -// we have one global mouse pointer, even if multiple mouses are attached -extern struct mousepointer_mtslot mousepointer; +struct platform_task { + int (*callback)(void *userdata); + void *userdata; +}; -extern FlutterEngine engine; +struct platform_message { + bool is_response; + union { + FlutterPlatformMessageResponseHandle *target_handle; + struct { + char *target_channel; + FlutterPlatformMessageResponseHandle *response_handle; + }; + }; + uint8_t *message; + size_t message_size; +}; -void post_platform_task(struct flutterpi_task *task); +extern struct flutterpi flutterpi; -int flutterpi_send_platform_message(const char *channel, - const uint8_t *restrict message, - size_t message_size, - FlutterPlatformMessageResponseHandle *responsehandle); +struct input_device_data { + int64_t flutter_device_id_offset; + double x, y; + int64_t buttons; + uint64_t timestamp; +}; -int flutterpi_respond_to_platform_message(FlutterPlatformMessageResponseHandle *handle, - const uint8_t *restrict message, - size_t message_size); +int flutterpi_fill_view_properties( + bool has_orientation, + enum device_orientation orientation, + bool has_rotation, + int rotation +); + +int flutterpi_send_platform_message( + const char *channel, + const uint8_t *restrict message, + size_t message_size, + FlutterPlatformMessageResponseHandle *responsehandle +); + +int flutterpi_respond_to_platform_message( + FlutterPlatformMessageResponseHandle *handle, + const uint8_t *restrict message, + size_t message_size +); #endif \ No newline at end of file diff --git a/src/collection.c b/src/collection.c new file mode 100644 index 00000000..223cc93c --- /dev/null +++ b/src/collection.c @@ -0,0 +1,276 @@ +#include + +int queue_init(struct queue *queue, size_t element_size, size_t max_queue_size) { + memset(queue, 0, sizeof(*queue)); + + queue->start_index = 0; + queue->length = 0; + queue->size = 2; + queue->elements = calloc(2, element_size); + + queue->max_queue_size = max_queue_size; + queue->element_size = element_size; + + if (queue->elements == NULL) { + queue->size = 0; + return ENOMEM; + } + + return 0; +} + +int queue_deinit(struct queue *queue) { + if (queue->elements != NULL) { + free(queue->elements); + } + + queue->start_index = 0; + queue->length = 0; + queue->size = 0; + queue->elements = NULL; + + queue->max_queue_size = 0; + queue->element_size = 0; + + return 0; +} + +int queue_enqueue( + struct queue *queue, + const void *p_element +) { + size_t new_size; + + if (queue->size == queue->length) { + // expand the queue or wait for an element to be dequeued. + + new_size = queue->size ? (queue->size << 1) : 1; + + if (new_size < queue->max_queue_size) { + void *new_elements = realloc(queue->elements, new_size * queue->element_size); + + if (new_elements == NULL) { + return ENOMEM; + } + + if (queue->size) { + memcpy(((char*)new_elements) + (queue->element_size*queue->size), new_elements, queue->element_size*queue->size); + } + + queue->elements = new_elements; + queue->size = new_size; + } else { + return ENOSPC; + } + } + + memcpy( + ((char*) queue->elements) + (queue->element_size*((queue->start_index + queue->length) & (queue->size - 1))), + p_element, + queue->element_size + ); + + queue->length++; + + return 0; +} + +int queue_dequeue( + struct queue *queue, + void *element_out +) { + if (queue->length == 0) { + return EAGAIN; + } + + memcpy( + element_out, + ((char*) queue->elements) + (queue->element_size*queue->start_index), + queue->element_size + ); + + queue->start_index = (queue->start_index + 1) & (queue->size - 1); + queue->length--; + + return 0; +} + +int queue_peek( + struct queue *queue, + void **pelement_out +) { + if (queue->length == 0) { + if (pelement_out != NULL) { + *pelement_out = NULL; + } + return EAGAIN; + } + + if (pelement_out != NULL) { + *pelement_out = ((char*) queue->elements) + (queue->element_size*queue->start_index); + } + + return 0; +} + + +int cqueue_init( + struct concurrent_queue *queue, + size_t element_size, + size_t max_queue_size +) { + int ok; + + memset(queue, 0, sizeof(*queue)); + + ok = queue_init(&queue->queue, element_size, max_queue_size); + if (ok != 0) { + return ok; + } + + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->is_enqueueable, NULL); + pthread_cond_init(&queue->is_dequeueable, NULL); + + return 0; +} + +int cqueue_deinit(struct concurrent_queue *queue) { + queue_deinit(&queue->queue); + pthread_mutex_destroy(&queue->mutex); + pthread_cond_destroy(&queue->is_enqueueable); + pthread_cond_destroy(&queue->is_dequeueable); +} + +int cqueue_try_enqueue_locked( + struct concurrent_queue *queue, + const void *p_element +) { + return queue_enqueue(&queue->queue, p_element); +} + +int cqueue_enqueue_locked( + struct concurrent_queue *queue, + const void *p_element +) { + int ok; + + while (ok = queue_enqueue(&queue->queue, p_element), ok == ENOSPC) { + ok = pthread_cond_wait(&queue->is_enqueueable, &queue->mutex); + if (ok != 0) { + return ok; + } + } + + return ok; +} + +int cqueue_try_enqueue( + struct concurrent_queue *queue, + const void *p_element +) { + int ok; + + cqueue_lock(queue); + + ok = cqueue_try_enqueue_locked(queue, p_element); + if (ok != 0) { + cqueue_unlock(queue); + return ok; + } + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + +int cqueue_enqueue( + struct concurrent_queue *queue, + const void *p_element +) { + int ok; + + cqueue_lock(queue); + + ok = cqueue_enqueue_locked(queue, p_element); + if (ok != 0) { + cqueue_unlock(queue); + return ok; + } + + cqueue_unlock(queue); + + pthread_cond_signal(&queue->is_dequeueable); + + return 0; +} + + +int cqueue_try_dequeue_locked( + struct concurrent_queue *queue, + void *element_out +) { + int ok; + + ok = queue_dequeue(&queue->queue, element_out); + if (ok == 0) { + pthread_cond_signal(&queue->is_enqueueable); + } +} + +int cqueue_dequeue_locked( + struct concurrent_queue *queue, + void *element_out +) { + int ok; + + while (ok = queue_dequeue(&queue->queue, element_out), ok == EAGAIN) { + pthread_cond_wait(&queue->is_dequeueable, &queue->mutex); + } + + if (ok == 0) { + pthread_cond_signal(&queue->is_enqueueable); + } + + return ok; +} + +int cqueue_try_dequeue( + struct concurrent_queue *queue, + void *element_out +) { + int ok; + + cqueue_lock(queue); + + ok = cqueue_try_dequeue_locked(queue, element_out); + + cqueue_unlock(queue); + + return ok; +} + +int cqueue_dequeue( + struct concurrent_queue *queue, + void *element_out +) { + int ok; + + cqueue_lock(queue); + + ok = cqueue_dequeue_locked(queue, element_out); + + cqueue_unlock(queue); + + return ok; +} + + +int cqueue_peek_locked( + struct concurrent_queue *queue, + void **pelement_out +) { + return queue_peek(&queue->queue, pelement_out); +} \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c index 63fe4173..e4b70201 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -37,7 +37,7 @@ struct plane_reservation_data { bool is_reserved; }; -struct flutterpi_compositor flutterpi_compositor = { +struct compositor compositor = { .drmdev = NULL, .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), .planes = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), @@ -49,7 +49,7 @@ struct flutterpi_compositor flutterpi_compositor = { static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { struct view_cb_data *data; - for_each_pointer_in_cpset(&flutterpi_compositor.cbs, data) { + for_each_pointer_in_cpset(&compositor.cbs, data) { if (data->view_id == view_id) { return data; } @@ -61,9 +61,9 @@ static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { struct view_cb_data *data; - cpset_lock(&flutterpi_compositor.cbs); + cpset_lock(&compositor.cbs); data = get_cbs_for_view_id_locked(view_id); - cpset_unlock(&flutterpi_compositor.cbs); + cpset_unlock(&compositor.cbs); return data; } @@ -76,7 +76,7 @@ static void destroy_gbm_bo( struct drm_fb *fb = userdata; if (fb && fb->fb_id) - drmModeRmFB(drm.drmdev->fd, fb->fb_id); + drmModeRmFB(flutterpi.drm.drmdev->fd, fb->fb_id); free(fb); } @@ -114,7 +114,7 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { flags = DRM_MODE_FB_MODIFIERS; } - ok = drmModeAddFB2WithModifiers(drm.drmdev->fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); + ok = drmModeAddFB2WithModifiers(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); if (ok) { if (flags) @@ -124,7 +124,7 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); memset(offsets, 0, 16); - ok = drmModeAddFB2(drm.drmdev->fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); + ok = drmModeAddFB2(flutterpi.drm.drmdev->fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); } if (ok) { @@ -152,7 +152,7 @@ static int create_drm_rbo( eglGetError(); glGetError(); - fbo.egl_image = egl.createDRMImageMESA(egl.display, (const EGLint[]) { + fbo.egl_image = flutterpi.egl.createDRMImageMESA(flutterpi.egl.display, (const EGLint[]) { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA, @@ -164,7 +164,7 @@ static int create_drm_rbo( return EINVAL; } - egl.exportDRMImageMESA(egl.display, fbo.egl_image, NULL, &fbo.gem_handle, &fbo.gem_stride); + flutterpi.egl.exportDRMImageMESA(flutterpi.egl.display, fbo.egl_image, NULL, &fbo.gem_handle, &fbo.gem_stride); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[compositor] error getting handle & stride for DRM EGL Image, eglExportDRMImageMESA: %d\n", egl_error); return EINVAL; @@ -182,7 +182,7 @@ static int create_drm_rbo( return EINVAL; } - gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, fbo.egl_image); + flutterpi.gl.EGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, fbo.egl_image); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error binding DRM EGL Image to renderbuffer, glEGLImageTargetRenderbufferStorageOES: %ld\n", gl_error); return EINVAL; @@ -216,7 +216,7 @@ static int create_drm_rbo( // glBindFramebuffer(GL_FRAMEBUFFER, 0); ok = drmModeAddFB2( - drm.drmdev->fd, + flutterpi.drm.drmdev->fd, width, height, DRM_FORMAT_ARGB8888, @@ -301,12 +301,12 @@ static void destroy_drm_rbo( fprintf(stderr, "[compositor] error destroying OpenGL RBO, glDeleteRenderbuffers: 0x%08X\n", gl_error); } - ok = drmModeRmFB(drm.drmdev->fd, rbo->drm_fb_id); + ok = drmModeRmFB(flutterpi.drm.drmdev->fd, rbo->drm_fb_id); if (ok < 0) { fprintf(stderr, "[compositor] error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); } - eglDestroyImage(egl.display, rbo->egl_image); + eglDestroyImage(flutterpi.egl.display, rbo->egl_image); if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { fprintf(stderr, "[compositor] error destroying EGL image, eglDestroyImage: 0x%08X\n", egl_error); } @@ -353,7 +353,7 @@ int compositor_on_page_flip( static int create_window_surface_backing_store( const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, - struct flutterpi_compositor *compositor + struct compositor *compositor ) { struct backing_store_metadata *meta; int ok; @@ -376,8 +376,8 @@ static int create_window_surface_backing_store( meta->type = kWindowSurface; meta->window_surface.compositor = compositor; - meta->window_surface.current_front_bo = drm.current_bo; - meta->window_surface.gbm_surface = gbm.surface; + meta->window_surface.current_front_bo = NULL; + meta->window_surface.gbm_surface = flutterpi.gbm.surface; backing_store_out->type = kFlutterBackingStoreTypeOpenGL; backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; @@ -393,7 +393,7 @@ static int create_window_surface_backing_store( static int create_drm_fb_backing_store( const FlutterBackingStoreConfig *config, FlutterBackingStore *backing_store_out, - struct flutterpi_compositor *compositor + struct compositor *compositor ) { struct backing_store_metadata *meta; struct drm_fb_backing_store *inner; @@ -432,8 +432,8 @@ static int create_drm_fb_backing_store( } ok = create_drm_rbo( - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, + flutterpi.display.width, + flutterpi.display.height, inner->rbos + 0 ); if (ok != 0) { @@ -444,8 +444,8 @@ static int create_drm_fb_backing_store( } ok = create_drm_rbo( - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, + flutterpi.display.width, + flutterpi.display.height, inner->rbos + 1 ); if (ok != 0) { @@ -484,7 +484,9 @@ static bool create_backing_store( ) { int ok; - if (flutterpi_compositor.should_create_window_surface_backing_store) { + printf("create_backing_store\n"); + + if (compositor.should_create_window_surface_backing_store) { // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY // plane. That backing store isn't really a backing store at all, it's // FBO id is 0, so it's actually rendering to the window surface. @@ -498,7 +500,7 @@ static bool create_backing_store( return false; } - flutterpi_compositor.should_create_window_surface_backing_store = false; + compositor.should_create_window_surface_backing_store = false; } else { // After the primary plane backing store was created, // we only create overlay plane backing stores. I.e. @@ -531,6 +533,10 @@ static int collect_window_surface_backing_store( printf("collect_window_surface_backing_store\n"); + compositor_free_plane(inner->drm_plane_id); + + compositor.should_create_window_surface_backing_store = true; + return 0; } @@ -567,7 +573,7 @@ static bool collect_backing_store( return false; } - flutterpi_compositor.should_create_window_surface_backing_store = true; + compositor.should_create_window_surface_backing_store = true; } else if (meta->type == kDrmFb) { ok = collect_drm_fb_backing_store(renderer, meta); if (ok != 0) { @@ -606,18 +612,20 @@ static int present_window_surface_backing_store( drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_X", 0); drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) width) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) height) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_X", 0); drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_W", width); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_H", height); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_W", flutterpi.display.width); + drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_H", flutterpi.display.height); drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "zpos", zpos); // TODO: move this to the page flip handler. // We can only be sure the buffer can be released when the buffer swap // ocurred. - gbm_surface_release_buffer(backing_store->gbm_surface, backing_store->current_front_bo); + if (backing_store->current_front_bo != NULL) { + gbm_surface_release_buffer(backing_store->gbm_surface, backing_store->current_front_bo); + } backing_store->current_front_bo = (struct gbm_bo *) next_front_bo; return 0; @@ -659,18 +667,14 @@ static int present_drm_fb_backing_store( drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_X", 0); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) width) << 16); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) height) << 16); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_X", 0); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_W", width); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_H", height); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_W", flutterpi.display.width); + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_H", flutterpi.display.height); drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - - ok = drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "zpos", zpos); - if (ok != 0) { - fprintf(stderr, "Could not put zpos plane property: %s\n", strerror(ok)); - } + drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "zpos", zpos); return 0; } @@ -733,7 +737,7 @@ static bool present_layers_callback( void *user_data ) { struct plane_reservation_data *data; - struct flutterpi_compositor *compositor; + struct compositor *compositor; struct drmdev_atomic_req *req; struct view_cb_data *cb_data; uint32_t req_flags; @@ -741,6 +745,8 @@ static bool present_layers_callback( compositor = user_data; + printf("present_layers_callback\n"); + /* printf("[compositor] present_layers_callback(\n" " layers_count: %lu,\n" @@ -835,8 +841,8 @@ static bool present_layers_callback( // flush GL FlutterEngineTraceEventDurationBegin("eglSwapBuffers"); - eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); - eglSwapBuffers(egl.display, egl.surface); + eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); + eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); FlutterEngineTraceEventDurationEnd("eglSwapBuffers"); FlutterEngineTraceEventDurationBegin("drmdev_new_atomic_req"); @@ -1003,7 +1009,7 @@ static bool present_layers_callback( } } - eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); // all unused planes will be set inactive for_each_pointer_in_cpset(&compositor->planes, data) { @@ -1034,18 +1040,18 @@ int compositor_set_view_callbacks( ) { struct view_cb_data *entry; - cpset_lock(&flutterpi_compositor.cbs); + cpset_lock(&compositor.cbs); entry = get_cbs_for_view_id_locked(view_id); if (entry == NULL) { entry = calloc(1, sizeof(*entry)); if (!entry) { - cpset_unlock(&flutterpi_compositor.cbs); + cpset_unlock(&compositor.cbs); return ENOMEM; } - cpset_put_locked(&flutterpi_compositor.cbs, entry); + cpset_put_locked(&compositor.cbs, entry); } entry->view_id = view_id; @@ -1055,41 +1061,41 @@ int compositor_set_view_callbacks( entry->present = present; entry->userdata = userdata; - return cpset_unlock(&flutterpi_compositor.cbs); + return cpset_unlock(&compositor.cbs); } int compositor_remove_view_callbacks(int64_t view_id) { struct view_cb_data *entry; - cpset_lock(&flutterpi_compositor.cbs); + cpset_lock(&compositor.cbs); entry = get_cbs_for_view_id_locked(view_id); if (entry == NULL) { return EINVAL; } - cpset_remove_locked(&flutterpi_compositor.cbs, entry); + cpset_remove_locked(&compositor.cbs, entry); - return cpset_unlock(&flutterpi_compositor.cbs); + return cpset_unlock(&compositor.cbs); } /// DRM HARDWARE PLANE RESERVATION int compositor_reserve_plane(uint32_t *plane_id_out) { struct plane_reservation_data *data; - cpset_lock(&flutterpi_compositor.planes); + cpset_lock(&compositor.planes); - for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) { + for_each_pointer_in_cpset(&compositor.planes, data) { if (data->is_reserved == false) { data->is_reserved = true; - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); *plane_id_out = data->plane->plane->plane_id; return 0; } } - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); *plane_id_out = 0; return EBUSY; @@ -1098,17 +1104,17 @@ int compositor_reserve_plane(uint32_t *plane_id_out) { int compositor_free_plane(uint32_t plane_id) { struct plane_reservation_data *data; - cpset_lock(&flutterpi_compositor.planes); + cpset_lock(&compositor.planes); - for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) { + for_each_pointer_in_cpset(&compositor.planes, data) { if (data->plane->plane->plane_id == plane_id) { data->is_reserved = false; - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); return 0; } } - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); return EINVAL; } @@ -1117,26 +1123,27 @@ int compositor_initialize(struct drmdev *drmdev) { struct plane_reservation_data *data; const struct drm_plane *plane; - cpset_lock(&flutterpi_compositor.planes); + cpset_lock(&compositor.planes); for_each_plane_in_drmdev(drmdev, plane) { + data = calloc(1, sizeof (struct plane_reservation_data)); if (data == NULL) { - for_each_pointer_in_cpset(&flutterpi_compositor.planes, data) + for_each_pointer_in_cpset(&compositor.planes, data) free(data); - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); return ENOMEM; } data->plane = plane; data->is_reserved = false; - cpset_put_locked(&flutterpi_compositor.planes, data); + cpset_put_locked(&compositor.planes, data); } - cpset_unlock(&flutterpi_compositor.planes); + cpset_unlock(&compositor.planes); - flutterpi_compositor.drmdev = drmdev; + compositor.drmdev = drmdev; return 0; } @@ -1147,5 +1154,5 @@ const FlutterCompositor flutter_compositor = { .create_backing_store_callback = create_backing_store, .collect_backing_store_callback = collect_backing_store, .present_layers_callback = present_layers_callback, - .user_data = &flutterpi_compositor + .user_data = &compositor }; \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 5d7b3d36..2a5c5376 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -19,7 +19,8 @@ #include #include #include -#include +#include +#include #include #include @@ -31,6 +32,8 @@ #include #define GL_GLEXT_PROTOTYPES #include +#include +#include #include #include @@ -44,32 +47,62 @@ #include #include - -char* usage ="\ +const char const* usage ="\ flutter-pi - run flutter apps on your Raspberry Pi.\n\ \n\ USAGE:\n\ flutter-pi [options] [flutter engine options]\n\ \n\ OPTIONS:\n\ - -i Appends all files matching this glob pattern\n\ - to the list of input (touchscreen, mouse, touchpad)\n\ - devices. Brace and tilde expansion is enabled.\n\ - Every file that matches this pattern, but is not\n\ - a valid touchscreen / -pad or mouse is silently\n\ - ignored.\n\ - If no -i options are given, all files matching\n\ - \"/dev/input/event*\" will be used as inputs.\n\ - This should be what you want in most cases.\n\ - Note that you need to properly escape each glob pattern\n\ - you use as a parameter so it isn't implicitly expanded\n\ - by your shell.\n\ - \n\ - -h Show this help and exit.\n\ + -i, --input Appends all files matching this glob pattern to the\n\ + list of input (touchscreen, mouse, touchpad, \n\ + keyboard) devices. Brace and tilde expansion is \n\ + enabled.\n\ + Every file that matches this pattern, but is not\n\ + a valid touchscreen / -pad, mouse or keyboard is \n\ + silently ignored.\n\ + If no -i options are given, flutter-pi will try to\n\ + use all input devices assigned to udev seat0.\n\ + If that fails, or udev is not installed, flutter-pi\n\ + will fallback to using all devices matching \n\ + \"/dev/input/event*\" as inputs.\n\ + In most cases, there's no need to specify this\n\ + option.\n\ + Note that you need to properly escape each glob \n\ + pattern you use as a parameter so it isn't \n\ + implicitly expanded by your shell.\n\ + \n\ + -r, --release Run the app in release mode. This AOT snapshot\n\ + of the app (\"app.so\") must be located inside the\n\ + asset bundle directory.\n\ + \n\ + -p, --profile Run the app in profile mode. This runtime mode, too\n\ + depends on the AOT snapshot.\n\ + \n\ + -d, --debug Run the app in debug mode. This is the default.\n\ + \n\ + -o, --orientation Start the app in this orientation. Valid\n\ + for are: portrait_up, landscape_left,\n\ + portrait_down, landscape_right.\n\ + For more information about this orientation, see\n\ + the flutter docs for the \"DeviceOrientation\"\n\ + enum.\n\ + Only one of the --orientation and --rotation\n\ + options can be specified.\n\ + \n\ + -r, --rotation Start the app with this rotation. This is just an\n\ + alternative, more intuitive way to specify the\n\ + startup orientation. The angle is in degrees and\n\ + clock-wise.\n\ + Valid values are 0, 90, 180 and 270.\n\ + \n\ + -h Show this help and exit.\n\ \n\ EXAMPLES:\n\ flutter-pi -i \"/dev/input/event{0,1}\" -i \"/dev/input/event{2,3}\" /home/pi/helloworld_flutterassets\n\ flutter-pi -i \"/dev/input/mouse*\" /home/pi/helloworld_flutterassets\n\ + flutter-pi -o portrait_up ./flutter_assets\n\ + flutter-pi -r 90 ./flutter_assets\n\ flutter-pi /home/pi/helloworld_flutterassets\n\ \n\ SEE ALSO:\n\ @@ -83,137 +116,87 @@ SEE ALSO:\n\ https://github.com/flutter/engine/blob/master/shell/common/switches.h\n\ "; -/// width & height of the display in pixels -uint32_t width, height; - -/// physical width & height of the display in millimeters -/// the physical size can only be queried for HDMI displays (and even then, most displays will -/// probably return bogus values like 160mm x 90mm). -/// for DSI displays, the physical size of the official 7-inch display will be set in init_display. -/// init_display will only update width_mm and height_mm if they are set to zero, allowing you -/// to hardcode values for you individual display. -uint32_t width_mm = 0, height_mm = 0; -uint32_t refresh_rate; - -/// The pixel ratio used by flutter. -/// This is computed inside init_display using width_mm and height_mm. -/// flutter only accepts pixel ratios >= 1.0 -/// init_display will only update this value if it is equal to zero, -/// allowing you to hardcode values. -double pixel_ratio = 0.0; - -/// The current device orientation. -/// The initial device orientation is based on the width & height data from drm. -enum device_orientation orientation; - -/// The angle between the initial device orientation and the current device orientation in degrees. -/// (applied as a rotation to the flutter window in transformation_callback, and also -/// is used to determine if width/height should be swapped when sending a WindowMetrics event to flutter) -int rotation = 0; - -struct drm drm = {0}; -struct gbm gbm = {0}; -struct egl egl = {0}; -struct gl gl = {0}; - -struct { - char asset_bundle_path[240]; - 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; -} flutter = {0}; - -// Flutter VSync handles -// stored as a ring buffer. i_batons is the offset of the first baton (0 - 63) -// scheduled_frames - 1 is the number of total number of stored batons. -// (If 5 vsync events were asked for by the flutter engine, you only need to store 4 batons. -// The baton for the first one that was asked for would've been returned immediately.) -intptr_t batons[64]; -uint8_t i_batons = 0; -int scheduled_frames = 0; - -glob_t input_devices_glob; -size_t n_input_devices; -struct input_device *input_devices; -struct mousepointer_mtslot mousepointer; - -pthread_t io_thread_id; -pthread_t platform_thread_id; -struct flutterpi_task tasklist = { - .next = NULL, - .type = kFlutterTask, - .target_time = 0, - .task = {.runner = NULL, .task = 0} -}; -pthread_mutex_t tasklist_lock = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t task_added = PTHREAD_COND_INITIALIZER; - -FlutterEngine engine; -_Atomic bool engine_running = false; +struct flutterpi flutterpi; + +static int post_platform_task( + int (*callback)(void *userdata), + void *userdata +); + +static bool runs_platform_tasks_on_current_thread(void *userdata); /********************* * FLUTTER CALLBACKS * *********************/ +/// Called on some flutter internal thread when the flutter +/// rendering EGLContext should be made current. static bool on_make_current(void* userdata) { - if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.flutter_render_context) != EGL_TRUE) { - fprintf(stderr, "make_current: could not make the context current. eglMakeCurrent: %d\n", eglGetError()); + EGLint egl_error; + + eglGetError(); + + eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.flutter_render_context); + if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not make the flutter rendering EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); return false; } return true; } +/// Called on some flutter internal thread to +/// clear the EGLContext. static bool on_clear_current(void* userdata) { - if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { - fprintf(stderr, "clear_current: could not clear the current context.\n"); + EGLint egl_error; + + eglGetError(); + + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not clear the flutter EGL context. eglMakeCurrent: 0x%08X\n", egl_error); return false; } return true; } +/// Called on some flutter internal thread when the flutter +/// contents should be presented to the screen. +/// (Won't be called since we're supplying a compositor, +/// still needs to be present) static bool on_present(void *userdata) { - -} - -static void on_pageflip_event( - int fd, - unsigned int frame, - unsigned int sec, - unsigned int usec, - void *userdata -) { - FlutterEngineTraceEventInstant("pageflip"); - - compositor_on_page_flip(sec, usec); - - post_platform_task(&(struct flutterpi_task) { - .type = kVBlankReply, - .target_time = 0, - .vblank_ns = sec*1000000000ull + usec*1000ull, - }); + // no-op + return true; } +/// Called on some flutter internal thread to get the +/// GL FBO id flutter should render into +/// (Won't be called since we're supplying a compositor, +/// still needs to be present) static uint32_t fbo_callback(void* userdata) { return 0; } +/// Called on some flutter internal thread when the flutter +/// resource uploading EGLContext should be made current. static bool on_make_resource_current(void *userdata) { - if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl.flutter_resource_uploading_context) != EGL_TRUE) { - fprintf(stderr, "make_resource_current: could not make the resource context current. eglMakeCurrent: %d\n", eglGetError()); + EGLint egl_error; + + eglGetError(); + + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, flutterpi.egl.flutter_resource_uploading_context); + if (egl_error = eglGetError(), egl_error != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not make the flutter resource uploading EGL context current. eglMakeCurrent: 0x%08X\n", egl_error); return false; } - + return true; } +/// Cut a word from a string, mutating "string" static void cut_word_from_string( char* string, - char* word + const char* word ) { size_t word_length = strlen(word); char* word_in_str = strstr(string, word); @@ -232,6 +215,8 @@ static void cut_word_from_string( } } +/// An override for glGetString since the real glGetString +/// won't work. static const GLubyte *hacked_glGetString(GLenum name) { static GLubyte *extensions = NULL; @@ -315,6 +300,7 @@ static const GLubyte *hacked_glGetString(GLenum name) { return extensions; } +/// Called by flutter static void *proc_resolver( void* userdata, const char* name @@ -332,7 +318,7 @@ static void *proc_resolver( return NULL; // first detect if we're running on a VideoCore 4 / using the VC4 driver. - if ((is_VC4 == -1) && (is_VC4 = strcmp(egl.renderer, "VC4 V3D 2.1") == 0)) + if ((is_VC4 == -1) && (is_VC4 = strcmp(flutterpi.egl.renderer, "VC4 V3D 2.1") == 0)) printf( "detected VideoCore IV as underlying graphics chip, and VC4 as the driver.\n" "Reporting modified GL_EXTENSIONS string that doesn't contain non-working extensions.\n"); @@ -343,7 +329,7 @@ static void *proc_resolver( if ((address = dlsym(RTLD_DEFAULT, name)) || (address = eglGetProcAddress(name))) return address; - fprintf(stderr, "proc_resolver: could not resolve symbol \"%s\"\n", name); + fprintf(stderr, "[flutter-pi] proc_resolver: Could not resolve symbol \"%s\"\n", name); return NULL; } @@ -353,119 +339,117 @@ static void on_platform_message( void* userdata ) { int ok; - + ok = plugin_registry_on_platform_message((FlutterPlatformMessage *) message); if (ok != 0) { fprintf(stderr, "[flutter-pi] Error handling platform message. plugin_registry_on_platform_message: %s\n", strerror(ok)); } } -static void vsync_callback( - void* userdata, - intptr_t baton +/// Called on the main thread when a new frame request may have arrived. +/// Uses [drmCrtcGetSequence] or [FlutterEngineGetCurrentTime] to complete +/// the frame request. +static int on_execute_frame_request( + void *userdata ) { - post_platform_task(&(struct flutterpi_task) { - .type = kVBlankRequest, - .target_time = 0, - .baton = baton - }); -} + FlutterEngineResult result; + struct frame *peek; + int ok; -static FlutterTransformation transformation_callback(void *userdata) { - // report a transform based on the current device orientation. - static bool _transformsInitialized = false; - static FlutterTransformation rotate0, rotate90, rotate180, rotate270; - - static int counter = 0; - - if (!_transformsInitialized) { - rotate0 = (FlutterTransformation) { - .scaleX = 1, .skewX = 0, .transX = 0, - .skewY = 0, .scaleY = 1, .transY = 0, - .pers0 = 0, .pers1 = 0, .pers2 = 1 - }; - - rotate90 = FLUTTER_ROTATION_TRANSFORMATION(90); - rotate90.transX = width; - rotate180 = FLUTTER_ROTATION_TRANSFORMATION(180); - rotate180.transX = width; - rotate180.transY = height; - rotate270 = FLUTTER_ROTATION_TRANSFORMATION(270); - rotate270.transY = height; - - _transformsInitialized = true; - } - - if (rotation == 0) return rotate0; - else if (rotation == 90) return rotate90; - else if (rotation == 180) return rotate180; - else if (rotation == 270) return rotate270; - else return rotate0; -} + cqueue_lock(&flutterpi.frame_queue); + ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); + if (ok == 0) { + if (peek->state == kFramePending) { + uint64_t ns; + if (flutterpi.drm.platform_supports_get_sequence_ioctl) { + ns = 0; + ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); + if (ok < 0) { + perror("[flutter-pi] Couldn't get last vblank timestamp. drmCrtcGetSequence"); + cqueue_unlock(&flutterpi.frame_queue); + return errno; + } + } else { + ns = FlutterEngineGetCurrentTime(); + } + + result = FlutterEngineOnVsync( + flutterpi.flutter.engine, + peek->baton, + ns, + ns + (1000000000 / flutterpi.display.refresh_rate) + ); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); + cqueue_unlock(&flutterpi.frame_queue); + return EIO; + } -/************************ - * PLATFORM TASK-RUNNER * - ************************/ -static bool init_message_loop(void) { - platform_thread_id = pthread_self(); - return true; + peek->state = kFrameRendering; + } + } else if (ok == EAGAIN) { + // do nothing + } else if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); + cqueue_unlock(&flutterpi.frame_queue); + return ok; + } + + cqueue_unlock(&flutterpi.frame_queue); + + return 0; } -static bool message_loop(void) { - struct timespec abstargetspec; - uint64_t currenttime, abstarget; - intptr_t baton; +/// Called on some flutter internal thread to request a frame, +/// and also get the vblank timestamp of the pageflip preceding that frame. +static void on_frame_request( + void* userdata, + intptr_t baton +) { + sd_event_source *event_source; + struct frame *peek; + int ok; + + cqueue_lock(&flutterpi.frame_queue); - while (true) { - pthread_mutex_lock(&tasklist_lock); + ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); + if ((ok == 0) || (ok == EAGAIN)) { + bool reply_instantly = ok == EAGAIN; - // wait for a task to be inserted into the list - while (tasklist.next == NULL) - pthread_cond_wait(&task_added, &tasklist_lock); - - // wait for a task to be ready to be run - while (tasklist.target_time > (currenttime = FlutterEngineGetCurrentTime())) { - clock_gettime(CLOCK_REALTIME, &abstargetspec); - abstarget = abstargetspec.tv_nsec + abstargetspec.tv_sec*1000000000ull - currenttime; - abstargetspec.tv_nsec = abstarget % 1000000000; - abstargetspec.tv_sec = abstarget / 1000000000; - - pthread_cond_timedwait(&task_added, &tasklist_lock, &abstargetspec); + ok = cqueue_try_enqueue_locked(&flutterpi.frame_queue, &(struct frame) { + .state = kFramePending, + .baton = baton + }); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not enqueue frame request. cqueue_try_enqueue_locked: %s\n", strerror(ok)); + cqueue_unlock(&flutterpi.frame_queue); + return; } - struct flutterpi_task *task = tasklist.next; - tasklist.next = tasklist.next->next; + if (reply_instantly) { + post_platform_task( + on_execute_frame_request, + NULL + ); + } + } else if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not get peek of frame queue. cqueue_peek_locked: %s\n", strerror(ok)); + } - pthread_mutex_unlock(&tasklist_lock); - if (task->type == kVBlankRequest || task->type == kVBlankReply) { - intptr_t baton; - bool has_baton = false; - uint64_t ns; - - if (task->type == kVBlankRequest) { - if (scheduled_frames == 0) { - baton = task->baton; - has_baton = true; - drmCrtcGetSequence(drm.drmdev->fd, drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); - } else { - batons[(i_batons + (scheduled_frames-1)) & 63] = task->baton; - } - scheduled_frames++; - } else if (task->type == kVBlankReply) { - if (scheduled_frames > 1) { - baton = batons[i_batons]; - has_baton = true; - i_batons = (i_batons+1) & 63; - ns = task->vblank_ns; - } - scheduled_frames--; - } + cqueue_unlock(&flutterpi.frame_queue); +} - if (has_baton) { - FlutterEngineOnVsync(engine, baton, ns, ns + (1000000000ull / refresh_rate)); - } - +static FlutterTransformation on_get_transformation(void *userdata) { + printf("on_get_transformation\n"); + return FLUTTER_ROTZ_TRANSFORMATION((FlutterEngineGetCurrentTime() / 10000000) % 360); +} + + +/************************ + * PLATFORM TASK-RUNNER * + ************************/ +/* } else if (task->type == kUpdateOrientation) { rotation += ANGLE_FROM_ORIENTATION(task->orientation) - ANGLE_FROM_ORIENTATION(orientation); if (rotation < 0) rotation += 360; @@ -483,88 +467,255 @@ static bool message_loop(void) { .pixel_ratio = pixel_ratio }); - } else if (task->type == kSendPlatformMessage || task->type == kRespondToPlatformMessage) { - if (task->type == kSendPlatformMessage) { - FlutterEngineSendPlatformMessage( - engine, - &(const FlutterPlatformMessage) { - .struct_size = sizeof(FlutterPlatformMessage), - .channel = task->channel, - .message = task->message, - .message_size = task->message_size, - .response_handle = task->responsehandle - } - ); + } +*/ - free(task->channel); - } else if (task->type == kRespondToPlatformMessage) { - FlutterEngineSendPlatformMessageResponse( - engine, - task->responsehandle, - task->message, - task->message_size - ); - } +/// platform tasks +static int on_execute_platform_task( + sd_event_source *s, + void *userdata +) { + struct platform_task *task; + int ok; - free(task->message); - } else if (task->type == kRegisterExternalTexture) { - FlutterEngineRegisterExternalTexture(engine, task->texture_id); - } else if (task->type == kUnregisterExternalTexture) { - FlutterEngineUnregisterExternalTexture(engine, task->texture_id); - } else if (task->type == kMarkExternalTextureFrameAvailable) { - FlutterEngineMarkExternalTextureFrameAvailable(engine, task->texture_id); - } else if (task->type == kFlutterTask) { - if (FlutterEngineRunTask(engine, &task->task) != kSuccess) { - fprintf(stderr, "Error running platform task\n"); - return false; - } - } else if (task->type == kExit) { - printf("[flutter-pi] flutter requested application shutdown. not yet implemented\n"); - } else if (task->type == kGeneric) { - task->callback(task->userdata); + task = userdata; + ok = task->callback(task->userdata); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Error executing platform task: %s\n", strerror(ok)); + } + + free(task); + + sd_event_source_set_enabled(s, SD_EVENT_OFF); + sd_event_source_unrefp(&s); + + return 0; +} + +static int post_platform_task( + int (*callback)(void *userdata), + void *userdata +) { + struct platform_task *task; + int ok; + + task = malloc(sizeof *task); + if (task == NULL) { + return ENOMEM; + } + + task->callback = callback; + task->userdata = userdata; + + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_lock(&flutterpi.event_loop_mutex); + } + + ok = sd_event_add_defer( + flutterpi.event_loop, + NULL, + on_execute_platform_task, + task + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Error posting platform task to main loop. sd_event_add_defer: %s\n", strerror(-ok)); + ok = -ok; + goto fail_unlock_event_loop; + } + + if (pthread_self() != flutterpi.event_loop_thread) { + ok = write(flutterpi.wakeup_event_loop_fd, (uint8_t[8]) {0, 0, 0, 0, 0, 0, 0, 1}, 8); + if (ok < 0) { + perror("[flutter-pi] Error arming main loop for platform task. write"); + ok = errno; + goto fail_unlock_event_loop; } + } - free(task); + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); } - return true; + return 0; + + + fail_unlock_event_loop: + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + } + + return ok; +} + +/// timed platform tasks +static int on_execute_platform_task_with_time( + sd_event_source *s, + uint64_t usec, + void *userdata +) { + struct platform_task *task; + int ok; + + task = userdata; + ok = task->callback(task->userdata); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Error executing timed platform task: %s\n", strerror(ok)); + } + + free(task); + + sd_event_source_set_enabled(s, SD_EVENT_OFF); + sd_event_source_unrefp(&s); + + return 0; } -void post_platform_task(struct flutterpi_task *task) { - struct flutterpi_task *to_insert; +static int post_platform_task_with_time( + int (*callback)(void *userdata), + void *userdata, + uint64_t target_time_usec +) { + struct platform_task *task; + //sd_event_source *source; + int ok; + + task = malloc(sizeof *task); + if (task == NULL) { + return ENOMEM; + } + + task->callback = callback; + task->userdata = userdata; - to_insert = malloc(sizeof(*task)); - if (to_insert == NULL) { - return; + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_lock(&flutterpi.event_loop_mutex); + } + + ok = sd_event_add_time( + flutterpi.event_loop, + NULL, + CLOCK_MONOTONIC, + target_time_usec, + 1, + on_execute_platform_task_with_time, + task + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Error posting platform task to main loop. sd_event_add_time: %s\n", strerror(-ok)); + ok = -ok; + goto fail_unlock_event_loop; + } + + if (pthread_self() != flutterpi.event_loop_thread) { + ok = write(flutterpi.wakeup_event_loop_fd, (uint8_t[8]) {0, 0, 0, 0, 0, 0, 0, 1}, 8); + if (ok < 0) { + perror("[flutter-pi] Error arming main loop for platform task. write"); + ok = errno; + goto fail_unlock_event_loop; + } + } + + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + } + + return 0; + + + fail_unlock_event_loop: + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); } - memcpy(to_insert, task, sizeof(*task)); + return ok; +} + +/// flutter tasks +static int on_execute_flutter_task( + void *userdata +) { + FlutterEngineResult result; + FlutterTask *task; + + task = userdata; + + result = FlutterEngineRunTask(flutterpi.flutter.engine, task); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error running platform task. FlutterEngineRunTask: %d\n", result); + free(task); + return EINVAL; + } - pthread_mutex_lock(&tasklist_lock); - struct flutterpi_task* this = &tasklist; - while ((this->next) != NULL && (to_insert->target_time > this->next->target_time)) - this = this->next; + free(task); - to_insert->next = this->next; - this->next = to_insert; - pthread_mutex_unlock(&tasklist_lock); - pthread_cond_signal(&task_added); + return 0; } -static void flutter_post_platform_task( +static void on_post_flutter_task( FlutterTask task, uint64_t target_time, - void* userdata + void *userdata ) { - post_platform_task(&(struct flutterpi_task) { - .type = kFlutterTask, - .task = task, - .target_time = target_time - }); + sd_event_source *source; + FlutterTask *dup_task; + int ok; + + dup_task = malloc(sizeof *dup_task); + if (dup_task == NULL) { + return; + } + + *dup_task = task; + + ok = post_platform_task_with_time( + on_execute_flutter_task, + dup_task, + target_time / 1000 + ); + if (ok != 0) { + free(dup_task); + } } -static bool runs_platform_tasks_on_current_thread(void* userdata) { - return pthread_equal(pthread_self(), platform_thread_id) != 0; +/// platform messages +static int on_send_platform_message( + void *userdata +) { + struct platform_message *msg; + FlutterEngineResult result; + + msg = userdata; + + if (msg->is_response) { + result = FlutterEngineSendPlatformMessageResponse(flutterpi.flutter.engine, msg->target_handle, msg->message, msg->message_size); + } else { + result = FlutterEngineSendPlatformMessage( + flutterpi.flutter.engine, + &(FlutterPlatformMessage) { + .struct_size = sizeof(FlutterPlatformMessage), + .channel = msg->target_channel, + .message = msg->message, + .message_size = msg->message_size, + .response_handle = msg->response_handle + } + ); + } + + if (msg->message) { + free(msg->message); + } + + if (msg->is_response == false) { + free(msg->target_channel); + } + + free(msg); + + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error sending platform message. FlutterEngineSendPlatformMessage: %s\n", FLUTTER_RESULT_TO_STRING(result)); + } + + return 0; } int flutterpi_send_platform_message( @@ -573,12 +724,13 @@ int flutterpi_send_platform_message( size_t message_size, FlutterPlatformMessageResponseHandle *responsehandle ) { - struct flutterpi_task *task; + struct platform_message *msg; + FlutterEngineResult result; int ok; if (runs_platform_tasks_on_current_thread(NULL)) { - ok = kSuccess == FlutterEngineSendPlatformMessage( - engine, + result = FlutterEngineSendPlatformMessage( + flutterpi.flutter.engine, &(const FlutterPlatformMessage) { .struct_size = sizeof(FlutterPlatformMessage), .channel = channel, @@ -587,29 +739,42 @@ int flutterpi_send_platform_message( .response_handle = responsehandle } ); - - return ok? 0 : 1; + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error sending platform message. FlutterEngineSendPlatformMessage: %s\n", FLUTTER_RESULT_TO_STRING(result)); + return EIO; + } } else { - task = malloc(sizeof(struct flutterpi_task)); - if (task == NULL) return ENOMEM; + msg = calloc(1, sizeof *msg); + if (msg == NULL) { + return ENOMEM; + } - task->type = kSendPlatformMessage; - task->channel = strdup(channel); - task->responsehandle = responsehandle; + msg->is_response = false; + msg->target_channel = strdup(channel); + if (msg->target_channel == NULL) { + free(msg); + return ENOMEM; + } + msg->response_handle = responsehandle; + if (message && message_size) { - task->message_size = message_size; - task->message = memdup(message, message_size); - if (!task->message) { - free(task->channel); + msg->message_size = message_size; + msg->message = memdup(message, message_size); + if (msg->message == NULL) { + free(msg->target_channel); + free(msg); return ENOMEM; } } else { - task->message_size = 0; - task->message = 0; + msg->message = NULL; + msg->message_size = 0; } - post_platform_task(task); + ok = post_platform_task( + on_send_platform_message, + msg + ); } return 0; @@ -620,100 +785,381 @@ int flutterpi_respond_to_platform_message( const uint8_t *restrict message, size_t message_size ) { - struct flutterpi_task *task; + struct platform_message *msg; + FlutterEngineResult result; int ok; if (runs_platform_tasks_on_current_thread(NULL)) { - ok = kSuccess == FlutterEngineSendPlatformMessageResponse( - engine, + result = FlutterEngineSendPlatformMessageResponse( + flutterpi.flutter.engine, handle, message, message_size ); - - return ok? 0 : 1; + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error sending platform message response. FlutterEngineSendPlatformMessageResponse: %s\n", FLUTTER_RESULT_TO_STRING(result)); + return EIO; + } } else { - task = malloc(sizeof(struct flutterpi_task)); - if (task == NULL) return ENOMEM; - - task->type = kRespondToPlatformMessage; - task->channel = NULL; - task->responsehandle = handle; + msg = malloc(sizeof *msg); + if (msg == NULL) { + return ENOMEM; + } + msg->is_response = true; + msg->target_handle = handle; if (message && message_size) { - task->message_size = message_size; - task->message = memdup(message, message_size); - if (!task->message) return ENOMEM; + msg->message_size = message_size; + msg->message = memdup(message, message_size); + if (!msg->message) { + free(msg); + return ENOMEM; + } } else { - task->message_size = 0; - task->message = 0; + msg->message_size = 0; + msg->message = 0; } - post_platform_task(task); + ok = post_platform_task( + on_send_platform_message, + msg + ); + if (ok != 0) { + if (msg->message) { + free(msg->message); + } + free(msg); + } } return 0; } +static bool runs_platform_tasks_on_current_thread(void* userdata) { + return pthread_equal(pthread_self(), flutterpi.event_loop_thread) != 0; +} -/****************** - * INITIALIZATION * - ******************/ -static bool setup_paths(void) { - #define PATH_EXISTS(path) (access((path),R_OK)==0) +static int run_main_loop(void) { + int ok, evloop_fd; - if (!PATH_EXISTS(flutter.asset_bundle_path)) { - fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", flutter.asset_bundle_path); - return false; - } - - 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; + pthread_mutex_lock(&flutterpi.event_loop_mutex); + ok = sd_event_get_fd(flutterpi.event_loop); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not get fd for main event loop. sd_event_get_fd: %s\n", strerror(-ok)); + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + return -ok; } + pthread_mutex_unlock(&flutterpi.event_loop_mutex); - snprintf(flutter.icu_data_path, sizeof(flutter.icu_data_path), "/usr/lib/icudtl.dat"); + evloop_fd = ok; - if (!PATH_EXISTS(flutter.icu_data_path)) { - fprintf(stderr, "ICU Data file not find at %s.\n", flutter.icu_data_path); - return false; + { + fd_set fds; + int state; + FD_ZERO(&fds); + FD_SET(evloop_fd, &fds); + + const fd_set const_fds = fds; + + pthread_mutex_lock(&flutterpi.event_loop_mutex); + + do { + state = sd_event_get_state(flutterpi.event_loop); + switch (state) { + case SD_EVENT_INITIAL: + ok = sd_event_prepare(flutterpi.event_loop); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not prepare event loop. sd_event_prepare: %s\n", strerror(-ok)); + return -ok; + } + + break; + case SD_EVENT_ARMED: + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + + do { + fds = const_fds; + ok = select(evloop_fd + 1, &fds, &fds, &fds, NULL); + if ((ok < 0) && (errno != EINTR)) { + perror("[flutter-pi] Could not wait for event loop events. select"); + return errno; + } + } while ((ok < 0) && (errno == EINTR)); + + pthread_mutex_lock(&flutterpi.event_loop_mutex); + + ok = sd_event_wait(flutterpi.event_loop, 0); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not check for event loop events. sd_event_wait: %s\n", strerror(-ok)); + return -ok; + } + + break; + case SD_EVENT_PENDING: + ok = sd_event_dispatch(flutterpi.event_loop); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not dispatch event loop events. sd_event_dispatch: %s\n", strerror(-ok)); + return -ok; + } + + break; + case SD_EVENT_FINISHED: + printf("SD_EVENT_FINISHED\n"); + break; + default: + fprintf(stderr, "[flutter-pi] Unhandled event loop state: %d. Aborting\n", state); + abort(); + } + } while (state != SD_EVENT_FINISHED); + + pthread_mutex_unlock(&flutterpi.event_loop_mutex); } - //snprintf(drm.device, sizeof(drm.device), "/dev/dri/card0"); + pthread_mutex_destroy(&flutterpi.event_loop_mutex); + sd_event_unrefp(&flutterpi.event_loop); - return true; + return 0; +} - #undef PATH_EXISTS +static int on_wakeup_main_loop(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + uint8_t buffer[8]; + int ok; + + ok = read(fd, buffer, 8); + if (ok < 0) { + perror("[flutter-pi] Could not read mainloop wakeup userdata. read"); + return errno; + } + + return 0; } -static int load_gl_procs(void) { - LOAD_EGL_PROC(getPlatformDisplay); - LOAD_EGL_PROC(createPlatformWindowSurface); - LOAD_EGL_PROC(createPlatformPixmapSurface); - LOAD_EGL_PROC(createDRMImageMESA); - LOAD_EGL_PROC(exportDRMImageMESA); +static int init_main_loop(void) { + int ok, wakeup_fd; + + flutterpi.event_loop_thread = pthread_self(); + + wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (wakeup_fd < 0) { + perror("[flutter-pi] Could not create fd for waking up the main loop. eventfd"); + return errno; + } + + ok = sd_event_new(&flutterpi.event_loop); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not create main event loop. sd_event_new: %s\n", strerror(-ok)); + return -ok; + } + + ok = sd_event_add_io( + flutterpi.event_loop, + NULL, + wakeup_fd, + EPOLLIN, + on_wakeup_main_loop, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Error adding wakeup callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + sd_event_unrefp(&flutterpi.event_loop); + close(wakeup_fd); + return -ok; + } - LOAD_GL_PROC(EGLImageTargetTexture2DOES); - LOAD_GL_PROC(EGLImageTargetRenderbufferStorageOES); + flutterpi.wakeup_event_loop_fd = wakeup_fd; return 0; } -static int init_display() { - /********************** +/************************** + * DISPLAY INITIALIZATION * + **************************/ +/// Called on the main thread when a pageflip ocurred. +static void on_pageflip_event( + int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + void *userdata +) { + FlutterEngineResult result; + struct frame presented_frame, *peek; + int ok; + + FlutterEngineTraceEventInstant("pageflip"); + + cqueue_lock(&flutterpi.frame_queue); + + ok = cqueue_try_dequeue_locked(&flutterpi.frame_queue, &presented_frame); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not dequeue completed frame from frame queue: %s\n", strerror(ok)); + goto fail_unlock_frame_queue; + } + + ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); + if (ok == EAGAIN) { + // no frame queued after the one that was completed right now. + // do nothing here. + } else if (ok != 0) { + fprintf(stderr, "[flutter-pi] Could not get frame queue peek. cqueue_peek_locked: %s\n", strerror(ok)); + goto fail_unlock_frame_queue; + } else { + if (peek->state == kFramePending) { + uint64_t ns = (sec * 1000000000ll) + (usec * 1000ll); + + result = FlutterEngineOnVsync( + flutterpi.flutter.engine, + peek->baton, + ns, + ns + (1000000000ll / flutterpi.display.refresh_rate) + ); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not reply to frame request. FlutterEngineOnVsync: %s\n", FLUTTER_RESULT_TO_STRING(result)); + goto fail_unlock_frame_queue; + } + + peek->state = kFrameRendering; + } else { + fprintf(stderr, "[flutter-pi] frame queue in inconsistent state. aborting\n"); + abort(); + } + } + + cqueue_unlock(&flutterpi.frame_queue); + + ok = compositor_on_page_flip(sec, usec); + if (ok != 0) { + fprintf(stderr, "[flutter-pi] Error notifying compositor about page flip. compositor_on_page_flip: %s\n", strerror(ok)); + } + + return; + + + fail_unlock_frame_queue: + cqueue_unlock(&flutterpi.frame_queue); +} + +static int on_drm_fd_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + int ok; + + ok = drmHandleEvent(fd, &flutterpi.drm.evctx); + if (ok < 0) { + perror("[flutter-pi] Could not handle DRM event. drmHandleEvent"); + return -errno; + } + + return 0; +} + +int flutterpi_fill_view_properties( + bool has_orientation, + enum device_orientation orientation, + bool has_rotation, + int rotation +) { + enum device_orientation default_orientation = flutterpi.display.width >= flutterpi.display.height ? kLandscapeLeft : kPortraitUp; + + if (flutterpi.view.has_orientation) { + if (flutterpi.view.has_rotation == false) { + flutterpi.view.rotation = ANGLE_BETWEEN_ORIENTATIONS(default_orientation, flutterpi.view.orientation); + flutterpi.view.has_rotation = true; + } + } else if (flutterpi.view.has_rotation) { + for (int i = kPortraitUp; i <= kLandscapeRight; i++) { + if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == flutterpi.view.rotation) { + flutterpi.view.orientation = i; + flutterpi.view.has_orientation = true; + break; + } + } + } else { + flutterpi.view.orientation = default_orientation; + flutterpi.view.has_orientation = true; + flutterpi.view.rotation = 0; + flutterpi.view.has_rotation = true; + } + + if (has_orientation) { + flutterpi.view.rotation += ANGLE_BETWEEN_ORIENTATIONS(flutterpi.view.orientation, orientation); + if (flutterpi.view.rotation >= 360) { + flutterpi.view.rotation -= 360; + } + + flutterpi.view.orientation = orientation; + } else if (has_rotation) { + for (int i = kPortraitUp; i <= kLandscapeRight; i++) { + if (ANGLE_BETWEEN_ORIENTATIONS(default_orientation, i) == rotation) { + flutterpi.view.orientation = i; + flutterpi.view.rotation = rotation; + break; + } + } + } + + if ((flutterpi.view.rotation <= 45) || ((flutterpi.view.rotation >= 135) && (flutterpi.view.rotation <= 225)) || (flutterpi.view.rotation >= 315)) { + flutterpi.view.width = flutterpi.display.width; + flutterpi.view.height = flutterpi.display.height; + flutterpi.view.width_mm = flutterpi.display.width_mm; + flutterpi.view.height_mm = flutterpi.display.height_mm; + } else { + flutterpi.view.width = flutterpi.display.height; + flutterpi.view.height = flutterpi.display.width; + flutterpi.view.width_mm = flutterpi.display.height_mm; + flutterpi.view.height_mm = flutterpi.display.width_mm; + } + + FlutterTransformation center_view_around_origin = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.display.width - flutterpi.view.width, 0); + FlutterTransformation rotate = FLUTTER_ROTZ_TRANSFORMATION(flutterpi.view.rotation); + FlutterTransformation translate_to_fill_screen = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.display.width/2, flutterpi.display.height/2); + + FlutterTransformation multiplied; + multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(center_view_around_origin, rotate); + multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(multiplied, translate_to_fill_screen); + + flutterpi.view.view_to_display_transform = center_view_around_origin; + + FlutterTransformation inverse_center_view_around_origin = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.view.width/2, flutterpi.view.height/2); + FlutterTransformation inverse_rotate = FLUTTER_ROTZ_TRANSFORMATION(-flutterpi.view.rotation); + FlutterTransformation inverse_translate_to_fill_screen = FLUTTER_TRANSLATION_TRANSFORMATION(-flutterpi.display.width/2, -flutterpi.display.height/2); + + multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(inverse_translate_to_fill_screen, inverse_rotate); + multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(multiplied, inverse_center_view_around_origin); + + flutterpi.view.display_to_view_transform = inverse_center_view_around_origin; + + return 0; +} + +static int load_egl_gl_procs(void) { + LOAD_EGL_PROC(flutterpi, getPlatformDisplay); + LOAD_EGL_PROC(flutterpi, createPlatformWindowSurface); + LOAD_EGL_PROC(flutterpi, createPlatformPixmapSurface); + LOAD_EGL_PROC(flutterpi, createDRMImageMESA); + LOAD_EGL_PROC(flutterpi, exportDRMImageMESA); + + LOAD_GL_PROC(flutterpi, EGLImageTargetTexture2DOES); + LOAD_GL_PROC(flutterpi, EGLImageTargetRenderbufferStorageOES); + + return 0; +} + +static int init_display(void) { + /********************** * DRM INITIALIZATION * **********************/ const struct drm_connector *connector; const struct drm_encoder *encoder; const struct drm_crtc *crtc; const drmModeModeInfo *mode, *mode_iter; - struct drmdev *drmdev; drmDevicePtr devices[64]; EGLint egl_error; GLenum gl_error; int ok, area, num_devices; + + /********************** + * DRM INITIALIZATION * + **********************/ num_devices = drmGetDevices2(0, devices, sizeof(devices)/sizeof(*devices)); if (num_devices < 0) { @@ -722,6 +1168,7 @@ static int init_display() { } // find a GPU that has a primary node + flutterpi.drm.drmdev = NULL; for (int i = 0; i < num_devices; i++) { drmDevicePtr device; @@ -732,7 +1179,7 @@ static int init_display() { continue; } - ok = drmdev_new_from_path(&drmdev, device->nodes[DRM_NODE_PRIMARY]); + ok = drmdev_new_from_path(&flutterpi.drm.drmdev, device->nodes[DRM_NODE_PRIMARY]); if (ok != 0) { fprintf(stderr, "[flutter-pi] Could not create drmdev from device at \"%s\". Continuing.\n", device->nodes[DRM_NODE_PRIMARY]); continue; @@ -741,34 +1188,34 @@ static int init_display() { break; } - if (drmdev == NULL) { + if (flutterpi.drm.drmdev == NULL) { fprintf(stderr, "flutter-pi couldn't find a usable DRM device.\n" "Please make sure you've enabled the Fake-KMS driver in raspi-config.\n" "If you're not using a Raspberry Pi, please make sure there's KMS support for your graphics chip.\n"); - return false; + return ENOENT; } // find a connected connector - for_each_connector_in_drmdev(drmdev, connector) { + flutterpi.display.width_mm = 0; + flutterpi.display.height_mm = 0; + for_each_connector_in_drmdev(flutterpi.drm.drmdev, connector) { if (connector->connector->connection == DRM_MODE_CONNECTED) { // only update the physical size of the display if the values // are not yet initialized / not set with a commandline option - if ((width_mm == 0) && (height_mm == 0)) { - if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && - (connector->connector->mmWidth == 0) && - (connector->connector->mmHeight == 0)) - { - // if it's connected via DSI, and the width & height are 0, - // it's probably the official 7 inch touchscreen. - width_mm = 155; - height_mm = 86; - } else if ((connector->connector->mmHeight % 10 == 0) && - (connector->connector->mmWidth % 10 == 0)) { - // don't change anything. - } else { - width_mm = connector->connector->mmWidth; - height_mm = connector->connector->mmHeight; - } + if ((connector->connector->connector_type == DRM_MODE_CONNECTOR_DSI) && + (connector->connector->mmWidth == 0) && + (connector->connector->mmHeight == 0)) + { + // if it's connected via DSI, and the width & height are 0, + // it's probably the official 7 inch touchscreen. + flutterpi.display.width_mm = 155; + flutterpi.display.height_mm = 86; + } else if ((connector->connector->mmHeight % 10 == 0) && + (connector->connector->mmWidth % 10 == 0)) { + // don't change anything. + } else { + flutterpi.display.width_mm = connector->connector->mmWidth; + flutterpi.display.height_mm = connector->connector->mmHeight; } break; @@ -806,25 +1253,29 @@ static int init_display() { return EINVAL; } - width = mode->hdisplay; - height = mode->vdisplay; - refresh_rate = mode->vrefresh; - orientation = width >= height ? kLandscapeLeft : kPortraitUp; + flutterpi.display.width = mode->hdisplay; + flutterpi.display.height = mode->vdisplay; + flutterpi.display.refresh_rate = mode->vrefresh; - if (pixel_ratio == 0.0) { - if ((width_mm == 0) || (height_mm == 0)) { - fprintf( - stderr, - "[flutter-pi] WARNING: display didn't provide valid physical dimensions.\n" - " The device-pixel ratio will default to 1.0, which may not be the fitting device-pixel ratio for your display.\n" - ); - pixel_ratio = 1.0; - } else { - pixel_ratio = (10.0 * width) / (width_mm * 38.0); + if ((flutterpi.display.width_mm == 0) || (flutterpi.display.height_mm == 0)) { + fprintf( + stderr, + "[flutter-pi] WARNING: display didn't provide valid physical dimensions.\n" + " The device-pixel ratio will default to 1.0, which may not be the fitting device-pixel ratio for your display.\n" + ); + flutterpi.display.pixel_ratio = 1.0; + } else { + flutterpi.display.pixel_ratio = (10.0 * flutterpi.display.width) / (flutterpi.display.width_mm * 38.0); + + int horizontal_dpi = (int) (flutterpi.display.width / (flutterpi.display.width_mm / 25.4)); + int vertical_dpi = (int) (flutterpi.display.height / (flutterpi.display.height_mm / 25.4)); + + if (horizontal_dpi != vertical_dpi) { + fprintf(stderr, "[flutter-pi] WARNING: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); } } - for_each_encoder_in_drmdev(drmdev, encoder) { + for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { if (encoder->encoder->encoder_id == connector->connector->encoder_id) { break; } @@ -835,7 +1286,7 @@ static int init_display() { return EINVAL; } - for_each_crtc_in_drmdev(drmdev, crtc) { + for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { break; } @@ -846,9 +1297,56 @@ static int init_display() { return EINVAL; } - ok = drmdev_configure(drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); + ok = drmdev_configure(flutterpi.drm.drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); if (ok != 0) return ok; + // only enable vsync if the kernel supplies valid vblank timestamps + { + uint64_t ns = 0; + ok = drmCrtcGetSequence(flutterpi.drm.drmdev->fd, flutterpi.drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); + int _errno = errno; + + if ((ok == 0) && (ns != 0)) { + flutterpi.drm.platform_supports_get_sequence_ioctl = true; + } else { + flutterpi.drm.platform_supports_get_sequence_ioctl = false; + if (ok != 0) { + fprintf( + stderr, + "WARNING: Error getting last vblank timestamp. drmCrtcGetSequence: %s\n", + strerror(_errno) + ); + } else { + fprintf( + stderr, + "WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n" + ); + } + fprintf( + stderr, + " VSync will be disabled.\n" + " See https://github.com/ardera/flutter-pi/issues/38 for more info.\n" + ); + } + } + + memset(&flutterpi.drm.evctx, 0, sizeof(drmEventContext)); + flutterpi.drm.evctx.version = 4; + flutterpi.drm.evctx.page_flip_handler = on_pageflip_event; + + ok = sd_event_add_io( + flutterpi.event_loop, + &flutterpi.drm.drm_pageflip_event_source, + flutterpi.drm.drmdev->fd, + EPOLLIN | EPOLLHUP | EPOLLPRI, + on_drm_fd_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add DRM pageflip event listener. sd_event_add_io: %s\n", strerror(-ok)); + return -ok; + } + printf( "===================================\n" "display mode:\n" @@ -857,22 +1355,22 @@ static int init_display() { " physical size: %umm x %umm\n" " flutter device pixel ratio: %f\n" "===================================\n", - width, height, - refresh_rate, - width_mm, height_mm, - pixel_ratio + flutterpi.display.width, flutterpi.display.height, + flutterpi.display.refresh_rate, + flutterpi.display.width_mm, flutterpi.display.height_mm, + flutterpi.display.pixel_ratio ); /********************** * GBM INITIALIZATION * **********************/ - gbm.device = gbm_create_device(drmdev->fd); - gbm.format = DRM_FORMAT_ARGB8888; - gbm.surface = NULL; - gbm.modifier = DRM_FORMAT_MOD_LINEAR; + flutterpi.gbm.device = gbm_create_device(flutterpi.drm.drmdev->fd); + flutterpi.gbm.format = DRM_FORMAT_ARGB8888; + flutterpi.gbm.surface = NULL; + flutterpi.gbm.modifier = DRM_FORMAT_MOD_LINEAR; - gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); - if (gbm.surface == NULL) { + flutterpi.gbm.surface = gbm_surface_create_with_modifiers(flutterpi.gbm.device, flutterpi.display.width, flutterpi.display.height, flutterpi.gbm.format, &flutterpi.gbm.modifier, 1); + if (flutterpi.gbm.surface == NULL) { perror("[flutter-pi] Could not create GBM Surface. gbm_surface_create_with_modifiers"); return errno; } @@ -898,7 +1396,7 @@ static int init_display() { egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - ok = load_gl_procs(); + ok = load_egl_gl_procs(); if (ok != 0) { fprintf(stderr, "[flutter-pi] Could not load EGL / GL ES procedure addresses! error: %s\n", strerror(ok)); return ok; @@ -907,31 +1405,30 @@ static int init_display() { eglGetError(); #ifdef EGL_KHR_platform_gbm - egl.display = egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); + flutterpi.egl.display = flutterpi.egl.getPlatformDisplay(EGL_PLATFORM_GBM_KHR, flutterpi.gbm.device, NULL); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not get EGL display! eglGetPlatformDisplay: 0x%08X\n", egl_error); return EIO; } #else - egl.display = eglGetDisplay((void*) gbm.device); + flutterpi.egl.display = eglGetDisplay((void*) flutterpi.gbm.device); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not get EGL display! eglGetDisplay: 0x%08X\n", egl_error); return EIO; } #endif - eglInitialize(egl.display, &major, &minor); + eglInitialize(flutterpi.egl.display, &major, &minor); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Failed to initialize EGL! eglInitialize: 0x%08X\n", egl_error); return EIO; } - egl_exts_dpy = eglQueryString(egl.display, EGL_EXTENSIONS); + egl_exts_dpy = eglQueryString(flutterpi.egl.display, EGL_EXTENSIONS); - //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(" version: %s\n", eglQueryString(flutterpi.egl.display, EGL_VERSION)); + printf(" vendor: \"%s\"\n", eglQueryString(flutterpi.egl.display, EGL_VENDOR)); printf(" client extensions: \"%s\"\n", egl_exts_client); printf(" display extensions: \"%s\"\n", egl_exts_dpy); printf("===================================\n"); @@ -946,7 +1443,7 @@ static int init_display() { EGLConfig *configs; bool _found_matching_config = false; - eglGetConfigs(egl.display, NULL, 0, &count); + eglGetConfigs(flutterpi.egl.display, NULL, 0, &count); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not get the number of EGL framebuffer configurations. eglGetConfigs: 0x%08X\n", egl_error); return EIO; @@ -954,101 +1451,8 @@ static int init_display() { configs = malloc(count * sizeof(EGLConfig)); if (!configs) return ENOMEM; - - /* - eglGetConfigs(egl.display, configs, count * sizeof(EGLConfig), &count); - for (int i = 0; i < count; i++) { - EGLint value; - -# define GET_ATTRIB(attrib) eglGetConfigAttrib(egl.display, configs[i], attrib, &value) -# define PRINT_ATTRIB_STR(attrib, string) printf(" " #attrib ": %s,\n", string) -# define PRINT_ATTRIB(attrib, format, ...) printf(" " #attrib ": " format ",\n", __VA_ARGS__) -# define LOG_ATTRIB(attrib) \ - do { \ - eglGetConfigAttrib(egl.display, configs[i], attrib, &value); \ - printf(" " #attrib ": %d,\n", value); \ - } while (false) - printf("fb_config[%i] = {\n", i); - - GET_ATTRIB(EGL_COLOR_BUFFER_TYPE); - PRINT_ATTRIB_STR( - EGL_COLOR_BUFFER_TYPE, - value == EGL_RGB_BUFFER ? "EGL_RGB_BUFFER" : "EGL_LUMINANCE_BUFFER" - ); - - LOG_ATTRIB(EGL_RED_SIZE); - LOG_ATTRIB(EGL_GREEN_SIZE); - LOG_ATTRIB(EGL_BLUE_SIZE); - LOG_ATTRIB(EGL_ALPHA_SIZE); - LOG_ATTRIB(EGL_DEPTH_SIZE); - LOG_ATTRIB(EGL_STENCIL_SIZE); - LOG_ATTRIB(EGL_ALPHA_MASK_SIZE); - LOG_ATTRIB(EGL_LUMINANCE_SIZE); - - LOG_ATTRIB(EGL_BUFFER_SIZE); - - GET_ATTRIB(EGL_NATIVE_RENDERABLE); - PRINT_ATTRIB_STR( - EGL_NATIVE_RENDERABLE, - value ? "true" : "false" - ); - - LOG_ATTRIB(EGL_NATIVE_VISUAL_TYPE); - - GET_ATTRIB(EGL_NATIVE_VISUAL_ID); - PRINT_ATTRIB( - EGL_NATIVE_VISUAL_ID, - "%4s", - &value - ); - - LOG_ATTRIB(EGL_BIND_TO_TEXTURE_RGB); - LOG_ATTRIB(EGL_BIND_TO_TEXTURE_RGBA); - - GET_ATTRIB(EGL_CONFIG_CAVEAT); - PRINT_ATTRIB_STR( - EGL_CONFIG_CAVEAT, - value == EGL_NONE ? "EGL_NONE" : - value == EGL_SLOW_CONFIG ? "EGL_SLOW_CONFIG" : - value == EGL_NON_CONFORMANT_CONFIG ? "EGL_NON_CONFORMANT_CONFIG" : - "(?)" - ); - - LOG_ATTRIB(EGL_CONFIG_ID); - LOG_ATTRIB(EGL_CONFORMANT); - - LOG_ATTRIB(EGL_LEVEL); - - LOG_ATTRIB(EGL_MAX_PBUFFER_WIDTH); - LOG_ATTRIB(EGL_MAX_PBUFFER_HEIGHT); - LOG_ATTRIB(EGL_MAX_PBUFFER_PIXELS); - LOG_ATTRIB(EGL_MAX_SWAP_INTERVAL); - LOG_ATTRIB(EGL_MIN_SWAP_INTERVAL); - - LOG_ATTRIB(EGL_RENDERABLE_TYPE); - LOG_ATTRIB(EGL_SAMPLE_BUFFERS); - LOG_ATTRIB(EGL_SAMPLES); - - LOG_ATTRIB(EGL_SURFACE_TYPE); - - GET_ATTRIB(EGL_TRANSPARENT_TYPE); - PRINT_ATTRIB_STR( - EGL_TRANSPARENT_TYPE, - value == EGL_NONE ? "EGL_NONE" : - "EGL_TRANSPARENT_RGB" - ); - LOG_ATTRIB(EGL_TRANSPARENT_RED_VALUE); - LOG_ATTRIB(EGL_TRANSPARENT_GREEN_VALUE); - LOG_ATTRIB(EGL_TRANSPARENT_BLUE_VALUE); - - printf("}\n"); - -# undef LOG_ATTRIB - } - */ - - eglChooseConfig(egl.display, config_attribs, configs, count, &matched); + eglChooseConfig(flutterpi.egl.display, config_attribs, configs, count, &matched); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not query EGL framebuffer configurations with fitting attributes. eglChooseConfig: 0x%08X\n", egl_error); return EIO; @@ -1062,14 +1466,14 @@ static int init_display() { for (int i = 0; i < count; i++) { EGLint native_visual_id; - eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &native_visual_id); + eglGetConfigAttrib(flutterpi.egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &native_visual_id); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not query native visual ID of EGL config. eglGetConfigAttrib: 0x%08X\n", egl_error); continue; } - if (native_visual_id == gbm.format) { - egl.config = configs[i]; + if (native_visual_id == flutterpi.gbm.format) { + flutterpi.egl.config = configs[i]; _found_matching_config = true; break; } @@ -1081,51 +1485,53 @@ static int init_display() { return EIO; } - egl.root_context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); - if (egl.root_context == NULL) { - fprintf(stderr, "failed to create OpenGL ES root context\n"); - return false; + /**************************** + * OPENGL ES INITIALIZATION * + ****************************/ + flutterpi.egl.root_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, EGL_NO_CONTEXT, context_attribs); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not create OpenGL ES root context. eglCreateContext: 0x%08X\n", egl_error); + return EIO; } - egl.flutter_render_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); - if (egl.flutter_render_context == NULL) { - fprintf(stderr, "failed to create OpenGL ES context for flutter rendering\n"); - return false; + flutterpi.egl.flutter_render_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not create OpenGL ES context for flutter rendering. eglCreateContext: 0x%08X\n", egl_error); + return EIO; } - egl.flutter_resource_uploading_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); - if (egl.flutter_resource_uploading_context == NULL) { - fprintf(stderr, "failed to create OpenGL ES context for flutter resource uploads\n"); - return false; + flutterpi.egl.flutter_resource_uploading_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not create OpenGL ES context for flutter resource uploads. eglCreateContext: 0x%08X\n", egl_error); + return EIO; } - egl.compositor_context = eglCreateContext(egl.display, egl.config, egl.root_context, context_attribs); - if (egl.compositor_context == NULL) { - fprintf(stderr, "failed to create OpenGL ES context for compositor\n"); - return false; + flutterpi.egl.compositor_context = eglCreateContext(flutterpi.egl.display, flutterpi.egl.config, flutterpi.egl.root_context, context_attribs); + if ((egl_error = eglGetError()) != EGL_SUCCESS) { + fprintf(stderr, "[flutter-pi] Could not create OpenGL ES context for compositor. eglCreateContext: 0x%08X\n", egl_error); + return EIO; } - egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType) gbm.surface, NULL); + flutterpi.egl.surface = eglCreateWindowSurface(flutterpi.egl.display, flutterpi.egl.config, (EGLNativeWindowType) flutterpi.gbm.surface, NULL); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not create EGL window surface. eglCreateWindowSurface: 0x%08X\n", egl_error); return EIO; } - eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.root_context); + eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); if ((egl_error = eglGetError()) != EGL_SUCCESS) { fprintf(stderr, "[flutter-pi] Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); return EIO; } - egl.renderer = (char*) glGetString(GL_RENDERER); + flutterpi.egl.renderer = (char*) glGetString(GL_RENDERER); gl_exts = (char*) glGetString(GL_EXTENSIONS); - //printf("===================================\n"); printf("OpenGL ES 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", egl.renderer); + printf(" renderer: \"%s\"\n", flutterpi.egl.renderer); printf(" extensions: \"%s\"\n", gl_exts); printf("===================================\n"); @@ -1133,7 +1539,7 @@ static int init_display() { // to use the direct-rendering infrastructure; i.e. the open the devices inside /dev/dri/ // as read-write. flutter-pi must be run as root then. // sometimes it works fine without root, sometimes it doesn't. - if (strncmp(egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) { + if (strncmp(flutterpi.egl.renderer, "llvmpipe", sizeof("llvmpipe")-1) == 0) { printf("WARNING: Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer.\n" " Check that flutter-pi has permission to use the 3D graphics hardware,\n" " or try running it as root.\n" @@ -1141,64 +1547,49 @@ static int init_display() { " later on in the initialization.\n"); } - drm.evctx = (drmEventContext) { - .version = 4, - .vblank_handler = NULL, - .page_flip_handler = on_pageflip_event, - .page_flip_handler2 = NULL, - .sequence_handler = NULL - }; - - /* - printf("Swapping buffers...\n"); - eglSwapBuffers(egl.display, egl.surface); + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if ((egl_error = eglGetError()) != EGL_SUCCESS) { - fprintf(stderr, "[flutter-pi] Could not make OpenGL ES root context current to get OpenGL information. eglMakeCurrent: 0x%08X\n", egl_error); + fprintf(stderr, "[flutter-pi] Could not clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); return EIO; } - - printf("Locking front buffer...\n"); - drm.current_bo = gbm_surface_lock_front_buffer(gbm.surface); - - printf("getting new framebuffer for BO...\n"); - uint32_t current_fb_id = gbm_bo_get_drm_fb_id(drm.current_bo); - if (!current_fb_id) { - fprintf(stderr, "failed to get a new framebuffer BO\n"); - return false; - } - - ok = drmModeSetCrtc(drm.fd, drm.crtc_id, current_fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok < 0) { - fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); - return false; - } - */ - - eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if ((egl_error = eglGetError()) != EGL_SUCCESS) { - fprintf(stderr, "[flutter-pi] Could not clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); - return EIO; + /// miscellaneous initialization + /// initialize the compositor + ok = compositor_initialize(flutterpi.drm.drmdev); + if (ok != 0) { + return ok; } - ok = compositor_initialize(drmdev); + /// initialize the frame queue + ok = cqueue_init(&flutterpi.frame_queue, sizeof(struct frame), QUEUE_DEFAULT_MAX_SIZE); if (ok != 0) { return ok; } - drm.drmdev = drmdev; + /// We're starting without any rotation by default. + flutterpi_fill_view_properties(false, 0, false, 0); return 0; } -static void destroy_display(void) { - fprintf(stderr, "Deinitializing display not yet implemented\n"); -} - +/************************** + * FLUTTER INITIALIZATION * + **************************/ static int init_application(void) { + FlutterRendererConfig renderer_config = {0}; FlutterEngineResult engine_result; - uint64_t ns; - int ok, _errno; + FlutterProjectArgs project_args = {0}; + void *app_elf_handle; + int ok; + + if (flutterpi.flutter.runtime_mode == kRelease || flutterpi.flutter.runtime_mode == kProfile) { + fprintf( + stderr, + "flutter-pi doesn't support running apps in release or profile mode yet.\n" + "See https://github.com/ardera/flutter-pi/issues/65 for more info.\n" + ); + return EINVAL; + } ok = plugin_registry_init(); if (ok != 0) { @@ -1207,95 +1598,72 @@ static int init_application(void) { } // configure flutter rendering - memset(&flutter.renderer_config, 0, sizeof(FlutterRendererConfig)); - flutter.renderer_config.type = kOpenGL; - flutter.renderer_config.open_gl = (FlutterOpenGLRendererConfig) { - .struct_size = sizeof(FlutterOpenGLRendererConfig), - .make_current = on_make_current, - .clear_current = on_clear_current, - .present = on_present, - .fbo_callback = fbo_callback, - .make_resource_current = on_make_resource_current, - .gl_proc_resolver = proc_resolver, - .surface_transformation = transformation_callback, - .gl_external_texture_frame_callback = texreg_gl_external_texture_frame_callback + renderer_config = (FlutterRendererConfig) { + .type = kOpenGL, + .open_gl = { + .struct_size = sizeof(FlutterOpenGLRendererConfig), + .make_current = on_make_current, + .clear_current = on_clear_current, + .present = on_present, + .fbo_callback = fbo_callback, + .make_resource_current = on_make_resource_current, + .gl_proc_resolver = proc_resolver, + .surface_transformation = on_get_transformation, + .gl_external_texture_frame_callback = texreg_gl_external_texture_frame_callback, + } }; // configure the project - memset(&flutter.args, 0, sizeof(FlutterProjectArgs)); - flutter.args = (FlutterProjectArgs) { + project_args = (FlutterProjectArgs) { .struct_size = sizeof(FlutterProjectArgs), - .assets_path = flutter.asset_bundle_path, - .icu_data_path = flutter.icu_data_path, - .isolate_snapshot_data_size = 0, - .isolate_snapshot_data = NULL, - .isolate_snapshot_instructions_size = 0, - .isolate_snapshot_instructions = NULL, - .vm_snapshot_data_size = 0, + .assets_path = flutterpi.flutter.asset_bundle_path, + .icu_data_path = flutterpi.flutter.icu_data_path, + .command_line_argc = flutterpi.flutter.engine_argc, + .command_line_argv = (const char * const*) flutterpi.flutter.engine_argv, + .platform_message_callback = on_platform_message, .vm_snapshot_data = NULL, - .vm_snapshot_instructions_size = 0, + .vm_snapshot_data_size = 0, .vm_snapshot_instructions = NULL, - .command_line_argc = flutter.engine_argc, - .command_line_argv = flutter.engine_argv, - .platform_message_callback = on_platform_message, + .vm_snapshot_instructions_size = 0, + .isolate_snapshot_data = NULL, + .isolate_snapshot_data_size = 0, + .isolate_snapshot_instructions = NULL, + .isolate_snapshot_instructions_size = 0, + .root_isolate_create_callback = NULL, + .update_semantics_node_callback = NULL, + .update_semantics_custom_action_callback = NULL, + .persistent_cache_path = NULL, + .is_persistent_cache_read_only = false, + .vsync_callback = on_frame_request, + .custom_dart_entrypoint = NULL, .custom_task_runners = &(FlutterCustomTaskRunners) { .struct_size = sizeof(FlutterCustomTaskRunners), .platform_task_runner = &(FlutterTaskRunnerDescription) { .struct_size = sizeof(FlutterTaskRunnerDescription), .user_data = NULL, - .runs_task_on_current_thread_callback = &runs_platform_tasks_on_current_thread, - .post_task_callback = &flutter_post_platform_task + .runs_task_on_current_thread_callback = runs_platform_tasks_on_current_thread, + .post_task_callback = on_post_flutter_task } }, + .shutdown_dart_vm_when_done = true, .compositor = &flutter_compositor }; - // only enable vsync if the kernel supplies valid vblank timestamps - ns = 0; - ok = drmCrtcGetSequence(drm.drmdev->fd, drm.drmdev->selected_crtc->crtc->crtc_id, NULL, &ns); - _errno = errno; - - if ((ok == 0) && (ns != 0)) { - drm.disable_vsync = false; - flutter.args.vsync_callback = vsync_callback; - } else { - drm.disable_vsync = true; - if (ok != 0) { - fprintf( - stderr, - "WARNING: Could not get last vblank timestamp. %s\n", - strerror(_errno) - ); - } else { - fprintf( - stderr, - "WARNING: Kernel didn't return a valid vblank timestamp. (timestamp == 0)\n" - ); - } - fprintf( - stderr, - " VSync will be disabled.\n" - " See https://github.com/ardera/flutter-pi/issues/38 for more info.\n" - ); - } - // spin up the engine - engine_result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &flutter.renderer_config, &flutter.args, NULL, &engine); + engine_result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, NULL, &flutterpi.flutter.engine); if (engine_result != kSuccess) { - fprintf(stderr, "[flutter-pi] Could not run the flutter engine.\n"); + fprintf(stderr, "[flutter-pi] Could not run the flutter engine. FlutterEngineRun: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); return EINVAL; } - engine_running = true; - // update window size engine_result = FlutterEngineSendWindowMetricsEvent( - engine, + flutterpi.flutter.engine, &(FlutterWindowMetricsEvent) { .struct_size = sizeof(FlutterWindowMetricsEvent), - .width=width, - .height=height, - .pixel_ratio = pixel_ratio + .width = flutterpi.view.width, + .height = flutterpi.view.height, + .pixel_ratio = flutterpi.display.pixel_ratio } ); if (engine_result != kSuccess) { @@ -1306,383 +1674,244 @@ static int init_application(void) { return 0; } -static void destroy_application(void) { - FlutterEngineShutdown(engine); - plugin_registry_deinit(); +/************** + * USER INPUT * + **************/ +static int libinput_interface_on_open(const char *path, int flags, void *userdata) { + return open(path, flags | O_CLOEXEC); } -/**************** - * Input-Output * - ****************/ -static void init_io(void) { - int ok; - int n_flutter_slots = 0; - FlutterPointerEvent flutterevents[64] = {0}; - size_t i_flutterevent = 0; - - n_input_devices = 0; - input_devices = NULL; - - // add the mouse slot - mousepointer = (struct mousepointer_mtslot) { - .id = 0, - .flutter_slot_id = n_flutter_slots++, - .x = 0, .y = 0, - .phase = kCancel - }; - - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .phase = kAdd, - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .x = 0, - .y = 0, - .signal_kind = kFlutterPointerSignalKindNone, - .device_kind = kFlutterPointerDeviceKindMouse, - .device = 0, - .buttons = 0 - }; - - // go through all the given paths and add everything you can - for (int i=0; i < input_devices_glob.gl_pathc; i++) { - struct input_device dev = {0}; - uint32_t absbits[(ABS_CNT+31) /32] = {0}; - uint32_t relbits[(REL_CNT+31) /32] = {0}; - uint32_t keybits[(KEY_CNT+31) /32] = {0}; - uint32_t props[(INPUT_PROP_CNT+31) /32] = {0}; - - snprintf(dev.path, sizeof(dev.path), "%s", input_devices_glob.gl_pathv[i]); - - // first, try to open the event device. - dev.fd = open(dev.path, O_RDONLY); - if (dev.fd < 0) { - perror("\n error opening the input device"); - continue; - } - - // query name - ok = ioctl(dev.fd, EVIOCGNAME(sizeof(dev.name)), &dev.name); - if (ok != -1) ok = ioctl(dev.fd, EVIOCGID, &dev.input_id); - if (ok == -1) { - perror("\n could not query input device name / id: ioctl for EVIOCGNAME or EVIOCGID failed"); - goto close_continue; - } - - // query supported event codes (for EV_ABS, EV_REL and EV_KEY event types) - ok = ioctl(dev.fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits); - if (ok != -1) ok = ioctl(dev.fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits); - if (ok != -1) ok = ioctl(dev.fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits); - if (ok != -1) ok = ioctl(dev.fd, EVIOCGPROP(sizeof(props)), props); - if (ok == -1) { - perror(" could not query input device: ioctl for EVIOCGBIT(EV_ABS), EVIOCGBIT(EV_REL), EVIOCGBIT(EV_KEY) or EVIOCGPROP failed"); - goto close_continue; - } - - // check if this input device needs a mousepointer (true for mouse, touchpads) - dev.is_pointer = ISSET(props, INPUT_PROP_POINTER); - dev.is_direct = ISSET(props, INPUT_PROP_DIRECT); - if (!dev.is_pointer && !dev.is_direct) { - bool touch = ISSET(absbits, ABS_MT_SLOT) || ISSET(keybits, BTN_TOUCH); - bool touchpad = touch && ISSET(keybits, BTN_TOOL_FINGER); - bool touchscreen = touch && !touchpad; - bool mouse = !touch && (ISSET(relbits, REL_X) || ISSET(relbits, REL_Y)); - dev.is_pointer = touchpad || mouse; - dev.is_direct = touchscreen; - } - dev.kind = dev.is_pointer ? kFlutterPointerDeviceKindMouse : kFlutterPointerDeviceKindTouch; - - // check if the device emits ABS_X and ABS_Y events, and if yes - // query some calibration data. - if (ISSET(absbits, ABS_X) && ISSET(absbits, ABS_Y)) { - ok = ioctl(dev.fd, EVIOCGABS(ABS_X), &(dev.xinfo)); - if (ok != -1) ok = ioctl(dev.fd, EVIOCGABS(ABS_Y), &(dev.yinfo)); - if (ok == -1) { - perror(" could not query input_absinfo: ioctl for ECIOCGABS(ABS_X) or EVIOCGABS(ABS_Y) failed"); - goto close_continue; - } - } - - // check if the device is multitouch (so a multitouch touchscreen or touchpad) - if (ISSET(absbits, ABS_MT_SLOT)) { - struct input_absinfo slotinfo; - - // query the ABS_MT_SLOT absinfo - ok = ioctl(dev.fd, EVIOCGABS(ABS_MT_SLOT), &slotinfo); - if (ok == -1) { - perror(" could not query input_absinfo: ioctl for EVIOCGABS(ABS_MT_SLOT) failed"); - goto close_continue; - } - - // put the info in the input_device field - dev.n_mtslots = slotinfo.maximum + 1; - dev.i_active_mtslot = slotinfo.value; - } else { - // even if the device doesn't have multitouch support, - // i may need some space to store coordinates, for example - // to convert ABS_X/Y events into relative ones (no-multitouch touchpads for example) - dev.n_mtslots = 1; - dev.i_active_mtslot = 0; - } - - dev.mtslots = calloc(dev.n_mtslots, sizeof(struct mousepointer_mtslot)); - for (int j=0; j < dev.n_mtslots; j++) { - dev.mtslots[j].id = -1; - dev.mtslots[j].flutter_slot_id = n_flutter_slots++; - dev.mtslots[j].x = -1; - dev.mtslots[j].y = -1; - - if (dev.is_pointer) continue; - - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .phase = kAdd, - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .x = dev.mtslots[j].x == -1 ? 0 : dev.mtslots[j].x, - .y = dev.mtslots[j].y == -1 ? 0 : dev.mtslots[j].y, - .signal_kind = kFlutterPointerSignalKindNone, - .device_kind = dev.kind, - .device = dev.mtslots[j].flutter_slot_id, - .buttons = 0 - }; - } - - // if everything worked we expand the input_devices array and add our dev. - input_devices = realloc(input_devices, ++n_input_devices * sizeof(struct input_device)); - input_devices[n_input_devices-1] = dev; - continue; - - close_continue: - close(dev.fd); - dev.fd = -1; - } +static void libinput_interface_on_close(int fd, void *userdata) { + close(fd); +} - if (n_input_devices == 0) - printf("[flutter-pi] Warning: No input devices configured.\n"); +static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct libinput_event *event; + FlutterPointerEvent pointer_events[64]; + FlutterEngineResult result; + int n_pointer_events = 0; + int ok; - // configure the console - ok = console_make_raw(); - if (ok != 0) { - printf("[flutter-pi] warning: could not make stdin raw\n"); + ok = libinput_dispatch(flutterpi.input.libinput); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not dispatch libinput events. libinput_dispatch: %s\n", strerror(-ok)); + return -ok; } - console_flush_stdin(); - - // now send all the kAdd events to flutter. - ok = kSuccess == FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent); - if (!ok) fprintf(stderr, "[flutter-pi] error while sending initial mousepointer / multitouch slot information to flutter\n"); -} + while (event = libinput_get_event(flutterpi.input.libinput), event != NULL) { + enum libinput_event_type type = libinput_event_get_type(event); -static void on_evdev_input(fd_set fds, size_t n_ready_fds) { - struct input_event linuxevents[64]; - size_t n_linuxevents; - struct input_device *device; - struct mousepointer_mtslot *active_mtslot; - FlutterPointerEvent flutterevents[64] = {0}; - size_t i_flutterevent = 0; - int ok, i, j; - - while (n_ready_fds > 0) { - // find out which device got new data - i = 0; - while (!FD_ISSET(input_devices[i].fd, &fds)) i++; - device = &input_devices[i]; - active_mtslot = &device->mtslots[device->i_active_mtslot]; - - // read the input events, convert them to flutter pointer events - ok = read(input_devices[i].fd, linuxevents, sizeof(linuxevents)); - if (ok == -1) { - fprintf(stderr, "error reading input events from device \"%s\" with path \"%s\": %s\n", - input_devices[i].name, input_devices[i].path, strerror(errno)); - return; - } else if (ok == 0) { - fprintf(stderr, "reached EOF for input device \"%s\" with path \"%s\": %s\n", - input_devices[i].name, input_devices[i].path, strerror(errno)); - return; - } - n_ready_fds--; - n_linuxevents = ok / sizeof(struct input_event); - - // now go through all linux events and update the state and flutterevents array accordingly. - for (int i=0; i < n_linuxevents; i++) { - struct input_event *e = &linuxevents[i]; - - if (e->type == EV_REL) { - // pointer moved relatively in the X or Y direction - // for now, without a speed multiplier - - double relx = e->code == REL_X ? e->value : 0; - double rely = e->code == REL_Y ? e->value : 0; - - mousepointer.x += relx; - mousepointer.y += rely; - - // only change the current phase if we don't yet have one. - if ((mousepointer.phase == kCancel) && (relx != 0 || rely != 0)) - mousepointer.phase = device->active_buttons ? kMove : kHover; - - } else if (e->type == EV_ABS) { - - if (e->code == ABS_MT_SLOT) { + if (type == LIBINPUT_EVENT_DEVICE_ADDED) { + struct libinput_device *device = libinput_event_get_device(event); + + struct input_device_data *data = calloc(1, sizeof(*data)); + data->flutter_device_id_offset = flutterpi.input.next_unused_flutter_device_id; + + libinput_device_set_user_data(device, data); + + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kAdd, + .timestamp = FlutterEngineGetCurrentTime(), + .x = 0.0, + .y = 0.0, + .device = flutterpi.input.next_unused_flutter_device_id++, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = 0 + }; + } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + int touch_count = libinput_device_touch_get_touch_count(device); + + for (int i = 0; i < touch_count; i++) { + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kAdd, + .timestamp = FlutterEngineGetCurrentTime(), + .x = 0.0, + .y = 0.0, + .device = flutterpi.input.next_unused_flutter_device_id++, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindTouch, + .buttons = 0 + }; + } + } + } else if (LIBINPUT_EVENT_IS_TOUCH(type)) { + struct libinput_event_touch *touch_event = libinput_event_get_touch_event(event); - // select a new active mtslot. - device->i_active_mtslot = e->value; - active_mtslot = &device->mtslots[device->i_active_mtslot]; + struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); - } else if (e->code == ABS_MT_POSITION_X || e->code == ABS_X || e->code == ABS_MT_POSITION_Y || e->code == ABS_Y) { - double relx = 0, rely = 0; - - if (e->code == ABS_MT_POSITION_X || (e->code == ABS_X && device->n_mtslots == 1)) { - double newx = (e->value - device->xinfo.minimum) * width / (double) (device->xinfo.maximum - device->xinfo.minimum); - relx = active_mtslot->phase == kDown ? 0 : newx - active_mtslot->x; - active_mtslot->x = newx; - } else if (e->code == ABS_MT_POSITION_Y || (e->code == ABS_Y && device->n_mtslots == 1)) { - double newy = (e->value - device->yinfo.minimum) * height / (double) (device->yinfo.maximum - device->yinfo.minimum); - rely = active_mtslot->phase == kDown ? 0 : newy - active_mtslot->y; - active_mtslot->y = newy; - } + if ((type == LIBINPUT_EVENT_TOUCH_DOWN) || (type == LIBINPUT_EVENT_TOUCH_MOTION) || (type == LIBINPUT_EVENT_TOUCH_UP)) { + int slot = libinput_event_touch_get_slot(touch_event); + if (slot < 0) { + slot = 0; + } - // if the device is associated with the mouse pointer (touchpad), update that pointer. - if (relx != 0 || rely != 0) { - struct mousepointer_mtslot *slot = active_mtslot; + if ((type == LIBINPUT_EVENT_TOUCH_DOWN) || (type == LIBINPUT_EVENT_TOUCH_MOTION)) { + double x = libinput_event_touch_get_x_transformed(touch_event, flutterpi.display.width); + double y = libinput_event_touch_get_y_transformed(touch_event, flutterpi.display.height); - if (device->is_pointer) { - mousepointer.x += relx; - mousepointer.y += rely; - slot = &mousepointer; - } + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &x, &y); - if (slot->phase == kCancel) - slot->phase = device->active_buttons ? kMove : kHover; + FlutterPointerPhase phase; + if (type == LIBINPUT_EVENT_TOUCH_DOWN) { + phase = kDown; + } else if (type == LIBINPUT_EVENT_TOUCH_MOTION) { + phase = kMove; } - } else if ((e->code == ABS_MT_TRACKING_ID) && (active_mtslot->id == -1 || e->value == -1)) { - - // id -1 means no id, or no touch. one tracking id is equivalent one continuous touch contact. - bool before = device->active_buttons && true; - if (active_mtslot->id == -1) { - active_mtslot->id = e->value; - // only set active_buttons if a touch equals a kMove (not kHover, as it is for multitouch touchpads) - device->active_buttons |= (device->is_direct ? FLUTTER_BUTTON_FROM_EVENT_CODE(BTN_TOUCH) : 0); - } else { - active_mtslot->id = -1; - device->active_buttons &= ~(device->is_direct ? FLUTTER_BUTTON_FROM_EVENT_CODE(BTN_TOUCH) : 0); - } + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = phase, + .timestamp = libinput_event_touch_get_time_usec(touch_event), + .x = x, + .y = y, + .device = data->flutter_device_id_offset + slot, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindTouch, + .buttons = 0 + }; - if (!before != !device->active_buttons) - active_mtslot->phase = before ? kUp : kDown; + data->x = x; + data->y = y; + data->timestamp = libinput_event_touch_get_time_usec(touch_event); + } else { + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kUp, + .timestamp = libinput_event_touch_get_time_usec(touch_event), + .x = 0.0, + .y = 0.0, + .device = data->flutter_device_id_offset + slot, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindTouch, + .buttons = 0 + }; } - - } else if (e->type == EV_KEY) { - - // remember if some buttons were pressed before this update - bool before = device->active_buttons && true; - - // update the active_buttons bitmap - // only apply BTN_TOUCH to the active buttons if a touch really equals a pressed button (device->is_direct is set) - // is_direct is true for touchscreens, but not for touchpads; so BTN_TOUCH doesn't result in a kMove for touchpads - - glfw_key glfw_key = EVDEV_KEY_TO_GLFW_KEY(e->code); - if ((glfw_key != GLFW_KEY_UNKNOWN) && (glfw_key != 0)) { - glfw_key_action action; - switch (e->value) { - case 0: action = GLFW_RELEASE; break; - case 1: action = GLFW_PRESS; break; - case 2: action = GLFW_REPEAT; break; - default: action = -1; break; - } - - rawkb_on_keyevent(EVDEV_KEY_TO_GLFW_KEY(e->code), 0, action); - } else if (e->code != BTN_TOUCH || device->is_direct) { - if (e->value == 1) device->active_buttons |= FLUTTER_BUTTON_FROM_EVENT_CODE(e->code); - else device->active_buttons &= ~FLUTTER_BUTTON_FROM_EVENT_CODE(e->code); + } + } else if (LIBINPUT_EVENT_IS_POINTER(type)) { + struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event(event); + struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); + + if (type == LIBINPUT_EVENT_POINTER_MOTION) { + + } else if (type == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) { + double x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, flutterpi.display.width); + double y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, flutterpi.display.height); + + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &x, &y); + + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = data->buttons & kFlutterPointerButtonMousePrimary ? kMove : kHover, + .timestamp = libinput_event_pointer_get_time_usec(pointer_event), + .x = x, + .y = y, + .device = data->flutter_device_id_offset, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = data->buttons + }; + + data->x = x; + data->y = y; + data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); + } else if (type == LIBINPUT_EVENT_POINTER_BUTTON) { + uint32_t button = libinput_event_pointer_get_button(pointer_event); + enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); + + int64_t flutter_button = 0; + if (button == BTN_LEFT) { + flutter_button = kFlutterPointerButtonMousePrimary; + } else if (button == BTN_RIGHT) { + flutter_button = kFlutterPointerButtonMouseSecondary; + } else if (button == BTN_MIDDLE) { + flutter_button = kFlutterPointerButtonMouseMiddle; + } else if (button == BTN_BACK) { + flutter_button = kFlutterPointerButtonMouseBack; + } else if (button == BTN_FORWARD) { + flutter_button = kFlutterPointerButtonMouseForward; } - // check if the button state changed - // if yes, change the current pointer phase - if (!before != !device->active_buttons) - (device->is_pointer ? &mousepointer : active_mtslot) ->phase = before ? kUp : kDown; - - } else if ((e->type == EV_SYN) && (e->code == SYN_REPORT)) { - - // We can now summarise the updates we received from the evdev into a FlutterPointerEvent - // and put it in the flutterevents buffer. - - size_t n_slots = 0; - struct mousepointer_mtslot *slots; - - // if this is a pointer device, we don't care about the multitouch slots & only send the updated mousepointer. - if (device->is_pointer) { - slots = &mousepointer; - n_slots = 1; - } else if (device->is_direct) { - slots = device->mtslots; - n_slots = device->n_mtslots; + int64_t new_flutter_button_state = data->buttons; + if (button_state == LIBINPUT_BUTTON_STATE_RELEASED) { + new_flutter_button_state &= ~flutter_button; + } else { + new_flutter_button_state |= flutter_button; } - for (j = 0; j < n_slots; j++) { - - // we don't want to send an event to flutter if nothing changed. - if (slots[j].phase == kCancel) continue; - - // convert raw pixel coordinates to flutter pixel coordinates - // (raw pixel coordinates don't respect screen rotation) - double flutterx, fluttery; - if (rotation == 0) { - flutterx = slots[j].x; - fluttery = slots[j].y; - } else if (rotation == 90) { - flutterx = slots[j].y; - fluttery = width - slots[j].x; - } else if (rotation == 180) { - flutterx = width - slots[j].x; - fluttery = height - slots[j].y; - } else if (rotation == 270) { - flutterx = height - slots[j].y; - fluttery = slots[j].x; + if (new_flutter_button_state != data->buttons) { + FlutterPointerPhase phase; + if (new_flutter_button_state == 0) { + phase = kUp; + } else if (data->buttons == 0) { + phase = kDown; + } else { + phase = kMove; } - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), - .phase = slots[j].phase, - .timestamp = e->time.tv_sec*1000000 + e->time.tv_usec, - .x = flutterx, .y = fluttery, - .device = slots[j].flutter_slot_id, + .phase = phase, + .timestamp = libinput_event_pointer_get_time_usec(pointer_event), + .x = data->x, + .y = data->y, + .device = data->flutter_device_id_offset, .signal_kind = kFlutterPointerSignalKindNone, - .scroll_delta_x = 0, .scroll_delta_y = 0, - .device_kind = device->kind, - .buttons = device->active_buttons & 0xFF + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = new_flutter_button_state }; - slots[j].phase = kCancel; + data->buttons = new_flutter_button_state; } + } else if (type == LIBINPUT_EVENT_POINTER_AXIS) { + } } } - if (i_flutterevent == 0) return; - - // now, send the data to the flutter engine - // TODO: do this on the main thread - ok = kSuccess == FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent); - if (!ok) { - fprintf(stderr, "could not send pointer events to flutter engine\n"); + if (n_pointer_events > 0) { + result = FlutterEngineSendPointerEvent( + flutterpi.flutter.engine, + pointer_events, + n_pointer_events + ); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not add mouse as flutter input device. FlutterEngineSendPointerEvent: %s\n", FLUTTER_RESULT_TO_STRING(result)); + } } + + return 0; } -static void on_console_input(void) { +static int on_stdin_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { static char buffer[4096]; glfw_key key; char *cursor; char *c; int ok; - ok = read(STDIN_FILENO, buffer, sizeof(buffer)); + ok = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); if (ok == -1) { - perror("could not read from stdin"); - return; + perror("[flutter-pi] Could not read from stdin"); + return errno; } else if (ok == 0) { - fprintf(stderr, "warning: reached EOF for stdin\n"); - return; + fprintf(stderr, "[flutter-pi] WARNING: reached EOF for stdin\n"); + return EBADF; } buffer[ok] = '\0'; @@ -1697,113 +1926,213 @@ static void on_console_input(void) { // neither a char nor a (function) key. we don't know when // we can start parsing the buffer again, so just stop here break; - } + } } } -static void *io_loop(void *userdata) { - fd_set read_fds; - fd_set write_fds; - fd_set except_fds; - int n_ready_fds; - int nfds; +static int init_user_input(void) { + sd_event_source *libinput_event_source, *stdin_event_source; + struct libinput *libinput; + struct udev *udev; + int ok; - // put all file-descriptors in the `fds` fd set - nfds = 0; - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - FD_ZERO(&except_fds); + udev = udev_new(); + if (udev == NULL) { + perror("[flutter-pi] Could not create udev instance. udev_new"); + return errno; + } - for (int i = 0; i < n_input_devices; i++) { - FD_SET(input_devices[i].fd, &read_fds); - if (input_devices[i].fd + 1 > nfds) nfds = input_devices[i].fd + 1; + libinput = libinput_udev_create_context( + &(const struct libinput_interface) { + .open_restricted = libinput_interface_on_open, + .close_restricted = libinput_interface_on_close + }, + NULL, + udev + ); + if (libinput == NULL) { + perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); + udev_unref(udev); + return errno; } - FD_SET(drm.drmdev->fd, &read_fds); - if (drm.drmdev->fd + 1 > nfds) nfds = drm.drmdev->fd + 1; - - FD_SET(STDIN_FILENO, &read_fds); + ok = libinput_udev_assign_seat(libinput, "seat0"); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not assign udev seat to libinput instance. libinput_udev_assign_seat: %s\n", strerror(-ok)); + libinput_unref(libinput); + udev_unref(udev); + return -ok; + } + + ok = sd_event_add_io( + flutterpi.event_loop, + &libinput_event_source, + libinput_get_fd(libinput), + EPOLLIN | EPOLLRDHUP | EPOLLPRI, + on_libinput_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + libinput_unref(libinput); + udev_unref(udev); + return -ok; + } + + ok = sd_event_add_io( + flutterpi.event_loop, + &stdin_event_source, + STDIN_FILENO, + EPOLLIN, + on_stdin_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + sd_event_source_unrefp(&libinput_event_source); + libinput_unref(libinput); + udev_unref(udev); + return -ok; + } - fd_set const_read_fds = read_fds; - fd_set const_write_fds = write_fds; - fd_set const_except_fds = except_fds; + ok = 1; //console_make_raw(); + if (ok == 0) { + console_flush_stdin(); + } else { + fprintf(stderr, "[flutter-pi] WARNING: could not make stdin raw\n"); + sd_event_source_unrefp(&stdin_event_source); + } - while (engine_running) { - // wait for any device to offer new data, - // but only if no file descriptors have new data. - n_ready_fds = select(nfds, &read_fds, &write_fds, &except_fds, NULL); - - if (n_ready_fds == -1) { - perror("error while waiting for I/O"); - return NULL; - } else if (n_ready_fds == 0) { - continue; - } - - if (FD_ISSET(drm.drmdev->fd, &read_fds)) { - drmHandleEvent(drm.drmdev->fd, &drm.evctx); - FD_CLR(drm.drmdev->fd, &read_fds); - n_ready_fds--; - } - - if (FD_ISSET(STDIN_FILENO, &read_fds)) { - on_console_input(); - FD_CLR(STDIN_FILENO, &read_fds); - n_ready_fds--; - } + flutterpi.input.udev = udev; + flutterpi.input.libinput = libinput; + flutterpi.input.libinput_event_source = libinput_event_source; + flutterpi.input.stdin_event_source = stdin_event_source; - if (n_ready_fds > 0) { - on_evdev_input(read_fds, n_ready_fds); - } + return 0; +} - read_fds = const_read_fds; - write_fds = const_write_fds; - except_fds = const_except_fds; - } - return NULL; -} +static bool setup_paths(void) { + char *kernel_blob_path, *icu_data_path, *app_elf_path; + #define PATH_EXISTS(path) (access((path),R_OK)==0) -static bool run_io_thread(void) { - int ok = pthread_create(&io_thread_id, NULL, &io_loop, NULL); - if (ok != 0) { - fprintf(stderr, "couldn't create flutter-pi io thread: [%s]", strerror(ok)); + if (!PATH_EXISTS(flutterpi.flutter.asset_bundle_path)) { + fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", flutterpi.flutter.asset_bundle_path); return false; } + + asprintf(&kernel_blob_path, "%s/kernel_blob.bin", flutterpi.flutter.asset_bundle_path); + asprintf(&app_elf_path, "%s/app.so", flutterpi.flutter.asset_bundle_path); - ok = pthread_setname_np(io_thread_id, "io.flutter-pi"); - if (ok != 0) { - fprintf(stderr, "couldn't set name of flutter-pi io thread: [%s]", strerror(ok)); + if (flutterpi.flutter.runtime_mode == kDebug) { + if (!PATH_EXISTS(kernel_blob_path)) { + fprintf(stderr, "[flutter-pi] Could not find \"kernel.blob\" file inside \"%s\", which is required for debug mode.\n", flutterpi.flutter.asset_bundle_path); + return false; + } + } else { + if (!PATH_EXISTS(app_elf_path)) { + fprintf(stderr, "[flutter-pi] Could not find \"app.so\" file inside \"%s\", which is required for release and profile mode.\n", flutterpi.flutter.asset_bundle_path); + return false; + } + } + + asprintf(&icu_data_path, "/usr/lib/icudtl.dat"); + if (!PATH_EXISTS(icu_data_path)) { + fprintf(stderr, "[flutter-pi] Could not find \"icudtl.dat\" file inside \"/usr/lib/\".\n"); return false; } + flutterpi.flutter.kernel_blob_path = kernel_blob_path; + flutterpi.flutter.icu_data_path = icu_data_path; + flutterpi.flutter.app_elf_path = app_elf_path; + return true; -} + #undef PATH_EXISTS +} -static bool parse_cmd_args(int argc, char **argv) { +static bool parse_cmd_args(int argc, char **argv) { + glob_t input_devices_glob = {0}; bool input_specified = false; - int ok, opt, index = 0; - input_devices_glob = (glob_t) {0}; - - while ((opt = getopt(argc, (char *const *) argv, "+i:h")) != -1) { - index++; - switch(opt) { - case 'i': - input_specified = true; - glob(optarg, GLOB_BRACE | GLOB_TILDE | (input_specified ? GLOB_APPEND : 0), NULL, &input_devices_glob); - index++; - break; - case 'h': - default: - printf("%s", usage); + int ok, opt, longopt_index = 0, runtime_mode_int = kDebug; + + struct option long_options[] = { + {"release", no_argument, &runtime_mode_int, kRelease}, + {"profile", no_argument, &runtime_mode_int, kProfile}, + {"debug", no_argument, &runtime_mode_int, kDebug}, + {"input", required_argument, 0, 'i'}, + {"orientation", required_argument, 0, 'o'}, + {"rotation", required_argument, 0, 'r'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while (1) { + longopt_index = 0; + opt = getopt_long(argc, argv, "+rpdi:o:r:h", long_options, &longopt_index); + + if (opt == -1) { + break; + } else if (((opt == 0) && (longopt_index == 3)) || (opt == 'i')) { + glob(optarg, GLOB_BRACE | GLOB_TILDE | (input_specified ? GLOB_APPEND : 0), NULL, &input_devices_glob); + input_specified = true; + } else if (((opt == 0) && (longopt_index == 4)) || (opt == 'o')) { + if (STREQ(optarg, "portrait_up")) { + flutterpi.view.orientation = kPortraitUp; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "landscape_left")) { + flutterpi.view.orientation = kLandscapeLeft; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "portrait_down")) { + flutterpi.view.orientation = kPortraitDown; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "landscape_right")) { + flutterpi.view.orientation = kLandscapeRight; + flutterpi.view.has_orientation = true; + } else { + fprintf( + stderr, + "ERROR: Invalid argument for --orientation passed.\n" + "Valid values are \"portrait_up\", \"landscape_left\", \"portrait_down\", \"landscape_right\".\n" + "%s", + usage + ); + return false; + } + } else if (((opt == 0) && (longopt_index == 5)) || (opt == 'r')) { + errno = 0; + long rotation = strtol(optarg, NULL, 0); + if ((errno != 0) || ((rotation != 0) && (rotation != 90) && (rotation != 180) && (rotation != 270))) { + fprintf( + stderr, + "ERROR: Invalid argument for --rotation passed.\n" + "Valid values are 0, 90, 180, 270.\n" + "%s", + usage + ); return false; + } + + flutterpi.view.rotation = rotation; + } else { + printf("%s", usage); + return false; } } - if (!input_specified) - // user specified no input devices. use /dev/input/event*. + if (!input_specified) { + // user specified no input devices. use "/dev/input/event*"". glob("/dev/input/event*", GLOB_BRACE | GLOB_TILDE, NULL, &input_devices_glob); + } + + if (runtime_mode_int != kDebug) { + fprintf( + stderr, + "flutter-pi doesn't support running apps in release or profile mode yet.\n" + "See https://github.com/ardera/flutter-pi/issues/65 for more info.\n" + ); + return false; + } if (optind >= argc) { fprintf(stderr, "error: expected asset bundle path after options.\n"); @@ -1811,53 +2140,76 @@ static bool parse_cmd_args(int argc, char **argv) { return false; } - snprintf(flutter.asset_bundle_path, sizeof(flutter.asset_bundle_path), "%s", argv[optind]); + flutterpi.input.use_paths = input_specified; + flutterpi.flutter.asset_bundle_path = strdup(argv[optind]); + flutterpi.flutter.runtime_mode = (enum flutter_runtime_mode) runtime_mode_int; argv[optind] = argv[0]; - flutter.engine_argc = argc-optind; - flutter.engine_argv = (const char* const*) &(argv[optind]); + flutterpi.flutter.engine_argc = argc - optind; + flutterpi.flutter.engine_argv = argv + optind; return true; } -int main(int argc, char **argv) { +int init(int argc, char **argv) { int ok; - if (!parse_cmd_args(argc, argv)) { - return EXIT_FAILURE; + + ok = parse_cmd_args(argc, argv); + if (ok == false) { + return EINVAL; } - // check if asset bundle path is valid - if (!setup_paths()) { - return EXIT_FAILURE; + ok = setup_paths(); + if (ok == false) { + return EINVAL; } - if (!init_message_loop()) { - return EXIT_FAILURE; + ok = init_main_loop(); + if (ok != 0) { + return ok; } - - // initialize display + ok = init_display(); if (ok != 0) { - return EXIT_FAILURE; + return ok; } - - // initialize application + + ok = init_user_input(); + if (ok != 0) { + return ok; + } + ok = init_application(); + if (ok != 0) { + return ok; + } + + return 0; +} + +int run() { + return run_main_loop(); +} + +void deinit() { + return; +} + + +int main(int argc, char **argv) { + int ok; + + ok = init(argc, argv); if (ok != 0) { return EXIT_FAILURE; } - init_io(); - - // read input events - run_io_thread(); + ok = run(); + if (ok != 0) { + return EXIT_FAILURE; + } - // run message loop - message_loop(); + deinit(); - // exit - destroy_application(); - destroy_display(); - return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/platformchannel.c b/src/platformchannel.c index 53eae5a8..7459f392 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1010,43 +1010,64 @@ int platch_send(char *channel, struct platch_obj *object, enum platch_codec resp if (on_response) { handlerdata = malloc(sizeof(struct platch_msg_resp_handler_data)); - if (!handlerdata) return ENOMEM; + if (!handlerdata) { + return ENOMEM; + } handlerdata->codec = response_codec; handlerdata->on_response = on_response; handlerdata->userdata = userdata; - result = FlutterPlatformMessageCreateResponseHandle(engine, platch_on_response_internal, handlerdata, &response_handle); - if (result != kSuccess) return EINVAL; + result = FlutterPlatformMessageCreateResponseHandle(flutterpi.flutter.engine, platch_on_response_internal, handlerdata, &response_handle); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error create platform message response handle. FlutterPlatformMessageCreateResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); + goto fail_free_handlerdata; + } } - //printf("[platformchannel] sending platform message to flutter on channel \"%s\". message_size: %d, has response_handle? %s\n", channel, size, response_handle ? "yes" : "no"); - //printf(" message buffer: \""); - //for (int i = 0; i < size; i++) - // if (isprint(buffer[i])) printf("%c", buffer[i]); - // else printf("\\x%02X", buffer[i]); - //printf("\"\n"); - - result = FlutterEngineSendPlatformMessage( - engine, - & (const FlutterPlatformMessage) { - .struct_size = sizeof(FlutterPlatformMessage), - .channel = (const char*) channel, - .message = (const uint8_t*) buffer, - .message_size = (const size_t) size, - .response_handle = response_handle - } + ok = flutterpi_send_platform_message( + channel, + buffer, + size, + response_handle ); + if (ok != 0) { + goto fail_release_handle; + } if (on_response) { - result = FlutterPlatformMessageReleaseResponseHandle(engine, response_handle); - if (result != kSuccess) return EINVAL; + result = FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); + if (result != kSuccess) { + fprintf(stderr, "[flutter-pi] Error releasing platform message response handle. FlutterPlatformMessageReleaseResponseHandle: %s\n", FLUTTER_RESULT_TO_STRING(result)); + ok = EIO; + goto fail_free_buffer; + } } - if (object->codec != kBinaryCodec) + if (object->codec != kBinaryCodec) { free(buffer); + } - return (result == kSuccess) ? 0 : EINVAL; + return 0; + + + fail_release_handle: + if (on_response) { + FlutterPlatformMessageReleaseResponseHandle(flutterpi.flutter.engine, response_handle); + } + + fail_free_buffer: + if (object->codec != kBinaryCodec) { + free(buffer); + } + + fail_free_handlerdata: + if (on_response) { + free(handlerdata); + } + + fail_return_ok: + return ok; } int platch_call_std(char *channel, char *method, struct std_value *argument, platch_msg_resp_callback on_response, void *userdata) { @@ -1080,9 +1101,9 @@ int platch_respond(FlutterPlatformMessageResponseHandle *handle, struct platch_o ok = platch_encode(response, &buffer, &size); if (ok != 0) return ok; - result = FlutterEngineSendPlatformMessageResponse(engine, (const FlutterPlatformMessageResponseHandle*) handle, (const uint8_t*) buffer, size); + result = FlutterEngineSendPlatformMessageResponse(flutterpi.flutter.engine, (const FlutterPlatformMessageResponseHandle*) handle, (const uint8_t*) buffer, size); if (result != kSuccess) { - fprintf(stderr, "[platformchannel] Could not send platform message response. FlutterEngineSendPlatformMessageResponse: %d\n", result); + fprintf(stderr, "[platformchannel] Could not send platform message response. FlutterEngineSendPlatformMessageResponse: %s\n", FLUTTER_RESULT_TO_STRING(result)); } if (buffer != NULL) { diff --git a/src/pluginregistry.c b/src/pluginregistry.c index a52389c7..dbda3614 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -38,13 +38,14 @@ struct platch_obj_cb_data { platch_obj_recv_callback callback; void *userdata; }; -struct { + +struct plugin_registry { size_t n_plugins; struct flutterpi_plugin *plugins; // platch_obj callbacks struct concurrent_pointer_set platch_obj_cbs; -} pluginregistry; +} plugin_registry; /// array of plugins that are statically included in flutter-pi. struct flutterpi_plugin hardcoded_plugins[] = { @@ -75,7 +76,7 @@ struct flutterpi_plugin hardcoded_plugins[] = { static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel_locked(const char *channel) { struct platch_obj_cb_data *data; - for_each_pointer_in_cpset(&pluginregistry.platch_obj_cbs, data) { + for_each_pointer_in_cpset(&plugin_registry.platch_obj_cbs, data) { if (strcmp(data->channel, channel) == 0) { return data; } @@ -87,9 +88,9 @@ static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel_locked( static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel(const char *channel) { struct platch_obj_cb_data *data; - cpset_lock(&pluginregistry.platch_obj_cbs); + cpset_lock(&plugin_registry.platch_obj_cbs); data = plugin_registry_get_cb_data_by_channel_locked(channel); - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return data; } @@ -97,15 +98,15 @@ static struct platch_obj_cb_data *plugin_registry_get_cb_data_by_channel(const c int plugin_registry_init() { int ok; - pluginregistry.n_plugins = sizeof(hardcoded_plugins) / sizeof(*hardcoded_plugins); - pluginregistry.plugins = hardcoded_plugins; - pluginregistry.platch_obj_cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE); + plugin_registry.n_plugins = sizeof(hardcoded_plugins) / sizeof(*hardcoded_plugins); + plugin_registry.plugins = hardcoded_plugins; + plugin_registry.platch_obj_cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE); - for (int i = 0; i < pluginregistry.n_plugins; i++) { - if (pluginregistry.plugins[i].init != NULL) { - ok = pluginregistry.plugins[i].init(); + for (int i = 0; i < plugin_registry.n_plugins; i++) { + if (plugin_registry.plugins[i].init != NULL) { + ok = plugin_registry.plugins[i].init(); if (ok != 0) { - fprintf(stderr, "[plugin registry] Could not initialize plugin %s. init: %s\n", pluginregistry.plugins[i].name, strerror(ok)); + fprintf(stderr, "[plugin registry] Could not initialize plugin %s. init: %s\n", plugin_registry.plugins[i].name, strerror(ok)); return ok; } } @@ -119,16 +120,16 @@ int plugin_registry_on_platform_message(FlutterPlatformMessage *message) { struct platch_obj object; int ok; - cpset_lock(&pluginregistry.platch_obj_cbs); + cpset_lock(&plugin_registry.platch_obj_cbs); data = plugin_registry_get_cb_data_by_channel_locked(message->channel); if (data == NULL || data->callback == NULL) { - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle*) message->response_handle); } data_copy = *data; - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); ok = platch_decode((uint8_t*) message->message, message->message_size, data_copy.codec, &object); if (ok != 0) { @@ -155,11 +156,11 @@ int plugin_registry_set_receiver( struct platch_obj_cb_data *data; char *channel_dup; - cpset_lock(&pluginregistry.platch_obj_cbs); + cpset_lock(&plugin_registry.platch_obj_cbs); channel_dup = strdup(channel); if (channel_dup == NULL) { - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return ENOMEM; } @@ -168,11 +169,11 @@ int plugin_registry_set_receiver( data = calloc(1, sizeof *data); if (data == NULL) { free(channel_dup); - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return ENOMEM; } - cpset_put_locked(&pluginregistry.platch_obj_cbs, data); + cpset_put_locked(&plugin_registry.platch_obj_cbs, data); } data->channel = channel_dup; @@ -180,7 +181,7 @@ int plugin_registry_set_receiver( data->callback = callback; //data->userdata = userdata; - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return 0; } @@ -188,20 +189,20 @@ int plugin_registry_set_receiver( int plugin_registry_remove_receiver(const char *channel) { struct platch_obj_cb_data *data; - cpset_lock(&pluginregistry.platch_obj_cbs); + cpset_lock(&plugin_registry.platch_obj_cbs); data = plugin_registry_get_cb_data_by_channel_locked(channel); if (data == NULL) { - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return EINVAL; } - cpset_remove_locked(&pluginregistry.platch_obj_cbs, data); + cpset_remove_locked(&plugin_registry.platch_obj_cbs, data); free(data->channel); free(data); - cpset_unlock(&pluginregistry.platch_obj_cbs); + cpset_unlock(&plugin_registry.platch_obj_cbs); return 0; } @@ -211,24 +212,24 @@ int plugin_registry_deinit() { int ok; /// call each plugins 'deinit' - for (int i = 0; i < pluginregistry.n_plugins; i++) { - if (pluginregistry.plugins[i].deinit) { - ok = pluginregistry.plugins[i].deinit(); + for (int i = 0; i < plugin_registry.n_plugins; i++) { + if (plugin_registry.plugins[i].deinit) { + ok = plugin_registry.plugins[i].deinit(); if (ok != 0) { - fprintf(stderr, "[plugin registry] Could not deinitialize plugin %s. deinit: %s\n", pluginregistry.plugins[i].name, strerror(ok)); + fprintf(stderr, "[plugin registry] Could not deinitialize plugin %s. deinit: %s\n", plugin_registry.plugins[i].name, strerror(ok)); } } } - for_each_pointer_in_cpset(&pluginregistry.platch_obj_cbs, data) { - cpset_remove_locked(&pluginregistry.platch_obj_cbs, data); + for_each_pointer_in_cpset(&plugin_registry.platch_obj_cbs, data) { + cpset_remove_locked(&plugin_registry.platch_obj_cbs, data); if (data != NULL) { free(data->channel); free(data); } } - cpset_deinit(&pluginregistry.platch_obj_cbs); + cpset_deinit(&plugin_registry.platch_obj_cbs); return 0; } diff --git a/src/plugins/services.c b/src/plugins/services.c index 321fb864..6cf30eff 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -98,7 +98,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter } // if the list contains the current orientation, we just return and don't change the current orientation at all. - if (o == orientation) { + if (o == flutterpi.view.orientation) { return 0; } @@ -109,11 +109,21 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter // select the first one that is preferred by flutter. for (int i = kPortraitUp; i <= kLandscapeRight; i++) { if (preferred_orientations[i]) { - post_platform_task(&(struct flutterpi_task) { - .type = kUpdateOrientation, - .orientation = i, - .target_time = 0 + FlutterEngineResult result; + + flutterpi_fill_view_properties(true, i, false, 0); + + // send updated window metrics to flutter + result = FlutterEngineSendWindowMetricsEvent(flutterpi.flutter.engine, &(const FlutterWindowMetricsEvent) { + .struct_size = sizeof(FlutterWindowMetricsEvent), + .width = flutterpi.view.width, + .height = flutterpi.view.height, + .pixel_ratio = flutterpi.display.pixel_ratio }); + if (result != kSuccess) { + fprintf(stderr, "[services] Could not send updated window metrics to flutter. FlutterEngineSendWindowMetricsEvent: %s\n", FLUTTER_RESULT_TO_STRING(result)); + return platch_respond_error_json(responsehandle, "engine-error", "Could not send updated window metrics to flutter", NULL); + } return platch_respond_success_json(responsehandle, NULL); } @@ -171,10 +181,7 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter * systemNavigationBarIconBrightness: null / Brightness */ } else if (strcmp(object->method, "SystemNavigator.pop") == 0) { - post_platform_task(&(struct flutterpi_task) { - .type = kExit, - .target_time = 0, - }); + // do nothing } return platch_respond_not_implemented(responsehandle); diff --git a/src/plugins/video_player.c b/src/plugins/video_player.c index fb3800cd..7768f521 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/video_player.c @@ -1404,7 +1404,7 @@ static int on_create( return platch_respond_native_error_std(responsehandle, ENOMEM); } - ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_QUEUE_SIZE); + ok = cqueue_init(&mgr->task_queue, sizeof(struct omxplayer_mgr_task), CQUEUE_DEFAULT_MAX_SIZE); if (ok != 0) { goto fail_free_mgr; } @@ -1424,7 +1424,7 @@ static int on_create( ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { .type = kCreate, .responsehandle = responsehandle, - .orientation = rotation + .orientation = 0 //rotation }); if (ok != 0) { diff --git a/src/texture_registry.c b/src/texture_registry.c index 37270002..0e56bce1 100644 --- a/src/texture_registry.c +++ b/src/texture_registry.c @@ -202,7 +202,7 @@ int texreg_register_texture( return ok; } - engine_result = FlutterEngineRegisterExternalTexture(engine, tex_id); + engine_result = FlutterEngineRegisterExternalTexture(flutterpi.flutter.engine, tex_id); if (engine_result != kSuccess) { free(details); return EINVAL; @@ -216,7 +216,7 @@ int texreg_register_texture( int texreg_mark_texture_frame_available(int64_t texture_id) { FlutterEngineResult engine_result; - engine_result = FlutterEngineMarkExternalTextureFrameAvailable(engine, texture_id); + engine_result = FlutterEngineMarkExternalTextureFrameAvailable(flutterpi.flutter.engine, texture_id); if (engine_result != kSuccess) { return EINVAL; } @@ -233,7 +233,7 @@ int texreg_unregister_texture(int64_t texture_id) { return ok; } - engine_result = FlutterEngineUnregisterExternalTexture(engine, texture_id); + engine_result = FlutterEngineUnregisterExternalTexture(flutterpi.flutter.engine, texture_id); if (engine_result != kSuccess) { return EINVAL; } From 53e9ac93975a7b9ff486255c754abe814bb227e0 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 1 Aug 2020 15:58:29 +0200 Subject: [PATCH 09/14] - fixes - (untested) AOT support - made backing stores independent of drm planes - (untested, very early) mouse cursor support - additions in "collection" (non-concurrent pointer set) - added method in video player plugin for dart-side to check if it is present --- CMakeLists.txt | 2 +- Makefile | 3 +- include/collection.h | 316 ++-- include/compositor.h | 124 +- include/flutter-pi.h | 6 + include/modesetting.h | 15 + include/pluginregistry.h | 17 +- ...ideo_player.h => omxplayer_video_player.h} | 3 +- src/collection.c | 256 +++- src/compositor.c | 1269 +++++++++-------- src/flutter-pi.c | 194 ++- src/modesetting.c | 17 + src/platformchannel.c | 4 +- src/pluginregistry.c | 16 +- ...ideo_player.c => omxplayer_video_player.c} | 30 +- 15 files changed, 1438 insertions(+), 834 deletions(-) rename include/plugins/{video_player.h => omxplayer_video_player.h} (99%) rename src/plugins/{video_player.c => omxplayer_video_player.c} (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 064f7d7e..7e2d8854 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ set(FLUTTER_PI_SRC src/plugins/text_input.c src/plugins/raw_keyboard.c src/plugins/spidev.c - src/plugins/video_player.c + src/plugins/omxplayer_video_player.c ) if(GPIOD_FOUND) diff --git a/Makefile b/Makefile index 620e5020..3886ae92 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ REAL_LDFLAGS = \ -lpthread \ -ldl \ -lm \ + -rdynamic \ $(LDFLAGS) SOURCES = src/flutter-pi.c \ @@ -30,7 +31,7 @@ SOURCES = src/flutter-pi.c \ src/plugins/raw_keyboard.c \ src/plugins/gpiod.c \ src/plugins/spidev.c \ - src/plugins/video_player.c + src/plugins/omxplayer_video_player.c OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) diff --git a/include/collection.h b/include/collection.h index 2731f5d8..2242f157 100644 --- a/include/collection.h +++ b/include/collection.h @@ -25,13 +25,36 @@ struct concurrent_queue { struct queue queue; }; -struct concurrent_pointer_set { - pthread_mutex_t mutex; +struct pointer_set { + /** + * @brief The number of non-NULL pointers currently stored in @ref pointers. + */ size_t count_pointers; + + /** + * @brief The current size of the @ref pointers memory block, in pointers. + */ size_t size; + + /** + * @brief The actual memory where the pointers are stored. + */ void **pointers; + /** + * @brief The maximum size of the @ref pointers memory block, in pointers. + */ size_t max_size; + + /** + * @brief Whether this pointer_set is using static memory. + */ + bool is_static; +}; + +struct concurrent_pointer_set { + pthread_mutex_t mutex; + struct pointer_set set; }; #define QUEUE_DEFAULT_MAX_SIZE 64 @@ -56,15 +79,38 @@ struct concurrent_pointer_set { .queue = QUEUE_INITIALIZER(element_type, _max_queue_size) \ }) +#define PSET_DEFAULT_MAX_SIZE 64 + +#define PSET_INITIALIZER(_max_size) \ + ((struct pointer_set) { \ + .count_pointers = 0, \ + .size = 0, \ + .pointers = NULL, \ + .max_size = _max_size, \ + .is_static = false \ + }) + +#define PSET_INITIALIZER_STATIC(_storage, _size) \ + ((struct pointer_set) { \ + .count_pointers = 0, \ + .size = _size, \ + .pointers = _storage, \ + .max_size = _size, \ + .is_static = true \ + }) + #define CPSET_DEFAULT_MAX_SIZE 64 #define CPSET_INITIALIZER(_max_size) \ ((struct concurrent_pointer_set) { \ .mutex = PTHREAD_MUTEX_INITIALIZER, \ - .count_pointers = 0, \ - .size = 0, \ - .pointers = NULL, \ - .max_size = _max_size \ + .set = { \ + .count_pointers = 0, \ + .size = 0, \ + .pointers = NULL, \ + .max_size = _max_size, \ + .is_static = false \ + } \ }) @@ -157,203 +203,189 @@ int cqueue_peek_locked( void **pelement_out ); -static inline int cpset_init( - struct concurrent_pointer_set *const set, - const size_t max_size -) { - memset(set, 0, sizeof(*set)); +/* + * pointer set + */ +int pset_init( + struct pointer_set *set, + size_t max_size +); + +int pset_init_static( + struct pointer_set *set, + void **storage, + size_t size +); - pthread_mutex_init(&set->mutex, NULL); +void pset_deinit( + struct pointer_set *set +); + +int pset_put( + struct pointer_set *set, + void *pointer +); - set->count_pointers = 0; - set->size = 2; - set->pointers = (void**) calloc(2, sizeof(void*)); +bool pset_contains( + const struct pointer_set *set, + const void *pointer +); - set->max_size = max_size; +int pset_remove( + struct pointer_set *set, + const void *pointer +); - if (set->pointers == NULL) { - set->size = 0; - return ENOMEM; - } +static inline int pset_get_count_pointers( + const struct pointer_set *set +) { + return set->count_pointers; +} - return 0; +/** + * @brief Returns the size of the internal storage of set, in pointers. + */ +static inline int pset_get_storage_size( + const struct pointer_set *set +) { + return set->size; } -static inline int cpset_deinit(struct concurrent_pointer_set *const set) { - pthread_mutex_destroy(&set->mutex); +int pset_copy( + const struct pointer_set *src, + struct pointer_set *dest +); - if (set->pointers != NULL) { - free(set->pointers); - } +void pset_intersect( + struct pointer_set *src_dest, + const struct pointer_set *b +); - set->count_pointers = 0; - set->size = 0; - set->pointers = NULL; +int pset_union( + struct pointer_set *src_dest, + const struct pointer_set *b +); - set->max_size = 0; +int pset_subtract( + struct pointer_set *minuend_difference, + const struct pointer_set *subtrahend +); - return 0; -} +void *__pset_next_pointer( + struct pointer_set *set, + const void *pointer +); + +#define for_each_pointer_in_pset(set, pointer) for ((pointer) = __pset_next_pointer(set, NULL); (pointer) != NULL; (pointer) = __pset_next_pointer(set, (pointer))) + +/* + * concurrent pointer set + */ +int cpset_init( + struct concurrent_pointer_set *set, + size_t max_size +); + +void cpset_deinit(struct concurrent_pointer_set *set); -static inline int cpset_lock(struct concurrent_pointer_set *const set) { +static inline int cpset_lock(struct concurrent_pointer_set *set) { return pthread_mutex_lock(&set->mutex); } -static inline int cpset_unlock(struct concurrent_pointer_set *const set) { +static inline int cpset_unlock(struct concurrent_pointer_set *set) { return pthread_mutex_unlock(&set->mutex); } static inline int cpset_put_locked( - struct concurrent_pointer_set *const set, + struct concurrent_pointer_set *set, void *pointer ) { - size_t new_size; - int index; - - index = -1; - for (int i = 0; i < set->size; i++) { - if (set->pointers[i] && (pointer == set->pointers[i])) { - index = i; - break; - } - } - - if ((index == -1) && (set->size == set->count_pointers)) { - new_size = set->size ? set->size << 1 : 1; - - if (new_size < set->max_size) { - void **new_pointers = (void**) realloc(set->pointers, new_size * sizeof(void*)); - - if (new_pointers == NULL) { - return ENOMEM; - } - - memset(new_pointers + set->size, 0, (new_size - set->size) * sizeof(void*)); - - index = set->size; - - set->pointers = new_pointers; - set->size = new_size; - } else { - return ENOSPC; - } - } - - if (index == -1) { - while (set->pointers[++index]); - } - - set->pointers[index] = pointer; - - set->count_pointers++; - - return 0; + return pset_put(&set->set, pointer); } -static inline int cpset_put( - struct concurrent_pointer_set *const set, +static inline int cpset_put_( + struct concurrent_pointer_set *set, void *pointer ) { int ok; cpset_lock(set); - ok = cpset_put_locked(set, pointer); + ok = pset_put(&set->set, pointer); cpset_unlock(set); return ok; } static inline bool cpset_contains_locked( - struct concurrent_pointer_set *const set, - const void const *pointer + struct concurrent_pointer_set *set, + const void *pointer ) { - for (int i = 0; i < set->size; i++) { - if (set->pointers[i] && (set->pointers[i] == pointer)) { - return true; - } - } - - return false; + return pset_contains(&set->set, pointer); } -static inline bool cpset_contains( - struct concurrent_pointer_set *const set, - const void const *pointer +static inline bool cpset_contains_( + struct concurrent_pointer_set *set, + const void *pointer ) { bool result; - + cpset_lock(set); - result = cpset_contains_locked(set, pointer); + result = pset_contains(&set->set, pointer); cpset_unlock(set); return result; } - - -static inline void *cpset_remove_locked( - struct concurrent_pointer_set *const set, - const void const *pointer +static inline int cpset_remove_locked( + struct concurrent_pointer_set *set, + const void *pointer ) { - void *result; - size_t new_size; - int index; - - result = NULL; - - for (index = 0; index < set->size; index++) { - if (set->pointers[index] && (set->pointers[index] == pointer)) { - result = set->pointers[index]; - - set->pointers[index] = NULL; - set->count_pointers--; - - return result; - } - } - - return NULL; + return pset_remove(&set->set, pointer); } -static inline void *cpset_remove( - struct concurrent_pointer_set *const set, - const void const *pointer +static inline int cpset_remove_( + struct concurrent_pointer_set *set, + const void *pointer ) { - void *result; + int ok; cpset_lock(set); - result = cpset_remove_locked(set, pointer); + ok = cpset_remove_locked(set, pointer); cpset_unlock(set); - return result; + return ok; } -static inline void *__cpset_next_pointer( - struct concurrent_pointer_set *const set, - const void const *pointer +static inline int cpset_get_count_pointers_locked( + const struct concurrent_pointer_set *set ) { - int i = -1; - - if (pointer != NULL) { - for (i = 0; i < set->size; i++) { - if (set->pointers[i] == pointer) { - break; - } - } + return set->set.count_pointers; +} - if (i == set->size) return NULL; - } +/** + * @brief Returns the size of the internal storage of set, in pointers. + */ +static inline int cpset_get_storage_size_locked( + const struct concurrent_pointer_set *set +) { + return set->set.size; +} - for (i = i+1; i < set->size; i++) { - if (set->pointers[i]) { - return set->pointers[i]; - } - } +static inline int cpset_copy_into_pset_locked( + struct concurrent_pointer_set *src, + struct pointer_set *dest +) { + return pset_copy(&src->set, dest); +} - return NULL; +static inline void *__cpset_next_pointer_locked( + struct concurrent_pointer_set *set, + const void *pointer +) { + return __pset_next_pointer(&set->set, pointer); } -#define for_each_pointer_in_cpset(set, pointer) for ((pointer) = __cpset_next_pointer(set, NULL); (pointer) != NULL; (pointer) = __cpset_next_pointer(set, (pointer))) +#define for_each_pointer_in_cpset(set, pointer) for ((pointer) = __cpset_next_pointer_locked(set, NULL); (pointer) != NULL; (pointer) = __cpset_next_pointer_locked(set, (pointer))) static inline void *memdup(const void *restrict src, const size_t n) { void *__restrict__ dest; diff --git a/include/compositor.h b/include/compositor.h index 8c4df569..0889190d 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -56,19 +56,82 @@ typedef int (*platform_view_present_cb)( struct compositor { struct drmdev *drmdev; + + /** + * @brief Contains a struct for each existing platform view, containing the view id + * and platform view callbacks. + * + * @see compositor_set_view_callbacks compositor_remove_view_callbacks + */ struct concurrent_pointer_set cbs; + + /** + * @brief A set that contains a struct for each existing DRM primary/overlay plane, containing + * the DRM plane id and whether it's reserved. + * + * @see compositor_reserve_plane compositor_free_plane + */ struct concurrent_pointer_set planes; + + /** + * @brief Whether the compositor should invoke @ref rendertarget_gbm_new the next time + * flutter creates a backing store. Otherwise @ref rendertarget_nogbm_new is invoked. + * + * It's only possible to have at most one GBM-Surface backed backing store (== @ref rendertarget_gbm). So the first + * time @ref on_create_backing_store is invoked, a GBM-Surface backed backing store is returned and after that, + * only backing stores with @ref rendertarget_nogbm. + */ bool should_create_window_surface_backing_store; + + /** + * @brief Whether the display mode was already applied. (Resolution, Refresh rate, etc) + * If it wasn't already applied, it will be the first time @ref on_present_layers + * is invoked. + */ bool has_applied_modeset; + + FlutterCompositor flutter_compositor; + + /** + * @brief A cache of rendertargets that are not currently in use for + * any flutter layers and can be reused. + * + * Make sure to destroy all stale rendertargets before presentation so all the DRM planes + * that are reserved by any stale rendertargets get freed. + */ + struct concurrent_pointer_set stale_rendertargets; + + /** + * @brief Whether the mouse cursor is currently enabled and visible. + */ + bool is_cursor_enabled; + + struct { + bool is_enabled; + + int width; + int height; + int bpp; + int depth; + int pitch; + int size; + + uint32_t drm_fb_id; + uint32_t gem_bo_handle; + uint32_t *buffer; + int x, y; + } cursor; }; +/* struct window_surface_backing_store { struct compositor *compositor; struct gbm_surface *gbm_surface; struct gbm_bo *current_front_bo; uint32_t drm_plane_id; }; +*/ struct drm_rbo { EGLImage egl_image; @@ -83,6 +146,7 @@ struct drm_fb { uint32_t fb_id; }; +/* struct drm_fb_backing_store { struct compositor *compositor; @@ -108,6 +172,58 @@ struct backing_store_metadata { struct drm_fb_backing_store drm_fb; }; }; +*/ + +struct rendertarget_gbm { + struct gbm_surface *gbm_surface; + struct gbm_bo *current_front_bo; +}; + +/** + * @brief No-GBM Rendertarget. + * A type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. + */ +struct rendertarget_nogbm { + GLuint gl_fbo_id; + struct drm_rbo rbos[2]; + + /** + * @brief The index of the @ref drm_rbo in the @ref rendertarget_nogbm::rbos array that + * OpenGL is currently rendering into. + */ + int current_front_rbo; +}; + +struct rendertarget { + bool is_gbm; + + struct compositor *compositor; + + union { + struct rendertarget_gbm gbm; + struct rendertarget_nogbm nogbm; + }; + + GLuint gl_fbo_id; + + void (*destroy)(struct rendertarget *target); + int (*present)( + struct rendertarget *target, + struct drmdev_atomic_req *atomic_req, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos + ); +}; + +struct flutterpi_backing_store { + struct rendertarget *target; + FlutterBackingStore flutter_backing_store; + bool should_free_on_next_destroy; +}; extern const FlutterCompositor flutter_compositor; @@ -129,13 +245,9 @@ int compositor_remove_view_callbacks( int64_t view_id ); -int compositor_reserve_plane( - uint32_t *plane_id_out -); +int compositor_set_cursor_enabled(bool enabled); -int compositor_free_plane( - uint32_t plane_id -); +int compositor_set_cursor_pos(int x, int y); int compositor_initialize( struct drmdev *drmdev diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 240e5328..2ee31ca2 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -97,6 +97,12 @@ enum device_orientation { .pers1 = a.pers0 * b.skewX + a.pers1 * b.scaleY + a.pers2 * b.pers1, \ .pers2 = a.pers0 * b.transX + a.pers1 * b.transY + a.pers2 * b.pers2}) +#define FLUTTER_ADDED_TRANSFORMATIONS(a, b) ((FlutterTransformation) \ + {.scaleX = a.scaleX + b.scaleX, .skewX = a.skewX + b.skewX, .transX = a.transX + b.transX, \ + .skewY = a.skewY + b.skewY, .scaleY = a.scaleY + b.scaleY, .transY = a.transY + b.transY, \ + .pers0 = a.pers0 + b.pers0, .pers1 = a.pers1 + b.pers1, .pers2 = a.pers2 + b.pers2 \ + }) + static inline void apply_flutter_transformation( const FlutterTransformation t, double *px, diff --git a/include/modesetting.h b/include/modesetting.h index 2e17226e..1c022c75 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -7,6 +7,8 @@ #include #include +#include + struct drm_connector { drmModeConnector *connector; drmModeObjectProperties *props; @@ -24,6 +26,7 @@ struct drm_crtc { }; struct drm_plane { + int type; drmModePlane *plane; drmModeObjectProperties *props; drmModePropertyRes **props_info; @@ -60,6 +63,9 @@ struct drmdev { struct drmdev_atomic_req { struct drmdev *drmdev; drmModeAtomicReq *atomic_req; + + void *available_planes_storage[32]; + struct pointer_set available_planes; }; int drmdev_new_from_fd( @@ -113,6 +119,13 @@ int drmdev_atomic_req_put_modeset_props( uint32_t *flags ); +inline static int drmdev_atomic_req_reserve_plane( + struct drmdev_atomic_req *req, + struct drm_plane *plane +) { + return pset_remove(&req->available_planes, plane); +} + int drmdev_atomic_req_commit( struct drmdev_atomic_req *req, uint32_t flags, @@ -194,4 +207,6 @@ inline static drmModeModeInfo *__next_mode(const struct drm_connector *connector #define for_each_mode_in_connector(connector, mode) for (mode = __next_mode(connector, NULL); mode != NULL; mode = __next_mode(connector, mode)) +#define for_each_unreserved_plane_in_atomic_req(atomic_req, plane) for_each_pointer_in_pset(&(atomic_req)->available_planes, plane) + #endif \ No newline at end of file diff --git a/include/pluginregistry.h b/include/pluginregistry.h index 97fd13f7..3d009a48 100644 --- a/include/pluginregistry.h +++ b/include/pluginregistry.h @@ -44,8 +44,11 @@ struct flutterpi_plugin { }; -int plugin_registry_init(); -int plugin_registry_on_platform_message(FlutterPlatformMessage *message); +int plugin_registry_init(void); + +int plugin_registry_on_platform_message( + FlutterPlatformMessage *message +); /// Sets the callback that should be called when a platform message arrives on channel "channel", /// and the codec used to automatically decode the platform message. @@ -56,8 +59,14 @@ int plugin_registry_set_receiver( platch_obj_recv_callback callback ); -int plugin_registry_remove_receiver(const char *channel); +int plugin_registry_remove_receiver( + const char *channel +); + +bool plugin_registry_is_plugin_present( + const char *plugin_name +); -int plugin_registry_deinit(); +int plugin_registry_deinit(void); #endif \ No newline at end of file diff --git a/include/plugins/video_player.h b/include/plugins/omxplayer_video_player.h similarity index 99% rename from include/plugins/video_player.h rename to include/plugins/omxplayer_video_player.h index bbb99320..6c1546a9 100644 --- a/include/plugins/video_player.h +++ b/include/plugins/omxplayer_video_player.h @@ -5,9 +5,10 @@ #include #include #include - #include + #include +#include #include diff --git a/src/collection.c b/src/collection.c index 223cc93c..8d814e19 100644 --- a/src/collection.c +++ b/src/collection.c @@ -207,7 +207,6 @@ int cqueue_enqueue( return 0; } - int cqueue_try_dequeue_locked( struct concurrent_queue *queue, void *element_out @@ -267,10 +266,263 @@ int cqueue_dequeue( return ok; } - int cqueue_peek_locked( struct concurrent_queue *queue, void **pelement_out ) { return queue_peek(&queue->queue, pelement_out); +} + + +int pset_init( + struct pointer_set *set, + size_t max_size +) { + void **storage = (void**) calloc(2, sizeof(void*)); + if (storage == NULL) { + return ENOMEM; + } + + memset(set, 0, sizeof(*set)); + + set->count_pointers = 0; + set->size = 2; + set->pointers = storage; + set->max_size = max_size; + set->is_static = false; + + return 0; +} + +int pset_init_static( + struct pointer_set *set, + void **storage, + size_t size +) { + if (storage == NULL) { + return EINVAL; + } + + memset(set, 0, sizeof *set); + + set->count_pointers = 0; + set->size = size; + set->pointers = storage; + set->max_size = size; + set->is_static = true; + + return 0; +} + +void pset_deinit( + struct pointer_set *set +) { + if ((set->is_static == false) && (set->pointers != NULL)) { + free(set->pointers); + } + + set->count_pointers = 0; + set->size = 0; + set->pointers = NULL; + set->max_size = 0; + set->is_static = false; +} + +int pset_put( + struct pointer_set *set, + void *pointer +) { + size_t new_size; + int index; + + index = -1; + for (int i = 0; i < set->size; i++) { + if (set->pointers[i] == NULL) { + index = i; + } else if (pointer == set->pointers[i]) { + return 0; + } + } + + if (index != -1) { + set->pointers[index] = pointer; + set->count_pointers++; + return 0; + } + + if (set->is_static) { + return ENOSPC; + } else { + new_size = set->size ? set->size << 1 : 1; + + if (new_size < set->max_size) { + void **new_pointers = (void**) realloc(set->pointers, new_size * sizeof(void*)); + if (new_pointers == NULL) { + return ENOMEM; + } + + memset(new_pointers + set->size, 0, (new_size - set->size) * sizeof(void*)); + + new_pointers[set->size] = pointer; + set->count_pointers++; + + set->pointers = new_pointers; + set->size = new_size; + } else { + return ENOSPC; + } + } + + return 0; +} + +bool pset_contains( + const struct pointer_set *set, + const void *pointer +) { + for (int i = 0; i < set->size; i++) { + if ((set->pointers[i] != NULL) && (set->pointers[i] == pointer)) { + return true; + } + } + + return false; +} + +int pset_remove( + struct pointer_set *set, + const void *pointer +) { + for (int i = 0; i < set->size; i++) { + if ((set->pointers[i] != NULL) && (set->pointers[i] == pointer)) { + set->pointers[i] = NULL; + set->count_pointers--; + + return 0; + } + } + + return EINVAL; +} + +int pset_copy( + const struct pointer_set *src, + struct pointer_set *dest +) { + if (dest->size < src->count_pointers) { + if (dest->max_size < src->count_pointers) { + return ENOSPC; + } else { + void *new_pointers = realloc(dest->pointers, src->count_pointers); + if (new_pointers == NULL) { + return ENOMEM; + } + + dest->pointers = new_pointers; + dest->size = src->count_pointers; + } + } + + if (dest->size >= src->size) { + memcpy(dest->pointers, src->pointers, src->size * sizeof(void*)); + + if (dest->size > src->size) { + memset(dest->pointers + src->size, 0, (dest->size - src->size) * sizeof(void*)); + } + } else { + for (int i = 0, j = 0; i < src->size; i++) { + if (src->pointers[i] != NULL) { + dest->pointers[j] = src->pointers[i]; + j++; + } + } + } + + dest->count_pointers = src->count_pointers; +} + +void pset_intersect( + struct pointer_set *src_dest, + const struct pointer_set *b +) { + for (int i = 0, j = 0; (i < src_dest->size) && (j < src_dest->count_pointers); i++) { + if (src_dest->pointers[i] != NULL) { + if (pset_contains(b, src_dest->pointers[i]) == false) { + src_dest->pointers[i] = NULL; + src_dest->count_pointers--; + } else { + j++; + } + } + } +} + +int pset_union( + struct pointer_set *src_dest, + const struct pointer_set *b +) { + int ok; + + for (int i = 0, j = 0; (i < b->size) && (j < b->size); i++) { + if (b->pointers[i] != NULL) { + ok = pset_put(src_dest, b->pointers[i]); + if (ok != 0) { + return ok; + } + + j++; + } + } + + return 0; +} + +void *__pset_next_pointer( + struct pointer_set *set, + const void *pointer +) { + int i = -1; + + if (pointer != NULL) { + for (i = 0; i < set->size; i++) { + if (set->pointers[i] == pointer) { + break; + } + } + + if (i == set->size) return NULL; + } + + for (i = i+1; i < set->size; i++) { + if (set->pointers[i]) { + return set->pointers[i]; + } + } + + return NULL; +} + + +int cpset_init( + struct concurrent_pointer_set *set, + size_t max_size +) { + int ok; + + ok = pset_init(&set->set, max_size); + if (ok != 0) { + return ok; + } + + ok = pthread_mutex_init(&set->mutex, NULL); + if (ok < 0) { + return errno; + } + + return 0; +} + +void cpset_deinit(struct concurrent_pointer_set *set) { + pthread_mutex_destroy(&set->mutex); + pset_deinit(&set->set); } \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c index e4b70201..e50771d4 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -26,16 +27,21 @@ struct view_cb_data { void *userdata; bool was_present_last_frame; + int last_zpos; FlutterSize last_size; FlutterPoint last_offset; int last_num_mutations; FlutterPlatformViewMutation last_mutations[16]; }; -struct plane_reservation_data { +/* +struct plane_data { + int type; const struct drm_plane *plane; bool is_reserved; + int zpos; }; +*/ struct compositor compositor = { .drmdev = NULL, @@ -43,9 +49,9 @@ struct compositor compositor = { .planes = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), .has_applied_modeset = false, .should_create_window_surface_backing_store = true, + .is_cursor_enabled = false }; - static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { struct view_cb_data *data; @@ -68,7 +74,22 @@ static struct view_cb_data *get_cbs_for_view_id(int64_t view_id) { return data; } -/// GBM BO funcs for the main (window) surface +/** + * @brief Destroy all the rendertargets in the stale rendertarget cache. + */ +static int destroy_stale_rendertargets(void) { + struct rendertarget *target; + + cpset_lock(&compositor.stale_rendertargets); + + for_each_pointer_in_cpset(&compositor.stale_rendertargets, target) { + target->destroy(target); + target = NULL; + } + + cpset_unlock(&compositor.stale_rendertargets); +} + static void destroy_gbm_bo( struct gbm_bo *bo, void *userdata @@ -81,7 +102,9 @@ static void destroy_gbm_bo( free(fb); } -/// Get a DRM FB id for this GBM BO, so we can display it. +/** + * @brief Get a DRM FB id for this GBM BO, so we can display it. + */ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; int ok = -1; @@ -138,7 +161,10 @@ static uint32_t gbm_bo_get_drm_fb_id(struct gbm_bo *bo) { return fb->fb_id; } -/// Create a GL renderbuffer that is backed by a DRM buffer-object and registered as a DRM framebuffer + +/** + * @brief Create a GL renderbuffer that is backed by a DRM buffer-object and registered as a DRM framebuffer + */ static int create_drm_rbo( size_t width, size_t height, @@ -245,7 +271,9 @@ static int create_drm_rbo( return 0; } -/// Set the color attachment of a GL FBO to this DRM RBO. +/** + * @brief Set the color attachment of a GL FBO to this DRM RBO. + */ static int attach_drm_rbo_to_fbo( GLuint fbo_id, struct drm_rbo *rbo @@ -277,15 +305,9 @@ static int attach_drm_rbo_to_fbo( return 0; } -/// Destroy the OpenGL ES framebuffer-object that is associated with this -/// EGL Window Surface backed backing store -static void destroy_window_surface_backing_store_fb(void *userdata) { - struct backing_store_metadata *meta = (struct backing_store_metadata*) userdata; - - printf("destroying window surface backing store FBO\n"); -} - -/// Destroy this GL renderbuffer, and the associated DRM buffer-object and DRM framebuffer +/** + * @brief Destroy this GL renderbuffer, and the associated DRM buffer-object and DRM framebuffer + */ static void destroy_drm_rbo( struct drm_rbo *rbo ) { @@ -312,544 +334,392 @@ static void destroy_drm_rbo( } } -/// Destroy the OpenGL ES framebuffer-object that is associated with this -/// DRM FB backed backing store -static void destroy_drm_fb_backing_store_gl_fb(void *userdata) { - struct backing_store_metadata *meta; - EGLint egl_error; - GLenum gl_error; +static void rendertarget_gbm_destroy(struct rendertarget *target) { + free(target); +} + +static int rendertarget_gbm_present( + struct rendertarget *target, + struct drmdev_atomic_req *atomic_req, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos +) { + struct rendertarget_gbm *gbm_target; + struct gbm_bo *next_front_bo; + uint32_t next_front_fb_id; int ok; - meta = (struct backing_store_metadata*) userdata; + gbm_target = &target->gbm; - //printf("destroy_drm_fb_backing_store_gl_fb(gl_fbo_id: %u)\n", meta->drm_fb.gl_fbo_id); + next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); + next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - eglGetError(); - glGetError(); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "FB_ID", next_front_fb_id); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_X", 0); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_Y", 0); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_X", 0); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_Y", 0); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_W", flutterpi.display.width); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "CRTC_H", flutterpi.display.height); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0); + drmdev_atomic_req_put_plane_property(atomic_req, drm_plane_id, "zpos", zpos); - glDeleteFramebuffers(1, &meta->drm_fb.gl_fbo_id); - if (gl_error = glGetError()) { - fprintf(stderr, "[compositor] error destroying OpenGL FBO, glDeleteFramebuffers: %d\n", gl_error); + // TODO: move this to the page flip handler. + // We can only be sure the buffer can be released when the buffer swap + // ocurred. + if (gbm_target->current_front_bo != NULL) { + gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); } + gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; - destroy_drm_rbo(meta->drm_fb.rbos + 0); - destroy_drm_rbo(meta->drm_fb.rbos + 1); - - free(meta); -} - -int compositor_on_page_flip( - uint32_t sec, - uint32_t usec -) { - //x return 0; } -/// CREATE FUNCS -/// Create a flutter backing store that is backed by the -/// EGL window surface (created using libgbm). -/// I.e. create a EGL window surface and set fbo_id to 0. -static int create_window_surface_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, +/** + * @brief Create a type of rendertarget that is backed by a GBM Surface, used for rendering into the DRM primary plane. + * + * @param[out] out A pointer to the pointer of the created rendertarget. + * @param[in] compositor The compositor which this rendertarget should be associated with. + * + * @see rendertarget_gbm + */ +static int rendertarget_gbm_new( + struct rendertarget **out, struct compositor *compositor ) { - struct backing_store_metadata *meta; + struct rendertarget *target; int ok; - // This should really be creating the GBM surface, - // but we need the GBM surface to be bound as an EGL display earlier. - - meta = calloc(1, sizeof *meta); - if (meta == NULL) { - perror("[compositor] Could not allocate metadata for backing store. calloc"); + target = calloc(1, sizeof *target); + if (target == NULL) { + *out = NULL; return ENOMEM; } - ok = compositor_reserve_plane(&meta->window_surface.drm_plane_id); - if (ok != 0) { - free(meta); - fprintf(stderr, "[compositor] Could not find an unused DRM plane for flutter backing store creation. compositor_reserve_plane: %s\n", strerror(ok)); - return ok; - } + *target = (struct rendertarget) { + .is_gbm = true, + .compositor = compositor, + .gbm = { + .gbm_surface = flutterpi.gbm.surface, + .current_front_bo = NULL + }, + .gl_fbo_id = 0, + .destroy = rendertarget_gbm_destroy, + .present = rendertarget_gbm_present + }; - meta->type = kWindowSurface; - meta->window_surface.compositor = compositor; - meta->window_surface.current_front_bo = NULL; - meta->window_surface.gbm_surface = flutterpi.gbm.surface; + *out = target; - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; - backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; - backing_store_out->open_gl.framebuffer.name = 0; - backing_store_out->open_gl.framebuffer.destruction_callback = destroy_window_surface_backing_store_fb; - backing_store_out->open_gl.framebuffer.user_data = meta; - backing_store_out->user_data = meta; + return 0; +} + +static void rendertarget_nogbm_destroy(struct rendertarget *target) { + glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); + destroy_drm_rbo(target->nogbm.rbos + 1); + destroy_drm_rbo(target->nogbm.rbos + 0); + free(target); +} + +static int rendertarget_nogbm_present( + struct rendertarget *target, + struct drmdev_atomic_req *req, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos +) { + struct rendertarget_nogbm *nogbm_target; + int ok; + + nogbm_target = &target->nogbm; + + nogbm_target->current_front_rbo ^= 1; + ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); + if (ok != 0) return ok; + + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "FB_ID", nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_ID", target->compositor->drmdev->selected_crtc->crtc->crtc_id); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_X", 0); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_Y", 0); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_X", 0); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_Y", 0); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_W", flutterpi.display.width); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "CRTC_H", flutterpi.display.height); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); + drmdev_atomic_req_put_plane_property(req, drm_plane_id, "zpos", zpos); return 0; } -static int create_drm_fb_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, +/** + * @brief Create a type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. + * + * @param[out] out A pointer to the pointer of the created rendertarget. + * @param[in] compositor The compositor which this rendertarget should be associated with. + * + * @see rendertarget_nogbm + */ +static int rendertarget_nogbm_new( + struct rendertarget **out, struct compositor *compositor ) { - struct backing_store_metadata *meta; - struct drm_fb_backing_store *inner; - uint32_t plane_id; + struct rendertarget *target; EGLint egl_error; GLenum gl_error; int ok; - meta = calloc(1, sizeof *meta); - if (meta == NULL) { - perror("[compositor] Could not allocate backing store metadata, calloc"); + target = calloc(1, sizeof *target); + if (target == NULL) { return ENOMEM; } - meta->type = kDrmFb; - - inner = &meta->drm_fb; - inner->compositor = compositor; - - ok = compositor_reserve_plane(&inner->drm_plane_id); - if (ok != 0) { - free(meta); - fprintf(stderr, "[compositor] Could not find an unused DRM plane for flutter backing store creation. compositor_reserve_plane: %s\n", strerror(ok)); - return ok; - } + target->is_gbm = false; + target->compositor = compositor; + target->destroy = rendertarget_nogbm_destroy; + target->present = rendertarget_nogbm_present; eglGetError(); glGetError(); - glGenFramebuffers(1, &inner->gl_fbo_id); + glGenFramebuffers(1, &target->nogbm.gl_fbo_id); if (gl_error = glGetError()) { fprintf(stderr, "[compositor] error generating FBOs for flutter backing store, glGenFramebuffers: %d\n", gl_error); - compositor_free_plane(inner->drm_plane_id); - free(meta); - return EINVAL; + ok = EINVAL; + goto fail_free_target; } ok = create_drm_rbo( flutterpi.display.width, flutterpi.display.height, - inner->rbos + 0 + target->nogbm.rbos + 0 ); if (ok != 0) { - glDeleteFramebuffers(1, &inner->gl_fbo_id); - compositor_free_plane(inner->drm_plane_id); - free(meta); - return ok; + goto fail_delete_fb; } ok = create_drm_rbo( flutterpi.display.width, flutterpi.display.height, - inner->rbos + 1 + target->nogbm.rbos + 1 ); if (ok != 0) { - destroy_drm_rbo(inner->rbos + 0); - glDeleteFramebuffers(1, &inner->gl_fbo_id); - compositor_free_plane(inner->drm_plane_id); - free(meta); - return ok; + goto fail_destroy_drm_rbo_0; } - ok = attach_drm_rbo_to_fbo(inner->gl_fbo_id, inner->rbos + inner->current_front_rbo); + ok = attach_drm_rbo_to_fbo(target->nogbm.gl_fbo_id, target->nogbm.rbos + target->nogbm.current_front_rbo); if (ok != 0) { - destroy_drm_rbo(inner->rbos + 1); - destroy_drm_rbo(inner->rbos + 0); - glDeleteFramebuffers(1, &inner->gl_fbo_id); - compositor_free_plane(inner->drm_plane_id); - free(meta); - return ok; + goto fail_destroy_drm_rbo_1; } - backing_store_out->type = kFlutterBackingStoreTypeOpenGL; - backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; - backing_store_out->open_gl.framebuffer.target = GL_BGRA8_EXT; - backing_store_out->open_gl.framebuffer.name = inner->gl_fbo_id; - backing_store_out->open_gl.framebuffer.destruction_callback = destroy_drm_fb_backing_store_gl_fb; - backing_store_out->open_gl.framebuffer.user_data = meta; - backing_store_out->user_data = meta; + target->gl_fbo_id = target->nogbm.gl_fbo_id; + *out = target; return 0; -} -static bool create_backing_store( - const FlutterBackingStoreConfig *config, - FlutterBackingStore *backing_store_out, - void *user_data -) { - int ok; - - printf("create_backing_store\n"); - if (compositor.should_create_window_surface_backing_store) { - // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY - // plane. That backing store isn't really a backing store at all, it's - // FBO id is 0, so it's actually rendering to the window surface. - ok = create_window_surface_backing_store( - config, - backing_store_out, - user_data - ); - - if (ok != 0) { - return false; - } + fail_destroy_drm_rbo_1: + destroy_drm_rbo(target->nogbm.rbos + 1); - compositor.should_create_window_surface_backing_store = false; - } else { - // After the primary plane backing store was created, - // we only create overlay plane backing stores. I.e. - // backing stores, which have a FBO, that have a - // color-attached RBO, that has a DRM EGLImage as the storage, - // which in turn has a DRM FB associated with it. - - FlutterEngineTraceEventDurationBegin("create_drm_fb_backing_store"); - ok = create_drm_fb_backing_store( - config, - backing_store_out, - user_data - ); - FlutterEngineTraceEventDurationEnd("create_drm_fb_backing_store"); + fail_destroy_drm_rbo_0: + destroy_drm_rbo(target->nogbm.rbos + 0); - if (ok != 0) { - return false; - } - } + fail_delete_fb: + glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); - return true; + fail_free_target: + free(target); + *out = NULL; + return ok; } -/// COLLECT FUNCS -static int collect_window_surface_backing_store( - const FlutterBackingStore *store, - struct backing_store_metadata *meta -) { - struct window_surface_backing_store *inner = &meta->window_surface; - - printf("collect_window_surface_backing_store\n"); +/** + * @brief Called by flutter when the OpenGL FBO of a backing store should be destroyed. + * Called on an internal engine-managed thread. This is actually called after the engine + * calls @ref on_collect_backing_store. + * + * @param[in] userdata The pointer to the struct flutterpi_backing_store that should be destroyed. + */ +static void on_destroy_backing_store_gl_fb(void *userdata) { + struct flutterpi_backing_store *store; + struct compositor *compositor; + + store = userdata; + compositor = store->target->compositor; - compositor_free_plane(inner->drm_plane_id); + printf("on_destroy_backing_store_gl_fb(is_gbm: %s, backing_store: %p)\n", store->target->is_gbm ? "true" : "false", store); - compositor.should_create_window_surface_backing_store = true; + cpset_put_(&compositor->stale_rendertargets, store->target); - return 0; + if (store->should_free_on_next_destroy) { + free(store); + } else { + store->should_free_on_next_destroy = true; + } } -static int collect_drm_fb_backing_store( - const FlutterBackingStore *store, - struct backing_store_metadata *meta +/** + * @brief A callback invoked by the engine to release the backing store. The embedder may + * collect any resources associated with the backing store. Invoked on an internal + * engine-managed thread. This is actually called before the engine calls @ref on_destroy_backing_store_gl_fb. + * + * @param[in] backing_store The backing store to be collected. + * @param[in] userdata A pointer to the flutterpi compositor. + */ +static bool on_collect_backing_store( + const FlutterBackingStore *backing_store, + void *userdata ) { - struct drm_fb_backing_store *inner = &meta->drm_fb; - - //printf("collect_drm_fb_backing_store(gl_fbo_id: %u)\n", inner->gl_fbo_id); + struct flutterpi_backing_store *store; + struct compositor *compositor; - // makes sense that the FlutterBackingStore collect callback is called before the - // FlutterBackingStore OpenGL framebuffer destroy callback. Thanks flutter. (/s) - // free(inner); - - compositor_free_plane(inner->drm_plane_id); - - return 0; -} - -static bool collect_backing_store( - const FlutterBackingStore *renderer, - void *user_data -) { - int ok; + store = backing_store->user_data; + compositor = store->target->compositor; - if (renderer->type == kFlutterBackingStoreTypeOpenGL && - renderer->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer) { - struct backing_store_metadata *meta = renderer->open_gl.framebuffer.user_data; + printf("on_collect_backing_store(is_gbm: %s, backing_store: %p)\n", store->target->is_gbm ? "true" : "false", store); - if (meta->type == kWindowSurface) { - ok = collect_window_surface_backing_store(renderer, meta); - if (ok != 0) { - return false; - } + cpset_put_(&compositor->stale_rendertargets, store->target); - compositor.should_create_window_surface_backing_store = true; - } else if (meta->type == kDrmFb) { - ok = collect_drm_fb_backing_store(renderer, meta); - if (ok != 0) { - return false; - } - } else { - fprintf(stderr, "[compositor] Unsupported flutter backing store backend: %d\n", meta->type); - return false; - } + if (store->should_free_on_next_destroy) { + free(store); } else { - fprintf(stderr, "[compositor] Unsupported flutter backing store type\n"); - return false; + store->should_free_on_next_destroy = true; } return true; } -/// PRESENT FUNCS -static int present_window_surface_backing_store( - struct window_surface_backing_store *backing_store, - struct drmdev_atomic_req *atomic_req, - int offset_x, - int offset_y, - int width, - int height, - int zpos +/** + * @brief A callback invoked by the engine to obtain a FlutterBackingStore for a specific FlutterLayer. + * Called on an internal engine-managed thread. + * + * @param[in] config The dimensions of the backing store to be created, post transform. + * @param[out] backing_store_out The created backing store. + * @param[in] userdata A pointer to the flutterpi compositor. + */ +static bool on_create_backing_store( + const FlutterBackingStoreConfig *config, + FlutterBackingStore *backing_store_out, + void *userdata ) { - struct gbm_bo *next_front_bo; - uint32_t next_front_fb_id; + struct flutterpi_backing_store *store; + struct rendertarget *target; + struct compositor *compositor; int ok; - - next_front_bo = gbm_surface_lock_front_buffer(backing_store->gbm_surface); - next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "FB_ID", next_front_fb_id); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "CRTC_H", flutterpi.display.height); - drmdev_atomic_req_put_plane_property(atomic_req, backing_store->drm_plane_id, "zpos", zpos); + compositor = userdata; - // TODO: move this to the page flip handler. - // We can only be sure the buffer can be released when the buffer swap - // ocurred. - if (backing_store->current_front_bo != NULL) { - gbm_surface_release_buffer(backing_store->gbm_surface, backing_store->current_front_bo); + store = calloc(1, sizeof *store); + if (store == NULL) { + return false; } - backing_store->current_front_bo = (struct gbm_bo *) next_front_bo; - return 0; -} + printf("on_create_backing_store(%f x %f): %p\n", config->size.width, config->size.height, store); -static int present_drm_fb_backing_store( - struct drm_fb_backing_store *backing_store, - struct drmdev_atomic_req *req, - int offset_x, - int offset_y, - int width, - int height, - int zpos -) { - int ok; - - backing_store->current_front_rbo ^= 1; - ok = attach_drm_rbo_to_fbo(backing_store->gl_fbo_id, backing_store->rbos + backing_store->current_front_rbo); - if (ok != 0) return ok; - - // present the back buffer - /* - ok = drmModeSetPlane( - drm.fd, - backing_store->drm_plane_id, - drm.crtc_id, - backing_store->rbos[backing_store->current_front_rbo ^ 1].drm_fb_id, - 0, - offset_x, offset_y, width, height, - 0, 0, ((uint16_t) width) << 16, ((uint16_t) height) << 16 - ); - if (ok == -1) { - perror("[compositor] Could not update overlay plane for presenting a DRM FB backed backing store. drmModeSetPlane"); - return errno; + // first, try to find a stale GBM rendertarget. + cpset_lock(&compositor->stale_rendertargets); + for_each_pointer_in_cpset(&compositor->stale_rendertargets, target) break; + if (target != NULL) { + printf("found stale rendertarget.\n"); + cpset_remove_locked(&compositor->stale_rendertargets, target); } - */ + cpset_unlock(&compositor->stale_rendertargets); + + // if we didn't find one, check if we should create one. If not, + // try to find a stale No-GBM rendertarget. If there is none, + // create one. + if (target == NULL) { + if (compositor->should_create_window_surface_backing_store) { + // We create 1 "backing store" that is rendering to the DRM_PLANE_PRIMARY + // plane. That backing store isn't really a backing store at all, it's + // FBO id is 0, so it's actually rendering to the window surface. + + printf("Creating a GBM rendertarget.\n"); + + FlutterEngineTraceEventDurationBegin("rendertarget_gbm_new"); + ok = rendertarget_gbm_new( + &target, + compositor + ); + FlutterEngineTraceEventDurationEnd("rendertarget_gbm_new"); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "FB_ID", backing_store->rbos[backing_store->current_front_rbo ^ 1].drm_fb_id); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_ID", backing_store->compositor->drmdev->selected_crtc->crtc->crtc_id); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_X", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_Y", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_W", ((uint16_t) flutterpi.display.width) << 16); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "SRC_H", ((uint16_t) flutterpi.display.height) << 16); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_X", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_Y", 0); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_W", flutterpi.display.width); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "CRTC_H", flutterpi.display.height); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); - drmdev_atomic_req_put_plane_property(req, backing_store->drm_plane_id, "zpos", zpos); + if (ok != 0) { + free(store); + return false; + } - return 0; -} + compositor->should_create_window_surface_backing_store = false; + } else { + printf("Creating a No-GBM rendertarget.\n"); -static int present_platform_view( - int64_t view_id, - struct drmdev_atomic_req *req, - const FlutterPlatformViewMutation **mutations, - size_t num_mutations, - int offset_x, - int offset_y, - int width, - int height, - int zpos -) { - struct view_cb_data *cbs; + FlutterEngineTraceEventDurationBegin("rendertarget_nogbm_new"); + ok = rendertarget_nogbm_new( + &target, + compositor + ); + FlutterEngineTraceEventDurationEnd("rendertarget_nogbm_new"); - cbs = get_cbs_for_view_id(view_id); - if (cbs == NULL) { - return EINVAL; + if (ok != 0) { + free(store); + return false; + } + } } - if (cbs->was_present_last_frame == false) { - cbs->mount( - view_id, - req, - mutations, - num_mutations, - offset_x, - offset_y, - width, - height, - zpos, - cbs->userdata - ); - cbs->was_present_last_frame = true; - } + store->target = target; + store->flutter_backing_store = (FlutterBackingStore) { + .struct_size = backing_store_out->struct_size, + .type = kFlutterBackingStoreTypeOpenGL, + .open_gl = { + .type = kFlutterOpenGLTargetTypeFramebuffer, + .framebuffer = { + .target = GL_BGRA8_EXT, + .name = target->gl_fbo_id, + .destruction_callback = on_destroy_backing_store_gl_fb, + .user_data = store + } + }, + .user_data = store + }; - if (cbs->present != NULL) { - return cbs->present( - view_id, - req, - mutations, - num_mutations, - offset_x, - offset_y, - width, - height, - zpos, - cbs->userdata - ); - } else { - return 0; - } + memcpy(backing_store_out, &store->flutter_backing_store, sizeof(FlutterBackingStore)); + + return true; } -static bool present_layers_callback( + +/// PRESENT FUNCS +static bool on_present_layers( const FlutterLayer **layers, size_t layers_count, - void *user_data + void *userdata ) { - struct plane_reservation_data *data; - struct compositor *compositor; struct drmdev_atomic_req *req; struct view_cb_data *cb_data; + struct plane_data *plane; + struct compositor *compositor; uint32_t req_flags; - int ok; - - compositor = user_data; - - printf("present_layers_callback\n"); - - /* - printf("[compositor] present_layers_callback(\n" - " layers_count: %lu,\n" - " layers = {\n", - layers_count); - for (int i = 0; i < layers_count; i++) { - printf( - " [%d] = {\n" - " type: %s,\n" - " offset: {x: %f, y: %f},\n" - " size: %f x %f,\n", - i, - layers[i]->type == kFlutterLayerContentTypeBackingStore ? "backing store" : "platform view", - layers[i]->offset.x, - layers[i]->offset.y, - layers[i]->size.width, - layers[i]->size.height - ); - - if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - struct backing_store_metadata *meta = layers[i]->backing_store->user_data; - - printf(" %s\n", meta->type == kWindowSurface ? "window surface" : "drm fb"); - } else if (layers[i]->type == kFlutterLayerContentTypePlatformView) { - printf( - " platform_view: {\n" - " identifier: %lld\n" - " mutations: {\n", - layers[i]->platform_view->identifier - ); - for (int j = 0; j < layers[i]->platform_view->mutations_count; j++) { - const FlutterPlatformViewMutation *mut = layers[i]->platform_view->mutations[j]; - - printf( - " [%d] = {\n" - " type: %s,\n", - j, - mut->type == kFlutterPlatformViewMutationTypeOpacity ? "opacity" : - mut->type == kFlutterPlatformViewMutationTypeClipRect ? "clip rect" : - mut->type == kFlutterPlatformViewMutationTypeClipRoundedRect ? "clip rounded rect" : - mut->type == kFlutterPlatformViewMutationTypeTransformation ? "transformation" : - "(?)" - ); + int ok; - if (mut->type == kFlutterPlatformViewMutationTypeOpacity) { - printf( - " opacity: %f\n", - mut->opacity - ); - } else if (mut->type == kFlutterPlatformViewMutationTypeClipRect) { - printf( - " clip_rect: {bottom: %f, left: %f, right: %f, top: %f}\n", - mut->clip_rect.bottom, - mut->clip_rect.left, - mut->clip_rect.right, - mut->clip_rect.top - ); - } else if (mut->type == kFlutterPlatformViewMutationTypeClipRoundedRect) { - printf( - " clip_rounded_rect: {\n" - " lower_left_corner_radius: %f,\n" - " lower_right_corner_radius: %f,\n" - " upper_left_corner_radius: %f,\n" - " upper_right_corner_radius: %f,\n" - " rect: {bottom: %f, left: %f, right: %f, top: %f}\n" - " }\n", - mut->clip_rounded_rect.lower_left_corner_radius, - mut->clip_rounded_rect.lower_right_corner_radius, - mut->clip_rounded_rect.upper_left_corner_radius, - mut->clip_rounded_rect.upper_right_corner_radius, - mut->clip_rounded_rect.rect.bottom, - mut->clip_rounded_rect.rect.left, - mut->clip_rounded_rect.rect.right, - mut->clip_rounded_rect.rect.top - ); - } else if (mut->type == kFlutterPlatformViewMutationTypeTransformation) { - printf( - " transformation\n" - ); - } + compositor = userdata; - printf(" },\n"); - } - printf(" }\n"); - } - printf(" },\n"); - } - printf(" }\n)\n"); - */ + drmdev_new_atomic_req(compositor->drmdev, &req); - FlutterEngineTraceEventDurationBegin("present"); + cpset_lock(&compositor->cbs); - // flush GL - FlutterEngineTraceEventDurationBegin("eglSwapBuffers"); eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); - FlutterEngineTraceEventDurationEnd("eglSwapBuffers"); - - FlutterEngineTraceEventDurationBegin("drmdev_new_atomic_req"); - drmdev_new_atomic_req(compositor->drmdev, &req); - FlutterEngineTraceEventDurationEnd("drmdev_new_atomic_req"); - // if we haven't yet set the display mode, set one req_flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; if (compositor->has_applied_modeset == false) { ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); @@ -857,20 +727,63 @@ static bool present_layers_callback( compositor->has_applied_modeset = true; } + + // first, the state machine phase. + // go through the layers, update + // all platform views accordingly. + // unmount, update, mount. in that order + { + void *mounted_views_storage[layers_count]; + memset(mounted_views_storage, 0, layers_count * sizeof(void*)); + struct pointer_set mounted_views = PSET_INITIALIZER_STATIC(mounted_views_storage, layers_count); + + void *unmounted_views_storage[layers_count]; + memset(unmounted_views_storage, 0, layers_count * sizeof(void*)); + struct pointer_set unmounted_views = PSET_INITIALIZER_STATIC(unmounted_views_storage, layers_count); + + void *updated_views_storage[layers_count]; + memset(updated_views_storage, 0, layers_count * sizeof(void*)); + struct pointer_set updated_views = PSET_INITIALIZER_STATIC(updated_views_storage, layers_count); + + for_each_pointer_in_cpset(&compositor->cbs, cb_data) { + const FlutterLayer *layer; + bool is_present = false; + int zpos; + + for (int i = 0; i < layers_count; i++) { + if (layers[i]->type == kFlutterLayerContentTypePlatformView && + layers[i]->platform_view->identifier == cb_data->view_id) { + is_present = true; + layer = layers[i]; + zpos = i; + break; + } + } + + if (!is_present && cb_data->was_present_last_frame) { + pset_put(&unmounted_views, cb_data); + } else if (is_present && cb_data->was_present_last_frame) { + if (cb_data->update_view != NULL) { + bool did_update_view = false; + + did_update_view = did_update_view || (zpos != cb_data->last_zpos); + did_update_view = did_update_view || memcmp(&cb_data->last_size, &layer->size, sizeof(FlutterSize)); + did_update_view = did_update_view || memcmp(&cb_data->last_offset, &layer->offset, sizeof(FlutterPoint)); + did_update_view = did_update_view || (cb_data->last_num_mutations != layer->platform_view->mutations_count); + for (int i = 0; (i < layer->platform_view->mutations_count) && !did_update_view; i++) { + did_update_view = did_update_view || memcmp(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + } - // unmount non-present platform views - FlutterEngineTraceEventDurationBegin("unmount non-present platform views"); - for_each_pointer_in_cpset(&compositor->cbs, cb_data) { - bool is_present = false; - for (int i = 0; i < layers_count; i++) { - if (layers[i]->type == kFlutterLayerContentTypePlatformView && - layers[i]->platform_view->identifier == cb_data->view_id) { - is_present = true; - break; + if (did_update_view) { + pset_put(&updated_views, cb_data); + } + } + } else if (is_present && !cb_data->was_present_last_frame) { + pset_put(&mounted_views, cb_data); } } - if (!is_present && cb_data->was_present_last_frame) { + for_each_pointer_in_pset(&unmounted_views, cb_data) { if (cb_data->unmount != NULL) { ok = cb_data->unmount( cb_data->view_id, @@ -878,155 +791,160 @@ static bool present_layers_callback( cb_data->userdata ); if (ok != 0) { - fprintf(stderr, "[compositor] Could not unmount platform view. %s\n", strerror(ok)); + fprintf(stderr, "[compositor] Could not unmount platform view. unmount: %s\n", strerror(ok)); } } } - } - FlutterEngineTraceEventDurationEnd("unmount non-present platform views"); - // present all layers, invoke the mount/update_view/present callbacks of platform views - for (int i = 0; i < layers_count; i++) { - const FlutterLayer *layer = layers[i]; + for_each_pointer_in_pset(&updated_views, cb_data) { + const FlutterLayer *layer; + int zpos; - if (layer->type == kFlutterLayerContentTypeBackingStore) { - const FlutterBackingStore *backing_store = layer->backing_store; - struct backing_store_metadata *meta = backing_store->user_data; + for (int i = 0; i < layers_count; i++) { + if (layers[i]->type == kFlutterLayerContentTypePlatformView && + layers[i]->platform_view->identifier == cb_data->view_id) { + layer = layers[i]; + zpos = i - 127; + break; + } + } - if (meta->type == kWindowSurface) { - FlutterEngineTraceEventDurationBegin("present_window_surface_backing_store"); - ok = present_window_surface_backing_store( - &meta->window_surface, - req, - (int) layer->offset.x, - (int) layer->offset.y, - (int) layer->size.width, - (int) layer->size.height, - 0 - ); - FlutterEngineTraceEventDurationEnd("present_window_surface_backing_store"); - } else if (meta->type == kDrmFb) { - FlutterEngineTraceEventDurationBegin("present_drm_fb_backing_store"); - ok = present_drm_fb_backing_store( - &meta->drm_fb, - req, - (int) layer->offset.x, - (int) layer->offset.y, - (int) layer->size.width, - (int) layer->size.height, - 1 - ); - FlutterEngineTraceEventDurationEnd("present_drm_fb_backing_store"); + ok = cb_data->update_view( + cb_data->view_id, + req, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + zpos, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not update platform view. update_view: %s\n", strerror(ok)); } - - } else if (layer->type == kFlutterLayerContentTypePlatformView) { - cb_data = get_cbs_for_view_id(layer->platform_view->identifier); - - if (cb_data != NULL) { - if (cb_data->was_present_last_frame == false) { - if (cb_data->mount != NULL) { - FlutterEngineTraceEventDurationBegin("mount platform view"); - ok = cb_data->mount( - layer->platform_view->identifier, - req, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - (int) round(layer->offset.x), - (int) round(layer->offset.y), - (int) round(layer->size.width), - (int) round(layer->size.height), - 0, - cb_data->userdata - ); - FlutterEngineTraceEventDurationEnd("mount platform view"); - if (ok != 0) { - fprintf(stderr, "[compositor] Could not mount platform view. %s\n", strerror(ok)); - } - } - } else { - bool did_update_view = false; - - did_update_view = did_update_view || memcmp(&cb_data->last_size, &layer->size, sizeof(FlutterSize)); - did_update_view = did_update_view || memcmp(&cb_data->last_offset, &layer->offset, sizeof(FlutterPoint)); - did_update_view = did_update_view || (cb_data->last_num_mutations != layer->platform_view->mutations_count); - for (int i = 0; (i < layer->platform_view->mutations_count) && !did_update_view; i++) { - did_update_view = did_update_view || memcmp(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); - } - if (did_update_view) { - if (cb_data->update_view != NULL) { - FlutterEngineTraceEventDurationBegin("update platform view"); - ok = cb_data->update_view( - cb_data->view_id, - req, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - (int) round(layer->offset.x), - (int) round(layer->offset.y), - (int) round(layer->size.width), - (int) round(layer->size.height), - 0, - cb_data->userdata - ); - FlutterEngineTraceEventDurationEnd("update platform view"); - if (ok != 0) { - fprintf(stderr, "[compositor] Could not update platform views' view. %s\n", strerror(ok)); - } - } - } - } + cb_data->last_zpos = zpos; + cb_data->last_size = layer->size; + cb_data->last_offset = layer->offset; + cb_data->last_num_mutations = layer->platform_view->mutations_count; + for (int i = 0; i < layer->platform_view->mutations_count; i++) { + memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + } + } - if (cb_data->present) { - FlutterEngineTraceEventDurationBegin("present platform view"); - ok = cb_data->present( - layer->platform_view->identifier, - req, - layer->platform_view->mutations, - layer->platform_view->mutations_count, - (int) round(layer->offset.x), - (int) round(layer->offset.y), - (int) round(layer->size.width), - (int) round(layer->size.height), - 0, - cb_data->userdata - ); - FlutterEngineTraceEventDurationEnd("present platform view"); - if (ok != 0) { - fprintf(stderr, "[compositor] Could not present platform view. %s\n", strerror(ok)); - } + for_each_pointer_in_pset(&mounted_views, cb_data) { + const FlutterLayer *layer; + int zpos; + + for (int i = 0; i < layers_count; i++) { + if (layers[i]->type == kFlutterLayerContentTypePlatformView && + layers[i]->platform_view->identifier == cb_data->view_id) { + layer = layers[i]; + zpos = i - 127; + break; } + } - cb_data->was_present_last_frame = true; - cb_data->last_size = layer->size; - cb_data->last_offset = layer->offset; - cb_data->last_num_mutations = layer->platform_view->mutations_count; - for (int i = 0; i < layer->platform_view->mutations_count; i++) { - memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + if (cb_data->mount != NULL) { + ok = cb_data->mount( + layer->platform_view->identifier, + req, + layer->platform_view->mutations, + layer->platform_view->mutations_count, + (int) round(layer->offset.x), + (int) round(layer->offset.y), + (int) round(layer->size.width), + (int) round(layer->size.height), + zpos, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not mount platform view. %s\n", strerror(ok)); } } - } else { - fprintf(stderr, "[compositor] Unsupported flutter layer type: %d\n", layer->type); } } - - eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - // all unused planes will be set inactive - for_each_pointer_in_cpset(&compositor->planes, data) { - if (data->is_reserved == false) { - drmdev_atomic_req_put_plane_property(req, data->plane->plane->plane_id, "FB_ID", 0); + for (int i = 0; i < layers_count; i++) { + struct drm_plane *drm_plane; + + if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { + for_each_unreserved_plane_in_atomic_req(req, drm_plane) { + // choose a plane which has an "intrinsic" zpos that matches + // the zpos we want the plane to have. + // (Since planes are buggy and we can't rely on the zpos we explicitly + // configure the plane to have to be actually applied to the hardware. + // In short, assigning a different value to the zpos property won't always + // take effect.) + if ((i == 0) && (drm_plane->type == DRM_PLANE_TYPE_PRIMARY)) { + drmdev_atomic_req_reserve_plane(req, drm_plane); + break; + } else if ((i != 0) && (drm_plane->type == DRM_PLANE_TYPE_OVERLAY)) { + drmdev_atomic_req_reserve_plane(req, drm_plane); + break; + } + } + if (drm_plane == NULL) { + fprintf(stderr, "[compositor] Could not find a free primary/overlay DRM plane for presenting the backing store. drmdev_atomic_req_reserve_plane: %s\n", strerror(ok)); + continue; + } + + struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; + struct rendertarget *target = store->target; + + ok = target->present( + target, + req, + drm_plane->plane->plane_id, + 0, + 0, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, + i + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not present backing store. rendertarget->present: %s\n", strerror(ok)); + } + } else if (layers[i]->type == kFlutterLayerContentTypePlatformView) { + cb_data = get_cbs_for_view_id_locked(layers[i]->platform_view->identifier); + + if ((cb_data != NULL) && (cb_data->present != NULL)) { + ok = cb_data->present( + cb_data->view_id, + req, + layers[i]->platform_view->mutations, + layers[i]->platform_view->mutations_count, + (int) round(layers[i]->offset.x), + (int) round(layers[i]->offset.y), + (int) round(layers[i]->size.width), + (int) round(layers[i]->size.height), + i - 127, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not present platform view. platform_view->present: %s\n", strerror(ok)); + } + } } } + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + FlutterEngineTraceEventDurationBegin("drmdev_atomic_req_commit"); drmdev_atomic_req_commit(req, req_flags, NULL); FlutterEngineTraceEventDurationEnd("drmdev_atomic_req_commit"); - drmdev_destroy_atomic_req(req); - - FlutterEngineTraceEventDurationEnd("present"); + cpset_unlock(&compositor->planes); + cpset_unlock(&compositor->cbs); +} - return true; +int compositor_on_page_flip( + uint32_t sec, + uint32_t usec +) { + return 0; } /// PLATFORM VIEW CALLBACKS @@ -1080,54 +998,17 @@ int compositor_remove_view_callbacks(int64_t view_id) { } /// DRM HARDWARE PLANE RESERVATION -int compositor_reserve_plane(uint32_t *plane_id_out) { - struct plane_reservation_data *data; - - cpset_lock(&compositor.planes); - - for_each_pointer_in_cpset(&compositor.planes, data) { - if (data->is_reserved == false) { - data->is_reserved = true; - cpset_unlock(&compositor.planes); - - *plane_id_out = data->plane->plane->plane_id; - return 0; - } - } - - cpset_unlock(&compositor.planes); - - *plane_id_out = 0; - return EBUSY; -} - -int compositor_free_plane(uint32_t plane_id) { - struct plane_reservation_data *data; - - cpset_lock(&compositor.planes); - - for_each_pointer_in_cpset(&compositor.planes, data) { - if (data->plane->plane->plane_id == plane_id) { - data->is_reserved = false; - cpset_unlock(&compositor.planes); - return 0; - } - } - - cpset_unlock(&compositor.planes); - return EINVAL; -} /// COMPOSITOR INITIALIZATION int compositor_initialize(struct drmdev *drmdev) { - struct plane_reservation_data *data; - const struct drm_plane *plane; + //struct plane_data *data; + //const struct drm_plane *plane; + /* cpset_lock(&compositor.planes); for_each_plane_in_drmdev(drmdev, plane) { - - data = calloc(1, sizeof (struct plane_reservation_data)); + data = calloc(1, sizeof (struct plane_data)); if (data == NULL) { for_each_pointer_in_cpset(&compositor.planes, data) free(data); @@ -1135,24 +1016,170 @@ int compositor_initialize(struct drmdev *drmdev) { return ENOMEM; } + for (int i = 0; i < plane->props->count_props; i++) { + if (strcmp(plane->props_info[i]->name, "type") == 0) { + uint32_t prop_id = plane->props_info[i]->prop_id; + + for (int i = 0; i < plane->props->count_props; i++) { + if (plane->props->props[i] == prop_id) { + data->type = plane->props->prop_values[i]; + } + } + } + } + data->plane = plane; data->is_reserved = false; - cpset_put_locked(&compositor.planes, data); + cpset_put(&compositor.planes, data); } cpset_unlock(&compositor.planes); + */ compositor.drmdev = drmdev; return 0; } +int compositor_set_cursor_enabled(bool enabled) { + if ((compositor.cursor.is_enabled == false) && (enabled == true)) { + struct drm_mode_create_dumb create_req; + struct drm_mode_map_dumb map_req; + uint32_t drm_fb_id; + uint32_t *buffer; + uint64_t cap; + uint8_t depth; + int ok; + + ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ok < 0) { + perror("[compositor] Could not query GPU Driver support for dumb buffers. drmGetCap"); + return errno; + } + + if (cap == 0) { + fprintf(stderr, "[compositor] Kernel / GPU Driver does not support dumb DRM buffers. Mouse cursor will not be displayed.\n"); + return ENOTSUP; + } + + ok = drmGetCap(compositor.drmdev->fd, DRM_CAP_DUMB_PREFERRED_DEPTH, &cap); + if (ok < 0) { + perror("[compositor] Could not query dumb buffer preferred depth capability. drmGetCap"); + } + + depth = (uint8_t) cap; + + memset(&create_req, 0, sizeof create_req); + create_req.width = 64; + create_req.height = 64; + create_req.bpp = 32; + create_req.flags = 0; + + ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req); + if (ok < 0) { + perror("[compositor] Could not create a dumb buffer for the hardware cursor. ioctl"); + return errno; + } + + ok = drmModeAddFB(compositor.drmdev->fd, create_req.width, create_req.height, depth, create_req.bpp, create_req.pitch, create_req.handle, &drm_fb_id); + if (ok < 0) { + perror("[compositor] Could not make a DRM FB out of the hardware cursor buffer. drmModeAddFB"); + + return errno; + } + + memset(&map_req, 0, sizeof map_req); + map_req.handle = create_req.handle; + + ok = ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req); + if (ok < 0) { + perror("[compositor] Could not prepare dumb buffer mmap for uploading the hardware cursor icon. ioctl"); + return errno; + } + + buffer = mmap(0, create_req.size, PROT_READ | PROT_WRITE, MAP_SHARED, compositor.drmdev->fd, map_req.offset); + if (buffer == MAP_FAILED) { + perror("[compositor] Could not mmap dumb buffer for uploading the hardware cursor icon. mmap"); + return errno; + } + + memset(buffer, 0, create_req.size); + buffer[0] = 0xFFFFFF; + + ok = drmModeSetCursor( + compositor.drmdev->fd, + compositor.drmdev->selected_crtc->crtc->crtc_id, + create_req.handle, + create_req.width, + create_req.height + ); + if (ok < 0) { + perror("[compositor] Could not set the mouse cursor buffer. drmModeSetCursor"); + return errno; + } + + compositor.cursor.is_enabled = true; + compositor.cursor.width = create_req.width; + compositor.cursor.height = create_req.height; + compositor.cursor.bpp = create_req.bpp; + compositor.cursor.depth = depth; + compositor.cursor.gem_bo_handle = create_req.handle; + compositor.cursor.pitch = create_req.pitch; + compositor.cursor.size = create_req.size; + compositor.cursor.drm_fb_id = drm_fb_id; + compositor.cursor.x = 0; + compositor.cursor.y = 0; + compositor.cursor.buffer = buffer; + } else if ((compositor.cursor.is_enabled == true) && (enabled == false)) { + struct drm_mode_destroy_dumb destroy_req; + + drmModeSetCursor( + compositor.drmdev->fd, + compositor.drmdev->selected_crtc->crtc->crtc_id, + 0, 0, 0 + ); + + munmap(compositor.cursor.buffer, compositor.cursor.size); + + drmModeRmFB(compositor.drmdev->fd, compositor.cursor.drm_fb_id); + + memset(&destroy_req, 0, sizeof destroy_req); + destroy_req.handle = compositor.cursor.gem_bo_handle; + + ioctl(compositor.drmdev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req); + + compositor.cursor.is_enabled = false; + compositor.cursor.width = 0; + compositor.cursor.height = 0; + compositor.cursor.bpp = 0; + compositor.cursor.depth = 0; + compositor.cursor.gem_bo_handle = 0; + compositor.cursor.pitch = 0; + compositor.cursor.size = 0; + compositor.cursor.drm_fb_id = 0; + compositor.cursor.x = 0; + compositor.cursor.y = 0; + compositor.cursor.buffer = NULL; + } +} + +int compositor_set_cursor_pos(int x, int y) { + int ok; + + ok = drmModeMoveCursor(compositor.drmdev->fd, compositor.drmdev->selected_crtc->crtc->crtc_id, x, y); + if (ok < 0) { + perror("[compositor] Could not move cursor. drmModeMoveCursor"); + return errno; + } + + return 0; +} const FlutterCompositor flutter_compositor = { .struct_size = sizeof(FlutterCompositor), - .create_backing_store_callback = create_backing_store, - .collect_backing_store_callback = collect_backing_store, - .present_layers_callback = present_layers_callback, + .create_backing_store_callback = on_create_backing_store, + .collect_backing_store_callback = on_collect_backing_store, + .present_layers_callback = on_present_layers, .user_data = &compositor }; \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 2a5c5376..9bd759aa 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -13,14 +13,19 @@ #include #include #include -#include #include #include #include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -72,11 +77,11 @@ OPTIONS:\n\ pattern you use as a parameter so it isn't \n\ implicitly expanded by your shell.\n\ \n\ - -r, --release Run the app in release mode. This AOT snapshot\n\ + -r, --release Run the app in release mode. The AOT snapshot\n\ of the app (\"app.so\") must be located inside the\n\ asset bundle directory.\n\ \n\ - -p, --profile Run the app in profile mode. This runtime mode, too\n\ + -p, --profile Run the app in profile mode. This runtime mode too\n\ depends on the AOT snapshot.\n\ \n\ -d, --debug Run the app in debug mode. This is the default.\n\ @@ -441,8 +446,8 @@ static void on_frame_request( } static FlutterTransformation on_get_transformation(void *userdata) { - printf("on_get_transformation\n"); - return FLUTTER_ROTZ_TRANSFORMATION((FlutterEngineGetCurrentTime() / 10000000) % 360); + //return FLUTTER_ROTZ_TRANSFORMATION(FlutterEngineGetCurrentTime() % 10000000000000); + return flutterpi.view.view_to_display_transform; } @@ -775,6 +780,14 @@ int flutterpi_send_platform_message( on_send_platform_message, msg ); + if (ok != 0) { + if (message && message_size) { + free(msg->message); + } + free(msg->target_channel); + free(msg); + return ok; + } } return 0; @@ -1109,24 +1122,31 @@ int flutterpi_fill_view_properties( flutterpi.view.height_mm = flutterpi.display.width_mm; } - FlutterTransformation center_view_around_origin = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.display.width - flutterpi.view.width, 0); - FlutterTransformation rotate = FLUTTER_ROTZ_TRANSFORMATION(flutterpi.view.rotation); - FlutterTransformation translate_to_fill_screen = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.display.width/2, flutterpi.display.height/2); - - FlutterTransformation multiplied; - multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(center_view_around_origin, rotate); - multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(multiplied, translate_to_fill_screen); + if (flutterpi.view.rotation == 0) { + flutterpi.view.view_to_display_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); - flutterpi.view.view_to_display_transform = center_view_around_origin; + flutterpi.view.display_to_view_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); + } else if (flutterpi.view.rotation == 90) { + flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(90); + flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; - FlutterTransformation inverse_center_view_around_origin = FLUTTER_TRANSLATION_TRANSFORMATION(flutterpi.view.width/2, flutterpi.view.height/2); - FlutterTransformation inverse_rotate = FLUTTER_ROTZ_TRANSFORMATION(-flutterpi.view.rotation); - FlutterTransformation inverse_translate_to_fill_screen = FLUTTER_TRANSLATION_TRANSFORMATION(-flutterpi.display.width/2, -flutterpi.display.height/2); + flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-90); + flutterpi.view.display_to_view_transform.transY = flutterpi.display.width; + } else if (flutterpi.view.rotation == 180) { + flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(180); + flutterpi.view.view_to_display_transform.transX = flutterpi.display.width; + flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(inverse_translate_to_fill_screen, inverse_rotate); - multiplied = FLUTTER_MULTIPLIED_TRANSFORMATIONS(multiplied, inverse_center_view_around_origin); + flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-180); + flutterpi.view.display_to_view_transform.transX = flutterpi.display.width; + flutterpi.view.display_to_view_transform.transY = flutterpi.display.height; + } else if (flutterpi.view.rotation == 270) { + flutterpi.view.view_to_display_transform = FLUTTER_ROTZ_TRANSFORMATION(270); + flutterpi.view.view_to_display_transform.transY = flutterpi.display.height; - flutterpi.view.display_to_view_transform = inverse_center_view_around_origin; + flutterpi.view.display_to_view_transform = FLUTTER_ROTZ_TRANSFORMATION(-270); + flutterpi.view.display_to_view_transform.transX = flutterpi.display.height; + } return 0; } @@ -1154,8 +1174,7 @@ static int init_display(void) { const drmModeModeInfo *mode, *mode_iter; drmDevicePtr devices[64]; EGLint egl_error; - GLenum gl_error; - int ok, area, num_devices; + int ok, num_devices; /********************** * DRM INITIALIZATION * @@ -1582,15 +1601,6 @@ static int init_application(void) { void *app_elf_handle; int ok; - if (flutterpi.flutter.runtime_mode == kRelease || flutterpi.flutter.runtime_mode == kProfile) { - fprintf( - stderr, - "flutter-pi doesn't support running apps in release or profile mode yet.\n" - "See https://github.com/ardera/flutter-pi/issues/65 for more info.\n" - ); - return EINVAL; - } - ok = plugin_registry_init(); if (ok != 0) { fprintf(stderr, "[flutter-pi] Could not initialize plugin registry: %s\n", strerror(ok)); @@ -1649,6 +1659,101 @@ static int init_application(void) { .compositor = &flutter_compositor }; + if (flutterpi.flutter.runtime_mode == kRelease || flutterpi.flutter.runtime_mode == kProfile) { + const uint8_t *vm_instr, *vm_data, *isolate_instr, *isolate_data; + size_t vm_instr_size, vm_data_size, isolate_instr_size, isolate_data_size; + + app_elf_handle = dlopen(flutterpi.flutter.app_elf_path, RTLD_NOW | RTLD_LOCAL); + if (app_elf_handle == NULL) { + perror("[flutter-pi] Could not open \"app.so\". dlopen"); + return errno; + } + + vm_instr = dlsym(app_elf_handle, "_kDartVmSnapshotInstructions"); + if (vm_instr == NULL) { + perror("[flutter-pi] Could not resolve vm instructions section in \"app.so\". dlsym"); + dlclose(app_elf_handle); + return errno; + } + + vm_data = dlsym(app_elf_handle, "_kDartSnapshotData"); + if (vm_data == NULL) { + perror("[flutter-pi] Could not resolve vm data section in \"app.so\". dlsym"); + dlclose(app_elf_handle); + return errno; + } + + isolate_instr = dlsym(app_elf_handle, "_kDartIsolateSnapshotInstructions"); + if (isolate_instr == NULL) { + perror("[flutter-pi] Could not resolve isolate instructions section in \"app.so\". dlsym"); + dlclose(app_elf_handle); + return errno; + } + + isolate_data = dlsym(app_elf_handle, "_kDartIsolateSnapshotData"); + if (isolate_data == NULL) { + perror("[flutter-pi] Could not resolve isolate data section in \"app.so\". dlsym"); + dlclose(app_elf_handle); + return errno; + } + + // now find out the sizes for the sections... + int fd = open(flutterpi.flutter.app_elf_path, O_RDONLY); + + struct stat statbuf; + fstat(fd, &statbuf); + char *fbase = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + + Elf32_Ehdr *ehdr = (Elf32_Ehdr *) fbase; + Elf32_Shdr *sections = (Elf32_Shdr *) (fbase + ehdr->e_shoff); + + int section_header_size = ehdr->e_shentsize; + int section_header_num = ehdr->e_shnum; + int section_header_strndx = ehdr->e_shstrndx; + + Elf32_Shdr *shstr_section = sections + shstr_section->sh_offset; + char *shstrtab = fbase + shstr_section->sh_offset; + + + // Assume the first .text section is the vm instructions, + // the second .text is isolate instructions + // Assume the first .rodata section is the vm data, + // the second .rodata is isolate data + + vm_instr_size = 0; isolate_instr_size = 0; + vm_data_size = 0; isolate_data_size = 0; + for (int i = 0; i < section_header_num; i++) { + if (strcmp(shstrtab + sections[i].sh_name, ".text") == 0) { + if (vm_instr_size == 0) { + vm_instr_size = sections[i].sh_size; + } else if (isolate_instr_size == 0) { + isolate_instr_size = sections[i].sh_size; + } + } else if (strcmp(shstrtab + sections[i].sh_name, ".rodata") == 0) { + if (vm_data_size == 0) { + vm_data_size = sections[i].sh_size; + } else if (isolate_data_size == 0) { + isolate_data_size = sections[i].sh_size; + } + } + } + + munmap(fbase, statbuf.st_size); + close(fd); + + project_args.vm_snapshot_instructions = vm_instr; + project_args.vm_snapshot_instructions_size = vm_instr_size; + + project_args.isolate_snapshot_instructions = isolate_instr; + project_args.isolate_snapshot_instructions_size = isolate_instr_size; + + project_args.vm_snapshot_data = vm_data; + project_args.vm_snapshot_data_size = vm_data_size; + + project_args.isolate_snapshot_data = isolate_data; + project_args.isolate_snapshot_data_size = isolate_data_size; + } + // spin up the engine engine_result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config, &project_args, NULL, &flutterpi.flutter.engine); if (engine_result != kSuccess) { @@ -1723,6 +1828,8 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void .device_kind = kFlutterPointerDeviceKindMouse, .buttons = 0 }; + + compositor_set_cursor_enabled(true); } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { int touch_count = libinput_device_touch_get_touch_count(device); @@ -1762,8 +1869,10 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void FlutterPointerPhase phase; if (type == LIBINPUT_EVENT_TOUCH_DOWN) { phase = kDown; + printf("reporting touch: down at %03f, %03f\n", x, y); } else if (type == LIBINPUT_EVENT_TOUCH_MOTION) { phase = kMove; + printf("reporting touch: move at %03f, %03f\n", x, y); } pointer_events[n_pointer_events++] = (FlutterPointerEvent) { @@ -1784,12 +1893,14 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void data->y = y; data->timestamp = libinput_event_touch_get_time_usec(touch_event); } else { + printf("reporting touch: up\n"); + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), .phase = kUp, .timestamp = libinput_event_touch_get_time_usec(touch_event), - .x = 0.0, - .y = 0.0, + .x = data->x, + .y = data->y, .device = data->flutter_device_id_offset + slot, .signal_kind = kFlutterPointerSignalKindNone, .scroll_delta_x = 0.0, @@ -1804,7 +1915,7 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); if (type == LIBINPUT_EVENT_POINTER_MOTION) { - + } else if (type == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) { double x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, flutterpi.display.width); double y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, flutterpi.display.height); @@ -1825,6 +1936,8 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void .buttons = data->buttons }; + compositor_set_cursor_pos((int) round(x), (int) round(y)); + data->x = x; data->y = y; data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); @@ -2054,7 +2167,7 @@ static bool setup_paths(void) { static bool parse_cmd_args(int argc, char **argv) { glob_t input_devices_glob = {0}; bool input_specified = false; - int ok, opt, longopt_index = 0, runtime_mode_int = kDebug; + int opt, longopt_index = 0, runtime_mode_int = kDebug; struct option long_options[] = { {"release", no_argument, &runtime_mode_int, kRelease}, @@ -2114,7 +2227,7 @@ static bool parse_cmd_args(int argc, char **argv) { } flutterpi.view.rotation = rotation; - } else { + } else if ((opt == '?') || (opt == ':')) { printf("%s", usage); return false; } @@ -2124,15 +2237,6 @@ static bool parse_cmd_args(int argc, char **argv) { // user specified no input devices. use "/dev/input/event*"". glob("/dev/input/event*", GLOB_BRACE | GLOB_TILDE, NULL, &input_devices_glob); } - - if (runtime_mode_int != kDebug) { - fprintf( - stderr, - "flutter-pi doesn't support running apps in release or profile mode yet.\n" - "See https://github.com/ardera/flutter-pi/issues/65 for more info.\n" - ); - return false; - } if (optind >= argc) { fprintf(stderr, "error: expected asset bundle path after options.\n"); diff --git a/src/modesetting.c b/src/modesetting.c index 91edf0ea..1118e013 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -316,6 +316,16 @@ static int fetch_planes(struct drmdev *drmdev, struct drm_plane **planes_out, si drmModeFreePlane(plane); goto fail_free_planes; } + + if (strcmp(props_info[j]->name, "type") == 0) { + planes[i].type = 0; + for (int k = 0; k < props->count_props; k++) { + if (props->props[k] == props_info[j]->prop_id) { + planes[i].type = props->prop_values[k]; + break; + } + } + } } planes[i].plane = plane; @@ -554,6 +564,7 @@ int drmdev_new_atomic_req( struct drmdev_atomic_req **req_out ) { struct drmdev_atomic_req *req; + struct drm_plane *plane; int ok; req = calloc(1, sizeof *req); @@ -569,6 +580,12 @@ int drmdev_new_atomic_req( return ENOMEM; } + req->available_planes = PSET_INITIALIZER_STATIC(req->available_planes_storage, 32); + + for_each_plane_in_drmdev(drmdev, plane) { + pset_put(&req->available_planes, plane); + } + *req_out = req; return 0; diff --git a/src/platformchannel.c b/src/platformchannel.c index 7459f392..0337299f 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -591,7 +591,7 @@ int platch_decode_value_json(char *message, size_t size, jsmntok_t **pptoken, si jsmn_parser parser; size_t tokensremaining; - memset(tokens, sizeof(tokens), 0); + memset(tokens, 0, sizeof(tokens)); jsmn_init(&parser); result = jsmn_parse(&parser, (const char *) message, (const size_t) size, tokens, JSON_DECODE_TOKENLIST_SIZE); @@ -788,8 +788,6 @@ int platch_decode(uint8_t *buffer, size_t size, enum platch_codec codec, struct ok = _read8(&buffer_cursor, (uint8_t*) &object_out->success, &remaining); if (object_out->success) { - struct std_value result; - ok = platch_decode_value_std(&buffer_cursor, &remaining, &(object_out->std_result)); if (ok != 0) return ok; } else { diff --git a/src/pluginregistry.c b/src/pluginregistry.c index dbda3614..9778302f 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -28,7 +28,7 @@ # include #endif #ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN -# include +# include #endif @@ -186,6 +186,18 @@ int plugin_registry_set_receiver( return 0; } +bool plugin_registry_is_plugin_present( + const char *plugin_name +) { + for (int i = 0; i < plugin_registry.n_plugins; i++) { + if (strcmp(plugin_registry.plugins[i].name, plugin_name) == 0) { + return true; + } + } + + return false; +} + int plugin_registry_remove_receiver(const char *channel) { struct platch_obj_cb_data *data; @@ -222,7 +234,7 @@ int plugin_registry_deinit() { } for_each_pointer_in_cpset(&plugin_registry.platch_obj_cbs, data) { - cpset_remove_locked(&plugin_registry.platch_obj_cbs, data); + cpset_remove_(&plugin_registry.platch_obj_cbs, data); if (data != NULL) { free(data->channel); free(data); diff --git a/src/plugins/video_player.c b/src/plugins/omxplayer_video_player.c similarity index 98% rename from src/plugins/video_player.c rename to src/plugins/omxplayer_video_player.c index 7768f521..cd40c3c1 100644 --- a/src/plugins/video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -23,7 +23,7 @@ #include #include -#include +#include static struct { bool initialized; @@ -56,7 +56,7 @@ struct libsystemd libsystemd = {0}; /// Add a player instance to the player collection. int add_player(struct omxplayer_video_player *player) { - return cpset_put(&omxpvidpp.players, player); + return cpset_put_(&omxpvidpp.players, player); } /// Get a player instance by its id. @@ -92,8 +92,8 @@ struct omxplayer_video_player *get_player_by_evch(const char *const event_channe } /// Remove a player instance from the player collection. -static void *remove_player(struct omxplayer_video_player *player) { - return cpset_remove(&omxpvidpp.players, player); +static int remove_player(struct omxplayer_video_player *player) { + return cpset_remove_(&omxpvidpp.players, player); } /// Get the player id from the given arg, which is a kStdMap. @@ -435,7 +435,7 @@ static void *mgr_entry(void *userdata) { } // spawn the omxplayer process - current_zpos = -1; + current_zpos = -128; pid_t me = fork(); if (me == 0) { char orientation_str[16] = {0}; @@ -600,7 +600,21 @@ static void *mgr_entry(void *userdata) { has_scheduled_pause_time = false; while (1) { ok = cqueue_dequeue(q, &task); + + if (task.type == kUpdateView) { + struct omxplayer_mgr_task *peek; + + cqueue_lock(q); + + cqueue_peek_locked(q, (void**) &peek); + while ((peek != NULL) && (peek->type == kUpdateView)) { + cqueue_dequeue_locked(q, &task); + cqueue_peek_locked(q, (void**) &peek); + } + cqueue_unlock(q); + } + if (task.type == kCreate) { printf("[omxplayer_video_player plugin] Omxplayer manager got a creation task, even though the player is already running.\n"); } else if (task.type == kDispose) { @@ -757,6 +771,7 @@ static void *mgr_entry(void *userdata) { libsystemd.sd_bus_message_unref(msg); if (current_zpos != task.zpos) { + printf("setting omxplayer layer to %d\n", task.zpos); ok = libsystemd.sd_bus_call_method( bus, dbus_name, @@ -1424,7 +1439,7 @@ static int on_create( ok = cqueue_enqueue(&mgr->task_queue, &(const struct omxplayer_mgr_task) { .type = kCreate, .responsehandle = responsehandle, - .orientation = 0 //rotation + .orientation = flutterpi.view.rotation }); if (ok != 0) { @@ -1777,6 +1792,9 @@ static int on_receive_mch( return platch_respond_not_implemented(responsehandle); } +extern int8_t omxpvidpp_is_present(void) { + return plugin_registry_is_plugin_present("omxplayer_video_player"); +} int omxpvidpp_init(void) { int ok; From 78783f694ace6feb3503e73395dfc8ce4507dfbe Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 1 Aug 2020 18:51:26 +0200 Subject: [PATCH 10/14] mouse cursor support --- Makefile | 1 + include/compositor.h | 12 +- include/cursor.h | 16 + include/flutter-pi.h | 1 + src/compositor.c | 113 +- src/cursor.c | 1940 ++++++++++++++++++++++++++ src/flutter-pi.c | 70 +- src/plugins/omxplayer_video_player.c | 10 +- src/plugins/services.c | 3 + 9 files changed, 2114 insertions(+), 52 deletions(-) create mode 100644 include/cursor.h create mode 100644 src/cursor.c diff --git a/Makefile b/Makefile index 3886ae92..bdb74622 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ SOURCES = src/flutter-pi.c \ src/compositor.c \ src/modesetting.c \ src/collection.c \ + src/cursor.c \ src/plugins/services.c \ src/plugins/testplugin.c \ src/plugins/text_input.c \ diff --git a/include/compositor.h b/include/compositor.h index 0889190d..17deb66e 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -65,14 +65,6 @@ struct compositor { */ struct concurrent_pointer_set cbs; - /** - * @brief A set that contains a struct for each existing DRM primary/overlay plane, containing - * the DRM plane id and whether it's reserved. - * - * @see compositor_reserve_plane compositor_free_plane - */ - struct concurrent_pointer_set planes; - /** * @brief Whether the compositor should invoke @ref rendertarget_gbm_new the next time * flutter creates a backing store. Otherwise @ref rendertarget_nogbm_new is invoked. @@ -90,7 +82,6 @@ struct compositor { */ bool has_applied_modeset; - FlutterCompositor flutter_compositor; /** @@ -105,7 +96,6 @@ struct compositor { /** * @brief Whether the mouse cursor is currently enabled and visible. */ - bool is_cursor_enabled; struct { bool is_enabled; @@ -249,6 +239,8 @@ int compositor_set_cursor_enabled(bool enabled); int compositor_set_cursor_pos(int x, int y); +int compositor_apply_cursor_skin_for_rotation(int rotation); + int compositor_initialize( struct drmdev *drmdev ); diff --git a/include/cursor.h b/include/cursor.h new file mode 100644 index 00000000..329b7c33 --- /dev/null +++ b/include/cursor.h @@ -0,0 +1,16 @@ +#ifndef _CURSOR_H +#define _CURSOR_H + +#include + +struct cursor_icon { + int rotation; + int hot_x, hot_y; + int width, height; + uint32_t *data; +}; + +extern const struct cursor_icon cursors[4]; +extern int n_cursors; + +#endif \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 2ee31ca2..ccd2a01c 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -267,6 +267,7 @@ struct flutterpi { sd_event_source *libinput_event_source; sd_event_source *stdin_event_source; int64_t next_unused_flutter_device_id; + double cursor_x, cursor_y; } input; /// flutter stuff diff --git a/src/compositor.c b/src/compositor.c index e50771d4..16fbc1fc 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -17,6 +17,7 @@ #include #include #include +#include struct view_cb_data { int64_t view_id; @@ -46,10 +47,23 @@ struct plane_data { struct compositor compositor = { .drmdev = NULL, .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), - .planes = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), .has_applied_modeset = false, .should_create_window_surface_backing_store = true, - .is_cursor_enabled = false + .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .cursor = { + .is_enabled = false, + .width = 0, + .height = 0, + .bpp = 0, + .depth = 0, + .pitch = 0, + .size = 0, + .drm_fb_id = 0, + .gem_bo_handle = 0, + .buffer = NULL, + .x = 0, + .y = 0 + } }; static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { @@ -697,7 +711,6 @@ static bool on_create_backing_store( return true; } - /// PRESENT FUNCS static bool on_present_layers( const FlutterLayer **layers, @@ -706,8 +719,8 @@ static bool on_present_layers( ) { struct drmdev_atomic_req *req; struct view_cb_data *cb_data; - struct plane_data *plane; struct compositor *compositor; + struct drm_plane *plane; uint32_t req_flags; int ok; @@ -804,7 +817,7 @@ static bool on_present_layers( if (layers[i]->type == kFlutterLayerContentTypePlatformView && layers[i]->platform_view->identifier == cb_data->view_id) { layer = layers[i]; - zpos = i - 127; + zpos = i; break; } } @@ -842,7 +855,7 @@ static bool on_present_layers( if (layers[i]->type == kFlutterLayerContentTypePlatformView && layers[i]->platform_view->identifier == cb_data->view_id) { layer = layers[i]; - zpos = i - 127; + zpos = i; break; } } @@ -864,29 +877,35 @@ static bool on_present_layers( fprintf(stderr, "[compositor] Could not mount platform view. %s\n", strerror(ok)); } } + + cb_data->last_zpos = zpos; + cb_data->last_size = layer->size; + cb_data->last_offset = layer->offset; + cb_data->last_num_mutations = layer->platform_view->mutations_count; + for (int i = 0; i < layer->platform_view->mutations_count; i++) { + memcpy(cb_data->last_mutations + i, layer->platform_view->mutations[i], sizeof(FlutterPlatformViewMutation)); + } } } for (int i = 0; i < layers_count; i++) { - struct drm_plane *drm_plane; - if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - for_each_unreserved_plane_in_atomic_req(req, drm_plane) { + for_each_unreserved_plane_in_atomic_req(req, plane) { // choose a plane which has an "intrinsic" zpos that matches // the zpos we want the plane to have. // (Since planes are buggy and we can't rely on the zpos we explicitly // configure the plane to have to be actually applied to the hardware. // In short, assigning a different value to the zpos property won't always // take effect.) - if ((i == 0) && (drm_plane->type == DRM_PLANE_TYPE_PRIMARY)) { - drmdev_atomic_req_reserve_plane(req, drm_plane); + if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { + drmdev_atomic_req_reserve_plane(req, plane); break; - } else if ((i != 0) && (drm_plane->type == DRM_PLANE_TYPE_OVERLAY)) { - drmdev_atomic_req_reserve_plane(req, drm_plane); + } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { + drmdev_atomic_req_reserve_plane(req, plane); break; } } - if (drm_plane == NULL) { + if (plane == NULL) { fprintf(stderr, "[compositor] Could not find a free primary/overlay DRM plane for presenting the backing store. drmdev_atomic_req_reserve_plane: %s\n", strerror(ok)); continue; } @@ -897,12 +916,12 @@ static bool on_present_layers( ok = target->present( target, req, - drm_plane->plane->plane_id, + plane->plane->plane_id, 0, 0, compositor->drmdev->selected_mode->hdisplay, compositor->drmdev->selected_mode->vdisplay, - i + plane->type == DRM_PLANE_TYPE_PRIMARY ? 0 : 1 ); if (ok != 0) { fprintf(stderr, "[compositor] Could not present backing store. rendertarget->present: %s\n", strerror(ok)); @@ -920,7 +939,7 @@ static bool on_present_layers( (int) round(layers[i]->offset.y), (int) round(layers[i]->size.width), (int) round(layers[i]->size.height), - i - 127, + i, cb_data->userdata ); if (ok != 0) { @@ -930,13 +949,16 @@ static bool on_present_layers( } } + for_each_unreserved_plane_in_atomic_req(req, plane) { + drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB", 0); + } + eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); FlutterEngineTraceEventDurationBegin("drmdev_atomic_req_commit"); drmdev_atomic_req_commit(req, req_flags, NULL); FlutterEngineTraceEventDurationEnd("drmdev_atomic_req_commit"); - cpset_unlock(&compositor->planes); cpset_unlock(&compositor->cbs); } @@ -1042,6 +1064,41 @@ int compositor_initialize(struct drmdev *drmdev) { return 0; } +int compositor_apply_cursor_skin_for_rotation(int rotation) { + const struct cursor_icon *cursor; + int ok; + + if (compositor.cursor.is_enabled) { + cursor = NULL; + for (int i = 0; i < n_cursors; i++) { + if (cursors[i].rotation == rotation) { + cursor = cursors + i; + break; + } + } + + memcpy(compositor.cursor.buffer, cursor->data, compositor.cursor.size); + + ok = drmModeSetCursor2( + compositor.drmdev->fd, + compositor.drmdev->selected_crtc->crtc->crtc_id, + compositor.cursor.gem_bo_handle, + compositor.cursor.width, + compositor.cursor.height, + cursor->hot_x, + cursor->hot_y + ); + if (ok < 0) { + perror("[compositor] Could not set the mouse cursor buffer. drmModeSetCursor"); + return errno; + } + + return 0; + } + + return 0; +} + int compositor_set_cursor_enabled(bool enabled) { if ((compositor.cursor.is_enabled == false) && (enabled == true)) { struct drm_mode_create_dumb create_req; @@ -1104,21 +1161,6 @@ int compositor_set_cursor_enabled(bool enabled) { return errno; } - memset(buffer, 0, create_req.size); - buffer[0] = 0xFFFFFF; - - ok = drmModeSetCursor( - compositor.drmdev->fd, - compositor.drmdev->selected_crtc->crtc->crtc_id, - create_req.handle, - create_req.width, - create_req.height - ); - if (ok < 0) { - perror("[compositor] Could not set the mouse cursor buffer. drmModeSetCursor"); - return errno; - } - compositor.cursor.is_enabled = true; compositor.cursor.width = create_req.width; compositor.cursor.height = create_req.height; @@ -1166,13 +1208,16 @@ int compositor_set_cursor_enabled(bool enabled) { int compositor_set_cursor_pos(int x, int y) { int ok; - + ok = drmModeMoveCursor(compositor.drmdev->fd, compositor.drmdev->selected_crtc->crtc->crtc_id, x, y); if (ok < 0) { perror("[compositor] Could not move cursor. drmModeMoveCursor"); return errno; } + compositor.cursor.x = x; + compositor.cursor.y = y; + return 0; } diff --git a/src/cursor.c b/src/cursor.c new file mode 100644 index 00000000..b679cada --- /dev/null +++ b/src/cursor.c @@ -0,0 +1,1940 @@ +#include + +const unsigned char cursor_0_data[64*64*4 + 1] = + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000.\000\000\000\021\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\322\000\000\000\030" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377" + "\000\000\000\334\000\000\000!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000" + "\000\000\377\000\000\000\377\000\000\000\345\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\354\000\000\000\065\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\363" + "\000\000\000A\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\370\000\000\000N\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000\\\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000l\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000}\000\000\000\001\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\216\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\236\000\000\000\004\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\255\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\014\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\307\000\000\000\022\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\323" + "\000\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000!\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\345\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\355" + "\000\000\000\065\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\363\000\000\000A\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000O\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\374\000\000\000]\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000m\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000~\000" + "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\217\000\000\000\002" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\237\000\000\000\004\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\256\000\000\000\007\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\273\000\000\000\014\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\310\000\000\000\022\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\323\000\000\000\031\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\345\000\000\000\317\000\000\000\270" + "\000\000\000\242\000\000\000\214\000\000\000v\000\000\000`\000\000\000I\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\244\000\000\000j\000\000\000T\000\000\000>\000\000\000(\000\000" + "\000\021\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\240\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\372\000\000\000\033\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\334\000\000\000I\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\210\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000\000\032\000\000\000\000\000\000\000\272\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\015\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\316\000\000" + "\000\024\000\000\000\000\000\000\000\000\000\000\000L\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000o\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\305\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\333\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\340\000\000\000\004\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\275\000\000\000\014\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000n\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000W\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\263\000\000\000\010\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016\000\000\000\362\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\314\000\000\000\001\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\251\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\221\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\377" + "\000\000\000\377\000\000\000\236\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\263\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000X\000\000\000\377\000\000\000\222\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\263\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000)\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000X\000\000\000\206\000\000\000\001\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000E\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\232\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\017\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\325\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000\027\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000g\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\202\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\013\000\000\000\357\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\355\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\212\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000j\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\037\000\000\000\375\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000\003\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\255\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000R\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000>\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\306\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\317\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\255\000\000\000:\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000a\000\000\000\377\000\000\000\366\000\000\000\224\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000w\000\000\000\023\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; + +const unsigned char cursor_90_data[64*64*4 + 1] = + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\017\000\000\000X\000\000\000X\000\000\000X\000" + "\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000" + "\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000" + "X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000" + "\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000.\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\206\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\322\000\000\000\021\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\222\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\334\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\236" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\345\000\000\000!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\003\000\000\000\251\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\354\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\005\000\000\000\263\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\363\000\000\000\065\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\275\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370" + "\000\000\000A\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\305\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000N\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\316\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\\\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\024\000\000\000\325\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000l\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "}\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000I\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\216\000\000\000\001\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000L\000\000\000\272\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\236\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\016\000\000\000n\000\000\000\333\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\255\000" + "\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$\000\000\000\221\000" + "\000\000\362\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272\000\000\000\007\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000E\000\000\000\263\000\000\000\376\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\307\000\000\000\014\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\013\000\000\000g\000\000\000\325\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\323\000\000\000\022\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\037\000\000\000\212\000\000\000\357" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\372\000\000\000\240\000\000" + "\000\244\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\334\000\000\000\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000>\000\000\000\255\000\000\000\375\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\210\000\000\000\033\000\000\000\000\000\000\000j" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\345\000\000" + "\000!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\010\000\000\000a\000\000\000\317\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\340\000\000\000o\000\000\000\015\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000T\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\355\000\000\000+\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000w\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\314\000\000\000W\000\000\000\004\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000>\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\363\000\000\000\065\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000\366\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\263\000\000\000?\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000(\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000A\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\224\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000\232\000\000\000)\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\021\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\374\000\000\000O\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000$\000\000" + "\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\355\000\000\000\202\000\000\000" + "\027\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\373\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000]\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\255\000\000\000\377\000\000\000\377\000\000\000\334" + "\000\000\000j\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\345" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000m\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000:\000\000\000" + "\306\000\000\000R\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\317\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000~\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\270\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\217\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\242\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\237\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\214\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\256\000\000\000\004\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000v\000\000\000\377\000\000\000\377\000\000\000\273\000\000\000\007\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`\000\000\000\377\000\000\000\310\000\000\000\014\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000I\000\000\000\323\000\000\000\022\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\040\000\000\000\031\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000"; + +const unsigned char cursor_180_data[64*64*4 + 1] = + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\023\000\000\000w\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000$\000\000\000\224\000\000\000\366\000\000\000\377\000\000\000a\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000:\000\000\000\255\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\317\000" + "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\306\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000>\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000R\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\255\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000" + "\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000" + "\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000j\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\212\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\012\000\000\000\355\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\013\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\202\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000g\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\027\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\325\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\232\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000E\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\001\000\000\000\206\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000)\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\263" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\222\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\263\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\376\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000\000\000\236\000\000\000\377\000\000\000\377\000\000" + "\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000?\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\221\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\005\000\000\000\251\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\001\000\000\000\314\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\362\000\000\000\016\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010" + "\000\000\000\263\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000W\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000n\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\014\000\000\000\275\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\004\000\000\000\340\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333" + "\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\305\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000o\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000L\000\000\000\000\000\000\000\000\000\000\000\024\000\000\000\316" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\015\000\000\000\357\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\272" + "\000\000\000\000\000\000\000\032\000\000\000\325\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\210\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000I\000\000\000\334\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\033\000\000\000\372\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\240\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\021\000\000\000(\000\000" + "\000>\000\000\000T\000\000\000j\000\000\000\244\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\040\000\000\000I\000\000\000`\000\000\000v\000\000\000\214\000\000\000\242\000\000\000\270\000\000\000" + "\317\000\000\000\345\000\000\000\373\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031" + "\000\000\000\323\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\022\000\000\000\310\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\014\000\000\000\273\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\007\000\000\000\256\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004" + "\000\000\000\237\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000" + "\000\217\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000~\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000m\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000]\000\000\000\374\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000O\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000A\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\065\000\000\000\355\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000+\000\000\000\345\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000!\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\323\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\022\000\000\000\307\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014" + "\000\000\000\272\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\255\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\004\000\000\000\236\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\216\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\001\000\000\000}\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000l\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\\\000\000\000\374\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000N\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000A\000\000\000\363\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\065\000\000\000\354\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000X\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000*\000\000\000\345\000\000\000\377\000\000\000\377\000\000\000X\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\000\000\000\334\000\000\000\377\000" + "\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030\000\000" + "\000\322\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\021\000\000\000.\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; + +const unsigned char cursor_270_data[64*64*4 + 1] = + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\031\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\022\000\000\000\323\000\000\000I\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\310\000\000\000\377\000\000\000`\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\273\000\000\000\377\000\000\000\377\000\000\000v\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\256\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\214\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\237\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\242\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\217\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\270\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000~\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\317\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\003\000\000\000R\000\000\000\306\000\000\000:\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000m\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\345\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\012\000\000\000j\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\255\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000]\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\001\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\027\000\000\000\202\000\000\000\355\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\376\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000O\000\000" + "\000\374\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\021\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000)\000\000\000\232\000\000\000\370\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\224" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000A\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000(\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000" + "?\000\000\000\263\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\366\000\000\000\023\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\065" + "\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000>\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000W\000\000\000\314\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000w\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000+\000\000\000\355\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000T\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\015\000\000" + "\000o\000\000\000\340\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\317\000\000\000a\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000!\000\000\000\345\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000j\000\000\000\000\000\000\000\033\000\000\000\210\000\000\000\357\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\255\000\000\000>\000\000\000\001" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\031\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\244\000\000\000\240\000\000\000\372\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\357\000\000\000\212\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\022\000" + "\000\000\323\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\325\000\000\000g\000\000\000\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\307\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000\263\000\000\000E\000\000\000\001\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\272\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\362\000\000\000\221\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\004\000\000\000\255\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\333\000\000\000n\000\000\000" + "\016\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000" + "\236\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\272\000\000\000L\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\216\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000I\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000}\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000\032\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000l\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000\000\024\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\316\000\000\000\020\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000N\000\000\000\374\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\305\000\000\000\014\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000A\000\000\000\370\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\275\000\000\000\010\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\065\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\263\000\000" + "\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000*\000\000\000\354\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\251\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "!\000\000\000\345\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\236\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\030\000\000\000\334\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\222\000\000\000\001\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\021\000\000\000\322\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\206\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000.\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000" + "\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000" + "\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000" + "X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000" + "\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000X\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000"; + +const struct cursor_icon cursors[4] = { + { + .rotation = 0, + .hot_x = 18, + .hot_y = 5, + .width = 64, + .height = 64, + .data = (uint32_t*) cursor_0_data + }, + { + .rotation = 90, + .hot_x = 58, + .hot_y = 18, + .width = 64, + .height = 64, + .data = (uint32_t*) cursor_90_data + }, + { + .rotation = 180, + .hot_x = 45, + .hot_y = 58, + .width = 64, + .height = 64, + .data = (uint32_t*) cursor_180_data + }, + { + .rotation = 270, + .hot_x = 5, + .hot_y = 45, + .width = 64, + .height = 64, + .data = (uint32_t*) cursor_270_data + } +}; + +int n_cursors = sizeof(cursors) / sizeof(*cursors); \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 9bd759aa..fe126e3e 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1815,6 +1815,8 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void libinput_device_set_user_data(device, data); if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { + printf("pointer device was added\n"); + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), .phase = kAdd, @@ -1830,6 +1832,7 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void }; compositor_set_cursor_enabled(true); + compositor_apply_cursor_skin_for_rotation(flutterpi.view.rotation); } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { int touch_count = libinput_device_touch_get_touch_count(device); @@ -1915,13 +1918,65 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); if (type == LIBINPUT_EVENT_POINTER_MOTION) { - + double dx = libinput_event_pointer_get_dx(pointer_event); + double dy = libinput_event_pointer_get_dy(pointer_event); + + data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); + + apply_flutter_transformation(FLUTTER_ROTZ_TRANSFORMATION(flutterpi.view.rotation), &dx, &dy); + + double newx = flutterpi.input.cursor_x + dx; + double newy = flutterpi.input.cursor_y + dy; + + if (newx < 0) { + newx = 0; + } else if (newx > flutterpi.display.width - 1) { + newx = flutterpi.display.width - 1; + } + + if (newy < 0) { + newy = 0; + } else if (newy > flutterpi.display.height - 1) { + newy = flutterpi.display.height - 1; + } + + flutterpi.input.cursor_x = newx; + flutterpi.input.cursor_y = newy; + + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &newx, &newy); + + printf("cursor: %03f, %03f\n", newx, newy); + + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = data->buttons & kFlutterPointerButtonMousePrimary ? kMove : kHover, + .timestamp = libinput_event_pointer_get_time_usec(pointer_event), + .x = newx, + .y = newy, + .device = data->flutter_device_id_offset, + .signal_kind = kFlutterPointerSignalKindNone, + .scroll_delta_x = 0.0, + .scroll_delta_y = 0.0, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = data->buttons + }; + + compositor_set_cursor_pos(round(flutterpi.input.cursor_x), round(flutterpi.input.cursor_y)); } else if (type == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) { double x = libinput_event_pointer_get_absolute_x_transformed(pointer_event, flutterpi.display.width); double y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, flutterpi.display.height); + flutterpi.input.cursor_x = x; + flutterpi.input.cursor_y = y; + + data->x = x; + data->y = y; + data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &x, &y); + printf("cursor: %03f, %03f\n", x, y); + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), .phase = data->buttons & kFlutterPointerButtonMousePrimary ? kMove : kHover, @@ -1937,10 +1992,6 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void }; compositor_set_cursor_pos((int) round(x), (int) round(y)); - - data->x = x; - data->y = y; - data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); } else if (type == LIBINPUT_EVENT_POINTER_BUTTON) { uint32_t button = libinput_event_pointer_get_button(pointer_event); enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); @@ -1975,12 +2026,17 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void phase = kMove; } + double x = flutterpi.input.cursor_x; + double y = flutterpi.input.cursor_y; + + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &x, &y); + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), .phase = phase, .timestamp = libinput_event_pointer_get_time_usec(pointer_event), - .x = data->x, - .y = data->y, + .x = x, + .y = y, .device = data->flutter_device_id_offset, .signal_kind = kFlutterPointerSignalKindNone, .scroll_delta_x = 0.0, diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c index cd40c3c1..10713085 100644 --- a/src/plugins/omxplayer_video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -179,6 +179,10 @@ static int on_mount( ) { struct omxplayer_video_player *player = userdata; + if (zpos == 1) { + zpos = -126; + } + return cqueue_enqueue( &player->mgr->task_queue, &(struct omxplayer_mgr_task) { @@ -231,6 +235,10 @@ static int on_update_view( ) { struct omxplayer_video_player *player = userdata; + if (zpos == 1) { + zpos = -126; + } + return cqueue_enqueue( &player->mgr->task_queue, &(struct omxplayer_mgr_task) { @@ -1792,7 +1800,7 @@ static int on_receive_mch( return platch_respond_not_implemented(responsehandle); } -extern int8_t omxpvidpp_is_present(void) { +int8_t omxpvidpp_is_present(void) { return plugin_registry_is_plugin_present("omxplayer_video_player"); } diff --git a/src/plugins/services.c b/src/plugins/services.c index 6cf30eff..1eba5802 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -3,6 +3,7 @@ #include #include +#include #include static struct { @@ -113,6 +114,8 @@ static int on_receive_platform(char *channel, struct platch_obj *object, Flutter flutterpi_fill_view_properties(true, i, false, 0); + compositor_apply_cursor_skin_for_rotation(flutterpi.view.rotation); + // send updated window metrics to flutter result = FlutterEngineSendWindowMetricsEvent(flutterpi.flutter.engine, &(const FlutterWindowMetricsEvent) { .struct_size = sizeof(FlutterWindowMetricsEvent), From c4a12a4087915f51e812fbe1d1ea32e9dfa7f673 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 4 Aug 2020 18:34:01 +0200 Subject: [PATCH 11/14] - only load libsystemd once - fix the video player plugin reporting errors when none happened - raw-keyboard plugin fixups --- CMakeLists.txt | 39 ++- include/flutter-pi.h | 4 + include/plugins/omxplayer_video_player.h | 350 ------------------- src/flutter-pi.c | 90 +++-- src/plugins/omxplayer_video_player.c | 418 ++--------------------- 5 files changed, 103 insertions(+), 798 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e2d8854..876312d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,19 +99,26 @@ pkg_check_modules(GBM REQUIRED gbm) pkg_check_modules(DRM REQUIRED libdrm) pkg_check_modules(GLESV2 REQUIRED glesv2) pkg_check_modules(EGL REQUIRED egl) +pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) +pkg_check_modules(LIBUDEV REQUIRED libudev) pkg_check_modules(GPIOD libgpiod) set(FLUTTER_PI_SRC - src/flutter-pi.c - src/platformchannel.c - src/pluginregistry.c - src/console_keyboard.c - src/plugins/services.c - src/plugins/testplugin.c - src/plugins/text_input.c - src/plugins/raw_keyboard.c - src/plugins/spidev.c - src/plugins/omxplayer_video_player.c + src/flutter-pi.c \ + src/platformchannel.c \ + src/pluginregistry.c \ + src/console_keyboard.c \ + src/texture_registry.c \ + src/compositor.c \ + src/modesetting.c \ + src/collection.c \ + src/cursor.c \ + src/plugins/services.c \ + src/plugins/testplugin.c \ + src/plugins/text_input.c \ + src/plugins/raw_keyboard.c \ + src/plugins/spidev.c \ + src/plugins/omxplayer_video_player.c ) if(GPIOD_FOUND) @@ -141,10 +148,14 @@ target_compile_options(flutter-pi PRIVATE ${GBM_CFLAGS} ${DRM_CFLAGS} ${GLESV2_CFLAGS} ${EGL_CFLAGS} ${GPIOD_CFLAGS} -ggdb - -DBUILD_TEXT_INPUT_PLUGIN - -DBUILD_SPIDEV_PLUGIN - -DBUILD_TEST_PLUGIN - -DBUILD_VIDEO_PLAYER_PLUGIN + -DBUILD_TEXT_INPUT_PLUGIN + -DBUILD_SPIDEV_PLUGIN + -DBUILD_TEST_PLUGIN + -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN +) + +target_link_options(flutter-pi PRIVATE + -rdynamic ) install(TARGETS flutter-pi RUNTIME DESTINATION bin) diff --git a/include/flutter-pi.h b/include/flutter-pi.h index ccd2a01c..7e425f71 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -139,6 +139,9 @@ static inline void apply_flutter_transformation( ((event_type) == LIBINPUT_EVENT_POINTER_BUTTON) || \ ((event_type) == LIBINPUT_EVENT_POINTER_AXIS)) +#define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (\ + ((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) + enum frame_state { kFramePending, kFrameRendering, @@ -261,6 +264,7 @@ struct flutterpi { /// IO struct { bool use_paths; + bool disable_text_input; glob_t input_devices_glob; struct udev *udev; struct libinput *libinput; diff --git a/include/plugins/omxplayer_video_player.h b/include/plugins/omxplayer_video_player.h index 6c1546a9..7e8fdac1 100644 --- a/include/plugins/omxplayer_video_player.h +++ b/include/plugins/omxplayer_video_player.h @@ -12,18 +12,6 @@ #include -#define LOAD_LIBSYSTEMD_PROC(name) \ - do { \ - libsystemd.name = dlsym(libsystemd.handle, #name); \ - if (!libsystemd.name) {\ - perror("could not resolve dbus procedure " #name); \ - return errno; \ - } \ - } while (false) - -#define LOAD_LIBSYSTEMD_PROC_OPTIONAL(name) \ - libsystemd.name = dlsym(libsystemd.handle, #name) - #define DBUS_OMXPLAYER_OBJECT "/org/mpris/MediaPlayer2" #define DBUS_OMXPLAYER_PLAYER_FACE "org.mpris.MediaPlayer2.Player" #define DBUS_OMXPLAYER_ROOT_FACE "org.mpris.MediaPlayer2" @@ -31,344 +19,6 @@ #define DBUS_PROPERTY_GET "Get" #define DBUS_PROPRETY_SET "Set" -struct libsystemd { - void *handle; - - int (*sd_bus_default)(sd_bus **ret); - int (*sd_bus_default_user)(sd_bus **ret); - int (*sd_bus_default_system)(sd_bus **ret); - - int (*sd_bus_open)(sd_bus **ret); - int (*sd_bus_open_with_description)(sd_bus **ret, const char *description); - int (*sd_bus_open_user)(sd_bus **ret); - int (*sd_bus_open_user_with_description)(sd_bus **ret, const char *description); - int (*sd_bus_open_system)(sd_bus **ret); - int (*sd_bus_open_system_with_description)(sd_bus **ret, const char *description); - int (*sd_bus_open_system_remote)(sd_bus **ret, const char *host); - int (*sd_bus_open_system_machine)(sd_bus **ret, const char *machine); - - int (*sd_bus_new)(sd_bus **ret); - - int (*sd_bus_set_address)(sd_bus *bus, const char *address); - int (*sd_bus_set_fd)(sd_bus *bus, int input_fd, int output_fd); - int (*sd_bus_set_exec)(sd_bus *bus, const char *path, char *const argv[]); - int (*sd_bus_get_address)(sd_bus *bus, const char **address); - int (*sd_bus_set_bus_client)(sd_bus *bus, int b); - int (*sd_bus_is_bus_client)(sd_bus *bus); - int (*sd_bus_set_server)(sd_bus *bus, int b, sd_id128_t bus_id); - int (*sd_bus_is_server)(sd_bus *bus); - int (*sd_bus_set_anonymous)(sd_bus *bus, int b); - int (*sd_bus_is_anonymous)(sd_bus *bus); - int (*sd_bus_set_trusted)(sd_bus *bus, int b); - int (*sd_bus_is_trusted)(sd_bus *bus); - int (*sd_bus_set_monitor)(sd_bus *bus, int b); - int (*sd_bus_is_monitor)(sd_bus *bus); - int (*sd_bus_set_description)(sd_bus *bus, const char *description); - int (*sd_bus_get_description)(sd_bus *bus, const char **description); - int (*sd_bus_negotiate_creds)(sd_bus *bus, int b, uint64_t creds_mask); - int (*sd_bus_negotiate_timestamp)(sd_bus *bus, int b); - int (*sd_bus_negotiate_fds)(sd_bus *bus, int b); - int (*sd_bus_can_send)(sd_bus *bus, char type); - int (*sd_bus_get_creds_mask)(sd_bus *bus, uint64_t *creds_mask); - int (*sd_bus_set_allow_interactive_authorization)(sd_bus *bus, int b); - int (*sd_bus_get_allow_interactive_authorization)(sd_bus *bus); - int (*sd_bus_set_exit_on_disconnect)(sd_bus *bus, int b); - int (*sd_bus_get_exit_on_disconnect)(sd_bus *bus); - int (*sd_bus_set_close_on_exit)(sd_bus *bus, int b); - int (*sd_bus_get_close_on_exit)(sd_bus *bus); - int (*sd_bus_set_watch_bind)(sd_bus *bus, int b); - int (*sd_bus_get_watch_bind)(sd_bus *bus); - int (*sd_bus_set_connected_signal)(sd_bus *bus, int b); - int (*sd_bus_get_connected_signal)(sd_bus *bus); - int (*sd_bus_set_sender)(sd_bus *bus, const char *sender); - int (*sd_bus_get_sender)(sd_bus *bus, const char **ret); - - int (*sd_bus_start)(sd_bus *bus); - - int (*sd_bus_try_close)(sd_bus *bus); - void (*sd_bus_close)(sd_bus *bus); - - sd_bus *(*sd_bus_ref)(sd_bus *bus); - sd_bus *(*sd_bus_unref)(sd_bus *bus); - sd_bus *(*sd_bus_close_unref)(sd_bus *bus); - sd_bus *(*sd_bus_flush_close_unref)(sd_bus *bus); - - void (*sd_bus_default_flush_close)(void); - - int (*sd_bus_is_open)(sd_bus *bus); - int (*sd_bus_is_ready)(sd_bus *bus); - - int (*sd_bus_get_bus_id)(sd_bus *bus, sd_id128_t *id); - int (*sd_bus_get_scope)(sd_bus *bus, const char **scope); - int (*sd_bus_get_tid)(sd_bus *bus, pid_t *tid); - int (*sd_bus_get_owner_creds)(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); - - int (*sd_bus_send)(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); - int (*sd_bus_send_to)(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); - int (*sd_bus_call)(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); - int (*sd_bus_call_async)(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); - - int (*sd_bus_get_fd)(sd_bus *bus); - int (*sd_bus_get_events)(sd_bus *bus); - int (*sd_bus_get_timeout)(sd_bus *bus, uint64_t *timeout_usec); - int (*sd_bus_process)(sd_bus *bus, sd_bus_message **r); - int (*sd_bus_process_priority)(sd_bus *bus, int64_t max_priority, sd_bus_message **r); - int (*sd_bus_wait)(sd_bus *bus, uint64_t timeout_usec); - int (*sd_bus_flush)(sd_bus *bus); - - sd_bus_slot* (*sd_bus_get_current_slot)(sd_bus *bus); - sd_bus_message* (*sd_bus_get_current_message)(sd_bus *bus); - sd_bus_message_handler_t (*sd_bus_get_current_handler)(sd_bus *bus); - void* (*sd_bus_get_current_userdata)(sd_bus *bus); - - int (*sd_bus_attach_event)(sd_bus *bus, sd_event *e, int priority); - int (*sd_bus_detach_event)(sd_bus *bus); - sd_event *(*sd_bus_get_event)(sd_bus *bus); - - int (*sd_bus_get_n_queued_read)(sd_bus *bus, uint64_t *ret); - int (*sd_bus_get_n_queued_write)(sd_bus *bus, uint64_t *ret); - - int (*sd_bus_set_method_call_timeout)(sd_bus *bus, uint64_t usec); - int (*sd_bus_get_method_call_timeout)(sd_bus *bus, uint64_t *ret); - - int (*sd_bus_add_filter)(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_add_match)(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_add_match_async)(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata); - int (*sd_bus_add_object)(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_add_fallback)(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_add_object_vtable)(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); - int (*sd_bus_add_fallback_vtable)(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); - int (*sd_bus_add_node_enumerator)(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); - int (*sd_bus_add_object_manager)(sd_bus *bus, sd_bus_slot **slot, const char *path); - - sd_bus_slot* (*sd_bus_slot_ref)(sd_bus_slot *slot); - sd_bus_slot* (*sd_bus_slot_unref)(sd_bus_slot *slot); - - sd_bus* (*sd_bus_slot_get_bus)(sd_bus_slot *slot); - void *(*sd_bus_slot_get_userdata)(sd_bus_slot *slot); - void *(*sd_bus_slot_set_userdata)(sd_bus_slot *slot, void *userdata); - int (*sd_bus_slot_set_description)(sd_bus_slot *slot, const char *description); - int (*sd_bus_slot_get_description)(sd_bus_slot *slot, const char **description); - int (*sd_bus_slot_get_floating)(sd_bus_slot *slot); - int (*sd_bus_slot_set_floating)(sd_bus_slot *slot, int b); - int (*sd_bus_slot_set_destroy_callback)(sd_bus_slot *s, sd_bus_destroy_t callback); - int (*sd_bus_slot_get_destroy_callback)(sd_bus_slot *s, sd_bus_destroy_t *callback); - - sd_bus_message* (*sd_bus_slot_get_current_message)(sd_bus_slot *slot); - sd_bus_message_handler_t (*sd_bus_slot_get_current_handler)(sd_bus_slot *slot); - void *(*sd_bus_slot_get_current_userdata)(sd_bus_slot *slot); - - int (*sd_bus_message_new)(sd_bus *bus, sd_bus_message **m, uint8_t type); - int (*sd_bus_message_new_signal)(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); - int (*sd_bus_message_new_method_call)(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); - int (*sd_bus_message_new_method_return)(sd_bus_message *call, sd_bus_message **m); - int (*sd_bus_message_new_method_error)(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); - int (*sd_bus_message_new_method_errorf)(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); - int (*sd_bus_message_new_method_errno)(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); - int (*sd_bus_message_new_method_errnof)(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); - - sd_bus_message* (*sd_bus_message_ref)(sd_bus_message *m); - sd_bus_message* (*sd_bus_message_unref)(sd_bus_message *m); - - int (*sd_bus_message_seal)(sd_bus_message *m, uint64_t cookie, uint64_t timeout_usec); - - int (*sd_bus_message_get_type)(sd_bus_message *m, uint8_t *type); - int (*sd_bus_message_get_cookie)(sd_bus_message *m, uint64_t *cookie); - int (*sd_bus_message_get_reply_cookie)(sd_bus_message *m, uint64_t *cookie); - int (*sd_bus_message_get_priority)(sd_bus_message *m, int64_t *priority); - - int (*sd_bus_message_get_expect_reply)(sd_bus_message *m); - int (*sd_bus_message_get_auto_start)(sd_bus_message *m); - int (*sd_bus_message_get_allow_interactive_authorization)(sd_bus_message *m); - - const char *(*sd_bus_message_get_signature)(sd_bus_message *m, int complete); - const char *(*sd_bus_message_get_path)(sd_bus_message *m); - const char *(*sd_bus_message_get_interface)(sd_bus_message *m); - const char *(*sd_bus_message_get_member)(sd_bus_message *m); - const char *(*sd_bus_message_get_destination)(sd_bus_message *m); - const char *(*sd_bus_message_get_sender)(sd_bus_message *m); - const sd_bus_error *(*sd_bus_message_get_error)(sd_bus_message *m); - int (*sd_bus_message_get_errno)(sd_bus_message *m); - - int (*sd_bus_message_get_monotonic_usec)(sd_bus_message *m, uint64_t *usec); - int (*sd_bus_message_get_realtime_usec)(sd_bus_message *m, uint64_t *usec); - int (*sd_bus_message_get_seqnum)(sd_bus_message *m, uint64_t* seqnum); - - sd_bus* (*sd_bus_message_get_bus)(sd_bus_message *m); - sd_bus_creds *(*sd_bus_message_get_creds)(sd_bus_message *m); /* do not unref the result */ - - int (*sd_bus_message_is_signal)(sd_bus_message *m, const char *interface, const char *member); - int (*sd_bus_message_is_method_call)(sd_bus_message *m, const char *interface, const char *member); - int (*sd_bus_message_is_method_error)(sd_bus_message *m, const char *name); - int (*sd_bus_message_is_empty)(sd_bus_message *m); - int (*sd_bus_message_has_signature)(sd_bus_message *m, const char *signature); - - int (*sd_bus_message_set_expect_reply)(sd_bus_message *m, int b); - int (*sd_bus_message_set_auto_start)(sd_bus_message *m, int b); - int (*sd_bus_message_set_allow_interactive_authorization)(sd_bus_message *m, int b); - - int (*sd_bus_message_set_destination)(sd_bus_message *m, const char *destination); - int (*sd_bus_message_set_sender)(sd_bus_message *m, const char *sender); - int (*sd_bus_message_set_priority)(sd_bus_message *m, int64_t priority); - - int (*sd_bus_message_append)(sd_bus_message *m, const char *types, ...); - int (*sd_bus_message_appendv)(sd_bus_message *m, const char *types, va_list ap); - int (*sd_bus_message_append_basic)(sd_bus_message *m, char type, const void *p); - int (*sd_bus_message_append_array)(sd_bus_message *m, char type, const void *ptr, size_t size); - int (*sd_bus_message_append_array_space)(sd_bus_message *m, char type, size_t size, void **ptr); - int (*sd_bus_message_append_array_iovec)(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); - int (*sd_bus_message_append_array_memfd)(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); - int (*sd_bus_message_append_string_space)(sd_bus_message *m, size_t size, char **s); - int (*sd_bus_message_append_string_iovec)(sd_bus_message *m, const struct iovec *iov, unsigned n); - int (*sd_bus_message_append_string_memfd)(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); - int (*sd_bus_message_append_strv)(sd_bus_message *m, char **l); - int (*sd_bus_message_open_container)(sd_bus_message *m, char type, const char *contents); - int (*sd_bus_message_close_container)(sd_bus_message *m); - int (*sd_bus_message_copy)(sd_bus_message *m, sd_bus_message *source, int all); - - int (*sd_bus_message_read)(sd_bus_message *m, const char *types, ...); - int (*sd_bus_message_readv)(sd_bus_message *m, const char *types, va_list ap); - int (*sd_bus_message_read_basic)(sd_bus_message *m, char type, void *p); - int (*sd_bus_message_read_array)(sd_bus_message *m, char type, const void **ptr, size_t *size); - int (*sd_bus_message_read_strv)(sd_bus_message *m, char ***l); /* free the result! */ - int (*sd_bus_message_skip)(sd_bus_message *m, const char *types); - int (*sd_bus_message_enter_container)(sd_bus_message *m, char type, const char *contents); - int (*sd_bus_message_exit_container)(sd_bus_message *m); - int (*sd_bus_message_peek_type)(sd_bus_message *m, char *type, const char **contents); - int (*sd_bus_message_verify_type)(sd_bus_message *m, char type, const char *contents); - int (*sd_bus_message_at_end)(sd_bus_message *m, int complete); - int (*sd_bus_message_rewind)(sd_bus_message *m, int complete); - - int (*sd_bus_get_unique_name)(sd_bus *bus, const char **unique); - int (*sd_bus_request_name)(sd_bus *bus, const char *name, uint64_t flags); - int (*sd_bus_request_name_async)(sd_bus *bus, sd_bus_slot **ret_slot, const char *name, uint64_t flags, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_release_name)(sd_bus *bus, const char *name); - int (*sd_bus_release_name_async)(sd_bus *bus, sd_bus_slot **ret_slot, const char *name, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_list_names)(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ - int (*sd_bus_get_name_creds)(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ - int (*sd_bus_get_name_machine_id)(sd_bus *bus, const char *name, sd_id128_t *machine); - - int (*sd_bus_call_method)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); - int (*sd_bus_call_method_async)(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); - int (*sd_bus_get_property)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); - int (*sd_bus_get_property_trivial)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); - int (*sd_bus_get_property_string)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ - int (*sd_bus_get_property_strv)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ - int (*sd_bus_set_property)(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); - - int (*sd_bus_reply_method_return)(sd_bus_message *call, const char *types, ...); - int (*sd_bus_reply_method_error)(sd_bus_message *call, const sd_bus_error *e); - int (*sd_bus_reply_method_errorf)(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); - int (*sd_bus_reply_method_errno)(sd_bus_message *call, int error, const sd_bus_error *e); - int (*sd_bus_reply_method_errnof)(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); - - int (*sd_bus_emit_signal)(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); - - int (*sd_bus_emit_properties_changed_strv)(sd_bus *bus, const char *path, const char *interface, char **names); - int (*sd_bus_emit_properties_changed)(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; - - int (*sd_bus_emit_object_added)(sd_bus *bus, const char *path); - int (*sd_bus_emit_object_removed)(sd_bus *bus, const char *path); - int (*sd_bus_emit_interfaces_added_strv)(sd_bus *bus, const char *path, char **interfaces); - int (*sd_bus_emit_interfaces_added)(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; - int (*sd_bus_emit_interfaces_removed_strv)(sd_bus *bus, const char *path, char **interfaces); - int (*sd_bus_emit_interfaces_removed)(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; - - int (*sd_bus_query_sender_creds)(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); - int (*sd_bus_query_sender_privilege)(sd_bus_message *call, int capability); - - int (*sd_bus_match_signal)(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata); - int (*sd_bus_match_signal_async)(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t match_callback, sd_bus_message_handler_t add_callback, void *userdata); - - int (*sd_bus_creds_new_from_pid)(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); - sd_bus_creds *(*sd_bus_creds_ref)(sd_bus_creds *c); - sd_bus_creds *(*sd_bus_creds_unref)(sd_bus_creds *c); - uint64_t (*sd_bus_creds_get_mask)(const sd_bus_creds *c); - uint64_t (*sd_bus_creds_get_augmented_mask)(const sd_bus_creds *c); - - int (*sd_bus_creds_get_pid)(sd_bus_creds *c, pid_t *pid); - int (*sd_bus_creds_get_ppid)(sd_bus_creds *c, pid_t *ppid); - int (*sd_bus_creds_get_tid)(sd_bus_creds *c, pid_t *tid); - int (*sd_bus_creds_get_uid)(sd_bus_creds *c, uid_t *uid); - int (*sd_bus_creds_get_euid)(sd_bus_creds *c, uid_t *euid); - int (*sd_bus_creds_get_suid)(sd_bus_creds *c, uid_t *suid); - int (*sd_bus_creds_get_fsuid)(sd_bus_creds *c, uid_t *fsuid); - int (*sd_bus_creds_get_gid)(sd_bus_creds *c, gid_t *gid); - int (*sd_bus_creds_get_egid)(sd_bus_creds *c, gid_t *egid); - int (*sd_bus_creds_get_sgid)(sd_bus_creds *c, gid_t *sgid); - int (*sd_bus_creds_get_fsgid)(sd_bus_creds *c, gid_t *fsgid); - int (*sd_bus_creds_get_supplementary_gids)(sd_bus_creds *c, const gid_t **gids); - int (*sd_bus_creds_get_comm)(sd_bus_creds *c, const char **comm); - int (*sd_bus_creds_get_tid_comm)(sd_bus_creds *c, const char **comm); - int (*sd_bus_creds_get_exe)(sd_bus_creds *c, const char **exe); - int (*sd_bus_creds_get_cmdline)(sd_bus_creds *c, char ***cmdline); - int (*sd_bus_creds_get_cgroup)(sd_bus_creds *c, const char **cgroup); - int (*sd_bus_creds_get_unit)(sd_bus_creds *c, const char **unit); - int (*sd_bus_creds_get_slice)(sd_bus_creds *c, const char **slice); - int (*sd_bus_creds_get_user_unit)(sd_bus_creds *c, const char **unit); - int (*sd_bus_creds_get_user_slice)(sd_bus_creds *c, const char **slice); - int (*sd_bus_creds_get_session)(sd_bus_creds *c, const char **session); - int (*sd_bus_creds_get_owner_uid)(sd_bus_creds *c, uid_t *uid); - int (*sd_bus_creds_has_effective_cap)(sd_bus_creds *c, int capability); - int (*sd_bus_creds_has_permitted_cap)(sd_bus_creds *c, int capability); - int (*sd_bus_creds_has_inheritable_cap)(sd_bus_creds *c, int capability); - int (*sd_bus_creds_has_bounding_cap)(sd_bus_creds *c, int capability); - int (*sd_bus_creds_get_selinux_context)(sd_bus_creds *c, const char **context); - int (*sd_bus_creds_get_audit_session_id)(sd_bus_creds *c, uint32_t *sessionid); - int (*sd_bus_creds_get_audit_login_uid)(sd_bus_creds *c, uid_t *loginuid); - int (*sd_bus_creds_get_tty)(sd_bus_creds *c, const char **tty); - int (*sd_bus_creds_get_unique_name)(sd_bus_creds *c, const char **name); - int (*sd_bus_creds_get_well_known_names)(sd_bus_creds *c, char ***names); - int (*sd_bus_creds_get_description)(sd_bus_creds *c, const char **name); - - void (*sd_bus_error_free)(sd_bus_error *e); - int (*sd_bus_error_set)(sd_bus_error *e, const char *name, const char *message); - int (*sd_bus_error_setf)(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); - int (*sd_bus_error_set_const)(sd_bus_error *e, const char *name, const char *message); - int (*sd_bus_error_set_errno)(sd_bus_error *e, int error); - int (*sd_bus_error_set_errnof)(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); - int (*sd_bus_error_set_errnofv)(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); - int (*sd_bus_error_get_errno)(const sd_bus_error *e); - int (*sd_bus_error_copy)(sd_bus_error *dest, const sd_bus_error *e); - int (*sd_bus_error_move)(sd_bus_error *dest, sd_bus_error *e); - int (*sd_bus_error_is_set)(const sd_bus_error *e); - int (*sd_bus_error_has_name)(const sd_bus_error *e, const char *name); - - int (*sd_bus_error_add_map)(const sd_bus_error_map *map); - - int (*sd_bus_path_encode)(const char *prefix, const char *external_id, char **ret_path); - int (*sd_bus_path_encode_many)(char **out, const char *path_template, ...); - int (*sd_bus_path_decode)(const char *path, const char *prefix, char **ret_external_id); - int (*sd_bus_path_decode_many)(const char *path, const char *path_template, ...); - - int (*sd_bus_track_new)(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); - sd_bus_track* (*sd_bus_track_ref)(sd_bus_track *track); - sd_bus_track* (*sd_bus_track_unref)(sd_bus_track *track); - - sd_bus* (*sd_bus_track_get_bus)(sd_bus_track *track); - void *(*sd_bus_track_get_userdata)(sd_bus_track *track); - void *(*sd_bus_track_set_userdata)(sd_bus_track *track, void *userdata); - - int (*sd_bus_track_add_sender)(sd_bus_track *track, sd_bus_message *m); - int (*sd_bus_track_remove_sender)(sd_bus_track *track, sd_bus_message *m); - int (*sd_bus_track_add_name)(sd_bus_track *track, const char *name); - int (*sd_bus_track_remove_name)(sd_bus_track *track, const char *name); - - int (*sd_bus_track_set_recursive)(sd_bus_track *track, int b); - int (*sd_bus_track_get_recursive)(sd_bus_track *track); - - unsigned (*sd_bus_track_count)(sd_bus_track *track); - int (*sd_bus_track_count_sender)(sd_bus_track *track, sd_bus_message *m); - int (*sd_bus_track_count_name)(sd_bus_track *track, const char *name); - - const char* (*sd_bus_track_contains)(sd_bus_track *track, const char *name); - const char* (*sd_bus_track_first)(sd_bus_track *track); - const char* (*sd_bus_track_next)(sd_bus_track *track); - - int (*sd_bus_track_set_destroy_callback)(sd_bus_track *s, sd_bus_destroy_t callback); - int (*sd_bus_track_get_destroy_callback)(sd_bus_track *s, sd_bus_destroy_t *ret); -}; - struct omxplayer_mgr; struct omxplayer_video_player { diff --git a/src/flutter-pi.c b/src/flutter-pi.c index fe126e3e..30d84d4a 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -101,7 +101,11 @@ OPTIONS:\n\ clock-wise.\n\ Valid values are 0, 90, 180 and 270.\n\ \n\ - -h Show this help and exit.\n\ + --no-text-input Disable text input from the console.\n\ + This means flutter-pi won't configure the console\n\ + to raw/non-canonical mode.\n\ + \n\ + -h, --help Show this help and exit.\n\ \n\ EXAMPLES:\n\ flutter-pi -i \"/dev/input/event{0,1}\" -i \"/dev/input/event{2,3}\" /home/pi/helloworld_flutterassets\n\ @@ -450,31 +454,6 @@ static FlutterTransformation on_get_transformation(void *userdata) { return flutterpi.view.view_to_display_transform; } - -/************************ - * PLATFORM TASK-RUNNER * - ************************/ -/* - } else if (task->type == kUpdateOrientation) { - rotation += ANGLE_FROM_ORIENTATION(task->orientation) - ANGLE_FROM_ORIENTATION(orientation); - if (rotation < 0) rotation += 360; - else if (rotation >= 360) rotation -= 360; - - orientation = task->orientation; - - // send updated window metrics to flutter - FlutterEngineSendWindowMetricsEvent(engine, &(const FlutterWindowMetricsEvent) { - .struct_size = sizeof(FlutterWindowMetricsEvent), - - // we send swapped width/height if the screen is rotated 90 or 270 degrees. - .width = (rotation == 0) || (rotation == 180) ? width : height, - .height = (rotation == 0) || (rotation == 180) ? height : width, - .pixel_ratio = pixel_ratio - }); - - } -*/ - /// platform tasks static int on_execute_platform_task( sd_event_source *s, @@ -2050,6 +2029,15 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void } else if (type == LIBINPUT_EVENT_POINTER_AXIS) { } + } else if (LIBINPUT_EVENT_IS_KEYBOARD(type)) { + struct libinput_event_keyboard *keyboard_event = libinput_event_get_keyboard_event(event); + + uint32_t keycode = libinput_event_keyboard_get_key(keyboard_event); + enum libinput_key_state state = libinput_event_keyboard_get_key_state(keyboard_event); + + glfw_key glfw_key = evdev_code_glfw_key[keycode]; + + rawkb_on_keyevent(glfw_key, keycode, state == LIBINPUT_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE); } } @@ -2148,28 +2136,30 @@ static int init_user_input(void) { return -ok; } - ok = sd_event_add_io( - flutterpi.event_loop, - &stdin_event_source, - STDIN_FILENO, - EPOLLIN, - on_stdin_ready, - NULL - ); - if (ok < 0) { - fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); - sd_event_source_unrefp(&libinput_event_source); - libinput_unref(libinput); - udev_unref(udev); - return -ok; - } + if (flutterpi.input.disable_text_input == false) { + ok = sd_event_add_io( + flutterpi.event_loop, + &stdin_event_source, + STDIN_FILENO, + EPOLLIN, + on_stdin_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); + sd_event_source_unrefp(&libinput_event_source); + libinput_unref(libinput); + udev_unref(udev); + return -ok; + } - ok = 1; //console_make_raw(); - if (ok == 0) { - console_flush_stdin(); - } else { - fprintf(stderr, "[flutter-pi] WARNING: could not make stdin raw\n"); - sd_event_source_unrefp(&stdin_event_source); + ok = console_make_raw(); + if (ok == 0) { + console_flush_stdin(); + } else { + fprintf(stderr, "[flutter-pi] WARNING: could not make stdin raw\n"); + sd_event_source_unrefp(&stdin_event_source); + } } flutterpi.input.udev = udev; @@ -2223,7 +2213,7 @@ static bool setup_paths(void) { static bool parse_cmd_args(int argc, char **argv) { glob_t input_devices_glob = {0}; bool input_specified = false; - int opt, longopt_index = 0, runtime_mode_int = kDebug; + int opt, longopt_index = 0, runtime_mode_int = kDebug, disable_text_input_int = false; struct option long_options[] = { {"release", no_argument, &runtime_mode_int, kRelease}, @@ -2232,6 +2222,7 @@ static bool parse_cmd_args(int argc, char **argv) { {"input", required_argument, 0, 'i'}, {"orientation", required_argument, 0, 'o'}, {"rotation", required_argument, 0, 'r'}, + {"no-text-input", required_argument, &disable_text_input_int, true}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; @@ -2283,7 +2274,7 @@ static bool parse_cmd_args(int argc, char **argv) { } flutterpi.view.rotation = rotation; - } else if ((opt == '?') || (opt == ':')) { + } else if (((opt == 0) && (longopt_index == 7)) || ((opt == 'h') || (opt == '?') || (opt == ':'))) { printf("%s", usage); return false; } @@ -2303,6 +2294,7 @@ static bool parse_cmd_args(int argc, char **argv) { flutterpi.input.use_paths = input_specified; flutterpi.flutter.asset_bundle_path = strdup(argv[optind]); flutterpi.flutter.runtime_mode = (enum flutter_runtime_mode) runtime_mode_int; + flutterpi.input.disable_text_input = disable_text_input_int; argv[optind] = argv[0]; flutterpi.flutter.engine_argc = argc - optind; diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c index 10713085..c781e149 100644 --- a/src/plugins/omxplayer_video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -51,9 +51,6 @@ static struct { .players = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) }; -/// libsystemd DLL -struct libsystemd libsystemd = {0}; - /// Add a player instance to the player collection. int add_player(struct omxplayer_video_player *player) { return cpset_put_(&omxpvidpp.players, player); @@ -284,7 +281,7 @@ static int get_dbus_property( sd_bus_message *msg; int ok; - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, destination, path, @@ -301,14 +298,14 @@ static int get_dbus_property( return -ok; } - ok = libsystemd.sd_bus_message_read_basic(msg, type, ret_ptr); + ok = sd_bus_message_read_basic(msg, type, ret_ptr); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not read DBus property: %s\n", strerror(-ok)); - libsystemd.sd_bus_message_unref(msg); + sd_bus_message_unref(msg); return -ok; } - libsystemd.sd_bus_message_unref(msg); + sd_bus_message_unref(msg); return 0; } @@ -328,11 +325,11 @@ static int mgr_on_dbus_message( task = userdata; - sender = libsystemd.sd_bus_message_get_sender(m); - member = libsystemd.sd_bus_message_get_member(m); + sender = sd_bus_message_get_sender(m); + member = sd_bus_message_get_member(m); if (STREQ(sender, "org.freedesktop.DBus") && STREQ(member, "NameOwnerChanged")) { - ok = libsystemd.sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); + ok = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); if (ok < 0) { fprintf(stderr, "Could not read message"); return -1; @@ -415,7 +412,7 @@ static void *mgr_entry(void *userdata) { ); // open the session dbus - ok = libsystemd.sd_bus_open_user(&bus); + ok = sd_bus_open_user(&bus); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] Could not open DBus in manager thread. sd_bus_open_user: %s\n", strerror(-ok)); platch_respond_native_error_std(task.responsehandle, -ok); @@ -426,7 +423,7 @@ static void *mgr_entry(void *userdata) { // omxplayer has registered to the dbus task.omxplayer_online = false; task.omxplayer_dbus_name = dbus_name; - ok = libsystemd.sd_bus_match_signal( + ok = sd_bus_match_signal( bus, &slot, "org.freedesktop.DBus", @@ -484,7 +481,7 @@ static void *mgr_entry(void *userdata) { } while (!task.omxplayer_online) { - ok = libsystemd.sd_bus_wait(bus, 1000*1000*5); + ok = sd_bus_wait(bus, 1000*1000*5); if (ok < 0) { ok = -ok; fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); @@ -492,7 +489,7 @@ static void *mgr_entry(void *userdata) { goto fail_kill_unregistered_player; } - ok = libsystemd.sd_bus_process(bus, NULL); + ok = sd_bus_process(bus, NULL); if (ok < 0) { ok = -ok; fprintf(stderr, "[omxplayer_video_player plugin] Could not wait for sd bus messages on manager thread: %s\n", strerror(ok)); @@ -501,7 +498,7 @@ static void *mgr_entry(void *userdata) { } } - libsystemd.sd_bus_slot_unref(slot); + sd_bus_slot_unref(slot); slot = NULL; duration_us = 0; @@ -530,7 +527,7 @@ static void *mgr_entry(void *userdata) { } // pause right on the first frame - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, @@ -546,7 +543,7 @@ static void *mgr_entry(void *userdata) { goto fail_kill_registered_player; } - libsystemd.sd_bus_message_unref(msg); + sd_bus_message_unref(msg); msg = NULL; // get the video duration @@ -636,14 +633,14 @@ static void *mgr_entry(void *userdata) { } // tell omxplayer to quit - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_ROOT_FACE, "Quit", &err, - &msg, + NULL, "" ); if (ok < 0) { @@ -652,14 +649,12 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); - ok = (int) waitpid(omxplayer_pid, NULL, 0); if (ok < 0) { fprintf(stderr, "[omxplayer_video_player plugin] omxplayer quit with exit code %d\n", ok); } - libsystemd.sd_bus_unref(bus); + sd_bus_unref(bus); plugin_registry_remove_receiver(mgr->player->event_channel_name); @@ -705,14 +700,14 @@ static void *mgr_entry(void *userdata) { } else if (task.type == kUnlisten) { platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kPlay) { - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, "Play", &err, - &msg, + NULL, "" ); if (ok < 0) { @@ -721,20 +716,18 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); - platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kPause) { has_scheduled_pause_time = false; - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, "Pause", &err, - &msg, + NULL, "" ); if (ok < 0) { @@ -743,7 +736,6 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); msg = NULL; platch_respond_success_std(task.responsehandle, NULL); @@ -759,14 +751,14 @@ static void *mgr_entry(void *userdata) { (double) (task.offset_y + task.height) ); - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, "VideoPos", &err, - &msg, + NULL, "os", "/obj/not/used", video_pos_str @@ -776,18 +768,16 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); - if (current_zpos != task.zpos) { printf("setting omxplayer layer to %d\n", task.zpos); - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, "SetLayer", &err, - &msg, + NULL, "x", (int64_t) task.zpos ); @@ -796,8 +786,6 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); - current_zpos = task.zpos; } } else if (task.type == kGetPosition) { @@ -843,40 +831,38 @@ static void *mgr_entry(void *userdata) { ); } } else { - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_OMXPLAYER_PLAYER_FACE, "SetPosition", &err, - &msg, + NULL, "ox", "/path/not/used", (int64_t) (task.position * 1000) ); - if (ok != 0) { - fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s\n", err.name, err.message); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not set omxplayer position: %s, %s, %s\n", strerror(-ok), err.name, err.message); respond_sd_bus_error(task.responsehandle, &err); continue; } - libsystemd.sd_bus_message_unref(msg); - platch_respond_success_std(task.responsehandle, NULL); } } else if (task.type == kSetLooping) { pause_on_end = false; platch_respond_success_std(task.responsehandle, NULL); } else if (task.type == kSetVolume) { - ok = libsystemd.sd_bus_call_method( + ok = sd_bus_call_method( bus, dbus_name, DBUS_OMXPLAYER_OBJECT, DBUS_PROPERTY_FACE, DBUS_PROPRETY_SET, &err, - &msg, + NULL, "ssd", DBUS_OMXPLAYER_PLAYER_FACE, "Volume", @@ -888,8 +874,6 @@ static void *mgr_entry(void *userdata) { continue; } - libsystemd.sd_bus_message_unref(msg); - platch_respond_success_std(task.responsehandle, NULL); } } @@ -907,11 +891,11 @@ static void *mgr_entry(void *userdata) { waitpid(omxplayer_pid, NULL, 0); fail_unref_slot: - libsystemd.sd_bus_slot_unref(slot); + sd_bus_slot_unref(slot); slot = NULL; fail_close_dbus: - libsystemd.sd_bus_unref(bus); + sd_bus_unref(bus); fail_remove_evch_listener: plugin_registry_remove_receiver(mgr->player->event_channel_name); @@ -937,342 +921,6 @@ static int ensure_binding_initialized(void) { return errno; } - libsystemd.handle = dlopen("libsystemd.so", RTLD_NOW | RTLD_LOCAL); - - LOAD_LIBSYSTEMD_PROC(sd_bus_default); - LOAD_LIBSYSTEMD_PROC(sd_bus_default_user); - LOAD_LIBSYSTEMD_PROC(sd_bus_default_system); - - LOAD_LIBSYSTEMD_PROC(sd_bus_open); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_with_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_user); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_user_with_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_system); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_with_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_remote); - LOAD_LIBSYSTEMD_PROC(sd_bus_open_system_machine); - - LOAD_LIBSYSTEMD_PROC(sd_bus_new); - - LOAD_LIBSYSTEMD_PROC(sd_bus_set_address); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_fd); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_exec); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_address); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_bus_client); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_bus_client); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_server); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_server); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_anonymous); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_anonymous); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_trusted); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_trusted); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_monitor); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_monitor); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_creds); - LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_timestamp); - LOAD_LIBSYSTEMD_PROC(sd_bus_negotiate_fds); - LOAD_LIBSYSTEMD_PROC(sd_bus_can_send); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_creds_mask); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_allow_interactive_authorization); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_allow_interactive_authorization); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_exit_on_disconnect); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_exit_on_disconnect); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_close_on_exit); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_close_on_exit); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_watch_bind); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_watch_bind); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_connected_signal); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_connected_signal); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_sender); - - LOAD_LIBSYSTEMD_PROC(sd_bus_start); - - LOAD_LIBSYSTEMD_PROC(sd_bus_try_close); - LOAD_LIBSYSTEMD_PROC(sd_bus_close); - - LOAD_LIBSYSTEMD_PROC(sd_bus_ref); - LOAD_LIBSYSTEMD_PROC(sd_bus_unref); - LOAD_LIBSYSTEMD_PROC(sd_bus_close_unref); - LOAD_LIBSYSTEMD_PROC(sd_bus_flush_close_unref); - - LOAD_LIBSYSTEMD_PROC(sd_bus_default_flush_close); - - LOAD_LIBSYSTEMD_PROC(sd_bus_is_open); - LOAD_LIBSYSTEMD_PROC(sd_bus_is_ready); - - LOAD_LIBSYSTEMD_PROC(sd_bus_get_bus_id); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_scope); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_tid); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_owner_creds); - - LOAD_LIBSYSTEMD_PROC(sd_bus_send); - LOAD_LIBSYSTEMD_PROC(sd_bus_send_to); - LOAD_LIBSYSTEMD_PROC(sd_bus_call); - LOAD_LIBSYSTEMD_PROC(sd_bus_call_async); - - LOAD_LIBSYSTEMD_PROC(sd_bus_get_fd); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_events); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_timeout); - LOAD_LIBSYSTEMD_PROC(sd_bus_process); - LOAD_LIBSYSTEMD_PROC(sd_bus_process_priority); - LOAD_LIBSYSTEMD_PROC(sd_bus_wait); - LOAD_LIBSYSTEMD_PROC(sd_bus_flush); - - LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_slot); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_message); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_handler); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_current_userdata); - - LOAD_LIBSYSTEMD_PROC(sd_bus_attach_event); - LOAD_LIBSYSTEMD_PROC(sd_bus_detach_event); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_event); - - LOAD_LIBSYSTEMD_PROC(sd_bus_get_n_queued_read); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_n_queued_write); - - LOAD_LIBSYSTEMD_PROC(sd_bus_set_method_call_timeout); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_method_call_timeout); - - LOAD_LIBSYSTEMD_PROC(sd_bus_add_filter); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_match); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_match_async); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_object); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_fallback); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_object_vtable); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_fallback_vtable); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_node_enumerator); - LOAD_LIBSYSTEMD_PROC(sd_bus_add_object_manager); - - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_ref); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_unref); - - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_bus); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_userdata); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_userdata); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_description); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_floating); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_floating); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_set_destroy_callback); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_destroy_callback); - - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_message); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_handler); - LOAD_LIBSYSTEMD_PROC(sd_bus_slot_get_current_userdata); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_signal); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_call); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_return); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_error); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errorf); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errno); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_new_method_errnof); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_ref); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_unref); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_seal); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_type); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_cookie); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_reply_cookie); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_priority); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_expect_reply); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_auto_start); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_allow_interactive_authorization); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_signature); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_path); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_interface); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_member); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_destination); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_error); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_errno); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_monotonic_usec); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_realtime_usec); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_seqnum); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_bus); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_get_creds); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_signal); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_method_call); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_method_error); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_is_empty); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_has_signature); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_expect_reply); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_auto_start); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_allow_interactive_authorization); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_destination); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_set_priority); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_appendv); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_basic); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_space); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_iovec); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_array_memfd); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_space); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_iovec); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_string_memfd); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_append_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_open_container); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_close_container); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_copy); - - LOAD_LIBSYSTEMD_PROC(sd_bus_message_read); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_readv); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_basic); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_array); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_read_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_skip); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_enter_container); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_exit_container); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_peek_type); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_verify_type); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_at_end); - LOAD_LIBSYSTEMD_PROC(sd_bus_message_rewind); - - LOAD_LIBSYSTEMD_PROC(sd_bus_get_unique_name); - LOAD_LIBSYSTEMD_PROC(sd_bus_request_name); - LOAD_LIBSYSTEMD_PROC(sd_bus_request_name_async); - LOAD_LIBSYSTEMD_PROC(sd_bus_release_name); - LOAD_LIBSYSTEMD_PROC(sd_bus_release_name_async); - LOAD_LIBSYSTEMD_PROC(sd_bus_list_names); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_name_creds); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_name_machine_id); - - LOAD_LIBSYSTEMD_PROC(sd_bus_call_method); - LOAD_LIBSYSTEMD_PROC(sd_bus_call_method_async); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_property); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_trivial); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_string); - LOAD_LIBSYSTEMD_PROC(sd_bus_get_property_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_set_property); - - LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_return); - LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_error); - LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errorf); - LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errno); - LOAD_LIBSYSTEMD_PROC(sd_bus_reply_method_errnof); - - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_signal); - - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_properties_changed_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_properties_changed); - - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_object_added); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_object_removed); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_added_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_added); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_removed_strv); - LOAD_LIBSYSTEMD_PROC(sd_bus_emit_interfaces_removed); - - LOAD_LIBSYSTEMD_PROC(sd_bus_query_sender_creds); - LOAD_LIBSYSTEMD_PROC(sd_bus_query_sender_privilege); - - LOAD_LIBSYSTEMD_PROC(sd_bus_match_signal); - LOAD_LIBSYSTEMD_PROC(sd_bus_match_signal_async); - - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_new_from_pid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_ref); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_unref); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_mask); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_augmented_mask); - - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_pid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_ppid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_uid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_euid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_suid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_fsuid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_gid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_egid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_sgid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_fsgid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_supplementary_gids); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_comm); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tid_comm); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_exe); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_cmdline); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_cgroup); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_unit); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_slice); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_user_unit); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_user_slice); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_session); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_owner_uid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_effective_cap); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_permitted_cap); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_inheritable_cap); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_has_bounding_cap); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_selinux_context); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_audit_session_id); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_audit_login_uid); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_tty); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_unique_name); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_well_known_names); - LOAD_LIBSYSTEMD_PROC(sd_bus_creds_get_description); - - LOAD_LIBSYSTEMD_PROC(sd_bus_error_free); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_set); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_setf); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_const); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errno); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errnof); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_set_errnofv); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_get_errno); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_copy); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_move); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_is_set); - LOAD_LIBSYSTEMD_PROC(sd_bus_error_has_name); - - LOAD_LIBSYSTEMD_PROC(sd_bus_error_add_map); - - LOAD_LIBSYSTEMD_PROC(sd_bus_path_encode); - LOAD_LIBSYSTEMD_PROC(sd_bus_path_encode_many); - LOAD_LIBSYSTEMD_PROC(sd_bus_path_decode); - LOAD_LIBSYSTEMD_PROC(sd_bus_path_decode_many); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_new); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_ref); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_unref); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_bus); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_userdata); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_userdata); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_add_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_remove_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_add_name); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_remove_name); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_recursive); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_recursive); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_count); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_count_sender); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_count_name); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_contains); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_first); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_next); - - LOAD_LIBSYSTEMD_PROC(sd_bus_track_set_destroy_callback); - LOAD_LIBSYSTEMD_PROC(sd_bus_track_get_destroy_callback); - omxpvidpp.initialized = true; return 0; From 0f59b4a066725bdb13b60fec65178cabdf58c98e Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 5 Aug 2020 15:17:40 +0200 Subject: [PATCH 12/14] support missing udev at build- and runtime update CMakeLists --- CMakeLists.txt | 35 ++-- include/flutter-pi.h | 103 +++++++++- src/flutter-pi.c | 434 +++++++++++++++++++++++++++++-------------- 3 files changed, 409 insertions(+), 163 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 876312d9..b765a615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,24 +100,24 @@ pkg_check_modules(DRM REQUIRED libdrm) pkg_check_modules(GLESV2 REQUIRED glesv2) pkg_check_modules(EGL REQUIRED egl) pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) -pkg_check_modules(LIBUDEV REQUIRED libudev) +pkg_check_modules(LIBUDEV libudev) pkg_check_modules(GPIOD libgpiod) set(FLUTTER_PI_SRC - src/flutter-pi.c \ - src/platformchannel.c \ - src/pluginregistry.c \ - src/console_keyboard.c \ - src/texture_registry.c \ - src/compositor.c \ - src/modesetting.c \ - src/collection.c \ - src/cursor.c \ - src/plugins/services.c \ - src/plugins/testplugin.c \ - src/plugins/text_input.c \ - src/plugins/raw_keyboard.c \ - src/plugins/spidev.c \ + src/flutter-pi.c + src/platformchannel.c + src/pluginregistry.c + src/console_keyboard.c + src/texture_registry.c + src/compositor.c + src/modesetting.c + src/collection.c + src/cursor.c + src/plugins/services.c + src/plugins/testplugin.c + src/plugins/text_input.c + src/plugins/raw_keyboard.c + src/plugins/spidev.c src/plugins/omxplayer_video_player.c ) @@ -128,6 +128,11 @@ else() message(STATUS "Could not find gpiod library and development headers. flutter-pi will be built without gpiod support. To install, execute 'sudo apt install libgpiod-dev'") endif() +if (NOT LIBUDEV_FOUND) + message(STATUS "Could not find libudev.so and libudev development headers. flutter-pi will be built without udev (hotplugging) support. To install, execute 'sudo apt install libudev-dev'") + add_compile_options(-DBUILD_WITHOUT_UDEV_SUPPORT) +endif() + add_executable(flutter-pi ${FLUTTER_PI_SRC}) target_link_libraries(flutter-pi diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 7e425f71..6306825c 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -55,6 +55,99 @@ enum device_orientation { kPortraitUp, kLandscapeLeft, kPortraitDown, kLandscapeRight }; +struct libudev { + struct udev *(*udev_ref)(struct udev *udev); + struct udev *(*udev_unref)(struct udev *udev); + struct udev *(*udev_new)(void); + void *(*udev_get_userdata)(struct udev *udev); + void (*udev_set_userdata)(struct udev *udev, void *userdata); + + struct udev_list_entry *(*udev_list_entry_get_next)(struct udev_list_entry *list_entry); + struct udev_list_entry *(*udev_list_entry_get_by_name)(struct udev_list_entry *list_entry, const char *name); + const char *(*udev_list_entry_get_name)(struct udev_list_entry *list_entry); + const char *(*udev_list_entry_get_value)(struct udev_list_entry *list_entry); + + struct udev_device *(*udev_device_ref)(struct udev_device *udev_device); + struct udev_device *(*udev_device_unref)(struct udev_device *udev_device); + struct udev *(*udev_device_get_udev)(struct udev_device *udev_device); + struct udev_device *(*udev_device_new_from_syspath)(struct udev *udev, const char *syspath); + struct udev_device *(*udev_device_new_from_devnum)(struct udev *udev, char type, dev_t devnum); + struct udev_device *(*udev_device_new_from_subsystem_sysname)(struct udev *udev, const char *subsystem, const char *sysname); + struct udev_device *(*udev_device_new_from_device_id)(struct udev *udev, const char *id); + struct udev_device *(*udev_device_new_from_environment)(struct udev *udev); + struct udev_device *(*udev_device_get_parent)(struct udev_device *udev_device); + struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype); + const char *(*udev_device_get_devpath)(struct udev_device *udev_device); + const char *(*udev_device_get_subsystem)(struct udev_device *udev_device); + const char *(*udev_device_get_devtype)(struct udev_device *udev_device); + const char *(*udev_device_get_syspath)(struct udev_device *udev_device); + const char *(*udev_device_get_sysname)(struct udev_device *udev_device); + const char *(*udev_device_get_sysnum)(struct udev_device *udev_device); + const char *(*udev_device_get_devnode)(struct udev_device *udev_device); + int (*udev_device_get_is_initialized)(struct udev_device *udev_device); + struct udev_list_entry *(*udev_device_get_devlinks_list_entry)(struct udev_device *udev_device); + struct udev_list_entry *(*udev_device_get_properties_list_entry)(struct udev_device *udev_device); + struct udev_list_entry *(*udev_device_get_tags_list_entry)(struct udev_device *udev_device); + struct udev_list_entry *(*udev_device_get_sysattr_list_entry)(struct udev_device *udev_device); + const char *(*udev_device_get_property_value)(struct udev_device *udev_device, const char *key); + const char *(*udev_device_get_driver)(struct udev_device *udev_device); + dev_t (*udev_device_get_devnum)(struct udev_device *udev_device); + const char *(*udev_device_get_action)(struct udev_device *udev_device); + unsigned long long int (*udev_device_get_seqnum)(struct udev_device *udev_device); + unsigned long long int (*udev_device_get_usec_since_initialized)(struct udev_device *udev_device); + const char *(*udev_device_get_sysattr_value)(struct udev_device *udev_device, const char *sysattr); + int (*udev_device_set_sysattr_value)(struct udev_device *udev_device, const char *sysattr, const char *value); + int (*udev_device_has_tag)(struct udev_device *udev_device, const char *tag); + + struct udev_monitor *(*udev_monitor_ref)(struct udev_monitor *udev_monitor); + struct udev_monitor *(*udev_monitor_unref)(struct udev_monitor *udev_monitor); + struct udev *(*udev_monitor_get_udev)(struct udev_monitor *udev_monitor); + struct udev_monitor *(*udev_monitor_new_from_netlink)(struct udev *udev, const char *name); + int (*udev_monitor_enable_receiving)(struct udev_monitor *udev_monitor); + int (*udev_monitor_set_receive_buffer_size)(struct udev_monitor *udev_monitor, int size); + int (*udev_monitor_get_fd)(struct udev_monitor *udev_monitor); + struct udev_device *(*udev_monitor_receive_device)(struct udev_monitor *udev_monitor); + int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *udev_monitor, + const char *subsystem, const char *devtype); + int (*udev_monitor_filter_add_match_tag)(struct udev_monitor *udev_monitor, const char *tag); + int (*udev_monitor_filter_update)(struct udev_monitor *udev_monitor); + int (*udev_monitor_filter_remove)(struct udev_monitor *udev_monitor); + + struct udev_enumerate *(*udev_enumerate_ref)(struct udev_enumerate *udev_enumerate); + struct udev_enumerate *(*udev_enumerate_unref)(struct udev_enumerate *udev_enumerate); + struct udev *(*udev_enumerate_get_udev)(struct udev_enumerate *udev_enumerate); + struct udev_enumerate *(*udev_enumerate_new)(struct udev *udev); + int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate *udev_enumerate, const char *subsystem); + int (*udev_enumerate_add_nomatch_subsystem)(struct udev_enumerate *udev_enumerate, const char *subsystem); + int (*udev_enumerate_add_match_sysattr)(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); + int (*udev_enumerate_add_nomatch_sysattr)(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); + int (*udev_enumerate_add_match_property)(struct udev_enumerate *udev_enumerate, const char *property, const char *value); + int (*udev_enumerate_add_match_sysname)(struct udev_enumerate *udev_enumerate, const char *sysname); + int (*udev_enumerate_add_match_tag)(struct udev_enumerate *udev_enumerate, const char *tag); + int (*udev_enumerate_add_match_parent)(struct udev_enumerate *udev_enumerate, struct udev_device *parent); + int (*udev_enumerate_add_match_is_initialized)(struct udev_enumerate *udev_enumerate); + int (*udev_enumerate_add_syspath)(struct udev_enumerate *udev_enumerate, const char *syspath); + int (*udev_enumerate_scan_devices)(struct udev_enumerate *udev_enumerate); + int (*udev_enumerate_scan_subsystems)(struct udev_enumerate *udev_enumerate); + struct udev_list_entry *(*udev_enumerate_get_list_entry)(struct udev_enumerate *udev_enumerate); + + struct udev_queue *(*udev_queue_ref)(struct udev_queue *udev_queue); + struct udev_queue *(*udev_queue_unref)(struct udev_queue *udev_queue); + struct udev *(*udev_queue_get_udev)(struct udev_queue *udev_queue); + struct udev_queue *(*udev_queue_new)(struct udev *udev); + int (*udev_queue_get_udev_is_active)(struct udev_queue *udev_queue); + int (*udev_queue_get_queue_is_empty)(struct udev_queue *udev_queue); + int (*udev_queue_get_fd)(struct udev_queue *udev_queue); + int (*udev_queue_flush)(struct udev_queue *udev_queue); + + struct udev_hwdb *(*udev_hwdb_new)(struct udev *udev); + struct udev_hwdb *(*udev_hwdb_ref)(struct udev_hwdb *hwdb); + struct udev_hwdb *(*udev_hwdb_unref)(struct udev_hwdb *hwdb); + struct udev_list_entry *(*udev_hwdb_get_properties_list_entry)(struct udev_hwdb *hwdb, const char *modalias, unsigned flags); + + int (*udev_util_encode_string)(const char *str, char *str_enc, size_t len); +}; + #define ANGLE_FROM_ORIENTATION(o) \ ((o) == kPortraitUp ? 0 : \ (o) == kLandscapeLeft ? 90 : \ @@ -161,10 +254,6 @@ struct frame { struct compositor; -enum flutter_runtime_mode { - kDebug, kProfile, kRelease -}; - struct flutterpi { /// graphics stuff struct { @@ -266,7 +355,9 @@ struct flutterpi { bool use_paths; bool disable_text_input; glob_t input_devices_glob; - struct udev *udev; +# ifndef BUILD_WITHOUT_UDEV_SUPPORT + struct libudev libudev; +# endif struct libinput *libinput; sd_event_source *libinput_event_source; sd_event_source *stdin_event_source; @@ -284,7 +375,7 @@ struct flutterpi { int engine_argc; char **engine_argv; - enum flutter_runtime_mode runtime_mode; + bool is_aot; FlutterEngine engine; } flutter; diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 30d84d4a..508eedcb 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -77,15 +77,10 @@ OPTIONS:\n\ pattern you use as a parameter so it isn't \n\ implicitly expanded by your shell.\n\ \n\ - -r, --release Run the app in release mode. The AOT snapshot\n\ + --aot Run the app in AOT mode. The AOT snapshot\n\ of the app (\"app.so\") must be located inside the\n\ asset bundle directory.\n\ \n\ - -p, --profile Run the app in profile mode. This runtime mode too\n\ - depends on the AOT snapshot.\n\ - \n\ - -d, --debug Run the app in debug mode. This is the default.\n\ - \n\ -o, --orientation Start the app in this orientation. Valid\n\ for are: portrait_up, landscape_left,\n\ portrait_down, landscape_right.\n\ @@ -119,8 +114,8 @@ SEE ALSO:\n\ Source: https://github.com/ardera/flutter-pi\n\ License: MIT\n\ \n\ - For instructions on how to build an asset bundle, please see the linked\n\ - git repository.\n\ + For instructions on how to build an asset bundle or an AOT snapshot\n\ + of your app, please see the linked git repository.\n\ For a list of options you can pass to the flutter engine, look here:\n\ https://github.com/flutter/engine/blob/master/shell/common/switches.h\n\ "; @@ -1638,9 +1633,8 @@ static int init_application(void) { .compositor = &flutter_compositor }; - if (flutterpi.flutter.runtime_mode == kRelease || flutterpi.flutter.runtime_mode == kProfile) { + if (flutterpi.flutter.is_aot) { const uint8_t *vm_instr, *vm_data, *isolate_instr, *isolate_data; - size_t vm_instr_size, vm_data_size, isolate_instr_size, isolate_data_size; app_elf_handle = dlopen(flutterpi.flutter.app_elf_path, RTLD_NOW | RTLD_LOCAL); if (app_elf_handle == NULL) { @@ -1655,7 +1649,7 @@ static int init_application(void) { return errno; } - vm_data = dlsym(app_elf_handle, "_kDartSnapshotData"); + vm_data = dlsym(app_elf_handle, "_kDartVmSnapshotData"); if (vm_data == NULL) { perror("[flutter-pi] Could not resolve vm data section in \"app.so\". dlsym"); dlclose(app_elf_handle); @@ -1676,61 +1670,17 @@ static int init_application(void) { return errno; } - // now find out the sizes for the sections... - int fd = open(flutterpi.flutter.app_elf_path, O_RDONLY); - - struct stat statbuf; - fstat(fd, &statbuf); - char *fbase = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); - - Elf32_Ehdr *ehdr = (Elf32_Ehdr *) fbase; - Elf32_Shdr *sections = (Elf32_Shdr *) (fbase + ehdr->e_shoff); - - int section_header_size = ehdr->e_shentsize; - int section_header_num = ehdr->e_shnum; - int section_header_strndx = ehdr->e_shstrndx; - - Elf32_Shdr *shstr_section = sections + shstr_section->sh_offset; - char *shstrtab = fbase + shstr_section->sh_offset; - - - // Assume the first .text section is the vm instructions, - // the second .text is isolate instructions - // Assume the first .rodata section is the vm data, - // the second .rodata is isolate data - - vm_instr_size = 0; isolate_instr_size = 0; - vm_data_size = 0; isolate_data_size = 0; - for (int i = 0; i < section_header_num; i++) { - if (strcmp(shstrtab + sections[i].sh_name, ".text") == 0) { - if (vm_instr_size == 0) { - vm_instr_size = sections[i].sh_size; - } else if (isolate_instr_size == 0) { - isolate_instr_size = sections[i].sh_size; - } - } else if (strcmp(shstrtab + sections[i].sh_name, ".rodata") == 0) { - if (vm_data_size == 0) { - vm_data_size = sections[i].sh_size; - } else if (isolate_data_size == 0) { - isolate_data_size = sections[i].sh_size; - } - } - } - - munmap(fbase, statbuf.st_size); - close(fd); - project_args.vm_snapshot_instructions = vm_instr; - project_args.vm_snapshot_instructions_size = vm_instr_size; + project_args.vm_snapshot_instructions_size = 0; project_args.isolate_snapshot_instructions = isolate_instr; - project_args.isolate_snapshot_instructions_size = isolate_instr_size; + project_args.isolate_snapshot_instructions_size = 0; project_args.vm_snapshot_data = vm_data; - project_args.vm_snapshot_data_size = vm_data_size; + project_args.vm_snapshot_data_size = 0; project_args.isolate_snapshot_data = isolate_data; - project_args.isolate_snapshot_data_size = isolate_data_size; + project_args.isolate_snapshot_data_size = 0; } // spin up the engine @@ -2087,16 +2037,128 @@ static int on_stdin_ready(sd_event_source *s, int fd, uint32_t revents, void *us } } -static int init_user_input(void) { - sd_event_source *libinput_event_source, *stdin_event_source; +static struct libinput *try_create_udev_backed_libinput(void) { +#ifdef BUILD_WITHOUT_UDEV_SUPPORT + return NULL; +#else struct libinput *libinput; + struct libudev *libudev; struct udev *udev; int ok; - udev = udev_new(); + void *handle = dlopen("libudev.so", RTLD_LAZY | RTLD_NOW); + if (handle == NULL) { + printf("[flutter-pi] Could not open libudev. Flutter-pi will run without hotplugging support.\n"); + return NULL; + } + + libudev = &flutterpi.input.libudev; + +# define LOAD_LIBUDEV_PROC(name) \ + do { \ + libudev->name = dlsym(handle, #name); \ + if (!libudev->name) {\ + perror("[flutter-pi] Could not resolve libudev procedure " #name ". dlsym"); \ + return NULL; \ + } \ + } while (false) + + LOAD_LIBUDEV_PROC(udev_ref); + LOAD_LIBUDEV_PROC(udev_unref); + LOAD_LIBUDEV_PROC(udev_new); + LOAD_LIBUDEV_PROC(udev_get_userdata); + LOAD_LIBUDEV_PROC(udev_set_userdata); + + LOAD_LIBUDEV_PROC(udev_list_entry_get_next); + LOAD_LIBUDEV_PROC(udev_list_entry_get_by_name); + LOAD_LIBUDEV_PROC(udev_list_entry_get_name); + LOAD_LIBUDEV_PROC(udev_list_entry_get_value); + + LOAD_LIBUDEV_PROC(udev_device_ref); + LOAD_LIBUDEV_PROC(udev_device_unref); + LOAD_LIBUDEV_PROC(udev_device_get_udev); + LOAD_LIBUDEV_PROC(udev_device_new_from_syspath); + LOAD_LIBUDEV_PROC(udev_device_new_from_devnum); + LOAD_LIBUDEV_PROC(udev_device_new_from_subsystem_sysname); + LOAD_LIBUDEV_PROC(udev_device_new_from_device_id); + LOAD_LIBUDEV_PROC(udev_device_new_from_environment); + LOAD_LIBUDEV_PROC(udev_device_get_parent); + LOAD_LIBUDEV_PROC(udev_device_get_parent_with_subsystem_devtype); + LOAD_LIBUDEV_PROC(udev_device_get_devpath); + LOAD_LIBUDEV_PROC(udev_device_get_subsystem); + LOAD_LIBUDEV_PROC(udev_device_get_devtype); + LOAD_LIBUDEV_PROC(udev_device_get_syspath); + LOAD_LIBUDEV_PROC(udev_device_get_sysname); + LOAD_LIBUDEV_PROC(udev_device_get_sysnum); + LOAD_LIBUDEV_PROC(udev_device_get_devnode); + LOAD_LIBUDEV_PROC(udev_device_get_is_initialized); + LOAD_LIBUDEV_PROC(udev_device_get_devlinks_list_entry); + LOAD_LIBUDEV_PROC(udev_device_get_properties_list_entry); + LOAD_LIBUDEV_PROC(udev_device_get_tags_list_entry); + LOAD_LIBUDEV_PROC(udev_device_get_sysattr_list_entry); + LOAD_LIBUDEV_PROC(udev_device_get_property_value); + LOAD_LIBUDEV_PROC(udev_device_get_driver); + LOAD_LIBUDEV_PROC(udev_device_get_devnum); + LOAD_LIBUDEV_PROC(udev_device_get_action); + LOAD_LIBUDEV_PROC(udev_device_get_seqnum); + LOAD_LIBUDEV_PROC(udev_device_get_usec_since_initialized); + LOAD_LIBUDEV_PROC(udev_device_get_sysattr_value); + LOAD_LIBUDEV_PROC(udev_device_set_sysattr_value); + LOAD_LIBUDEV_PROC(udev_device_has_tag); + + LOAD_LIBUDEV_PROC(udev_monitor_ref); + LOAD_LIBUDEV_PROC(udev_monitor_unref); + LOAD_LIBUDEV_PROC(udev_monitor_get_udev); + LOAD_LIBUDEV_PROC(udev_monitor_new_from_netlink); + LOAD_LIBUDEV_PROC(udev_monitor_enable_receiving); + LOAD_LIBUDEV_PROC(udev_monitor_set_receive_buffer_size); + LOAD_LIBUDEV_PROC(udev_monitor_get_fd); + LOAD_LIBUDEV_PROC(udev_monitor_receive_device); + LOAD_LIBUDEV_PROC(udev_monitor_filter_add_match_subsystem_devtype); + LOAD_LIBUDEV_PROC(udev_monitor_filter_add_match_tag); + LOAD_LIBUDEV_PROC(udev_monitor_filter_update); + LOAD_LIBUDEV_PROC(udev_monitor_filter_remove); + + LOAD_LIBUDEV_PROC(udev_enumerate_ref); + LOAD_LIBUDEV_PROC(udev_enumerate_unref); + LOAD_LIBUDEV_PROC(udev_enumerate_get_udev); + LOAD_LIBUDEV_PROC(udev_enumerate_new); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_subsystem); + LOAD_LIBUDEV_PROC(udev_enumerate_add_nomatch_subsystem); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_sysattr); + LOAD_LIBUDEV_PROC(udev_enumerate_add_nomatch_sysattr); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_property); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_sysname); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_tag); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_parent); + LOAD_LIBUDEV_PROC(udev_enumerate_add_match_is_initialized); + LOAD_LIBUDEV_PROC(udev_enumerate_add_syspath); + LOAD_LIBUDEV_PROC(udev_enumerate_scan_devices); + LOAD_LIBUDEV_PROC(udev_enumerate_scan_subsystems); + LOAD_LIBUDEV_PROC(udev_enumerate_get_list_entry); + + LOAD_LIBUDEV_PROC(udev_queue_ref); + LOAD_LIBUDEV_PROC(udev_queue_unref); + LOAD_LIBUDEV_PROC(udev_queue_get_udev); + LOAD_LIBUDEV_PROC(udev_queue_new); + LOAD_LIBUDEV_PROC(udev_queue_get_udev_is_active); + LOAD_LIBUDEV_PROC(udev_queue_get_queue_is_empty); + LOAD_LIBUDEV_PROC(udev_queue_get_fd); + LOAD_LIBUDEV_PROC(udev_queue_flush); + + LOAD_LIBUDEV_PROC(udev_hwdb_new); + LOAD_LIBUDEV_PROC(udev_hwdb_ref); + LOAD_LIBUDEV_PROC(udev_hwdb_unref); + LOAD_LIBUDEV_PROC(udev_hwdb_get_properties_list_entry); + + LOAD_LIBUDEV_PROC(udev_util_encode_string); + +# undef LOAD_LIBUDEV_PROC + + udev = libudev->udev_new(); if (udev == NULL) { perror("[flutter-pi] Could not create udev instance. udev_new"); - return errno; + return NULL; } libinput = libinput_udev_create_context( @@ -2104,38 +2166,108 @@ static int init_user_input(void) { .open_restricted = libinput_interface_on_open, .close_restricted = libinput_interface_on_close }, - NULL, + udev, udev ); if (libinput == NULL) { perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); - udev_unref(udev); - return errno; + libudev->udev_unref(udev); + return NULL; } ok = libinput_udev_assign_seat(libinput, "seat0"); if (ok < 0) { fprintf(stderr, "[flutter-pi] Could not assign udev seat to libinput instance. libinput_udev_assign_seat: %s\n", strerror(-ok)); libinput_unref(libinput); - udev_unref(udev); - return -ok; + libudev->udev_unref(udev); + return NULL; } - ok = sd_event_add_io( - flutterpi.event_loop, - &libinput_event_source, - libinput_get_fd(libinput), - EPOLLIN | EPOLLRDHUP | EPOLLPRI, - on_libinput_ready, + return libinput; + +#endif +} + +static struct libinput *try_create_path_backed_libinput(void) { + struct libinput_device *dev; + struct libinput *libinput; + + libinput = libinput_path_create_context( + &(const struct libinput_interface) { + .open_restricted = libinput_interface_on_open, + .close_restricted = libinput_interface_on_close + }, NULL ); - if (ok < 0) { - fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); - libinput_unref(libinput); - udev_unref(udev); - return -ok; + if (libinput == NULL) { + perror("[flutter-pi] Could not create path-backed libinput instance. libinput_path_create_context"); + return NULL; } + for (int i = 0; i < flutterpi.input.input_devices_glob.gl_pathc; i++) { + printf("adding %s to input_devices\n", flutterpi.input.input_devices_glob.gl_pathv[i]); + + dev = libinput_path_add_device( + libinput, + flutterpi.input.input_devices_glob.gl_pathv[i] + ); + if (dev == NULL) { + fprintf( + stderr, + "[flutter-pi] Could not add input device \"%s\" to libinput. libinput_path_add_device: %s\n", + flutterpi.input.input_devices_glob.gl_pathv[i], + strerror(errno) + ); + } + } + + return libinput; +} + +static int init_user_input(void) { + sd_event_source *libinput_event_source, *stdin_event_source; + struct libinput *libinput; + int ok; + + libinput = NULL; + if (flutterpi.input.use_paths == false) { + libinput = try_create_udev_backed_libinput(); + } + + if (libinput == NULL) { + libinput = try_create_path_backed_libinput(); + } + + libinput_event_source = NULL; + if (libinput != NULL) { + ok = sd_event_add_io( + flutterpi.event_loop, + &libinput_event_source, + libinput_get_fd(libinput), + EPOLLIN | EPOLLRDHUP | EPOLLPRI, + on_libinput_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); +# ifndef BUILD_WITHOUT_UDEV_SUPPORT + if (libinput_get_user_data(libinput) != NULL) { + struct udev *udev = libinput_get_user_data(libinput); + libinput_unref(libinput); + flutterpi.input.libudev.udev_unref(udev); + } else { + libinput_unref(libinput); + } +# else + libinput_unref(libinput); +# endif + return -ok; + } + } else { + fprintf(stderr, "[flutter-pi] Could not initialize input. Flutter-pi will run without user input.\n"); + } + + stdin_event_source = NULL; if (flutterpi.input.disable_text_input == false) { ok = sd_event_add_io( flutterpi.event_loop, @@ -2146,23 +2278,18 @@ static int init_user_input(void) { NULL ); if (ok < 0) { - fprintf(stderr, "[flutter-pi] Could not add libinput callback to main loop. sd_event_add_io: %s\n", strerror(-ok)); - sd_event_source_unrefp(&libinput_event_source); - libinput_unref(libinput); - udev_unref(udev); - return -ok; + fprintf(stderr, "[flutter-pi] Could not add callback for console input. sd_event_add_io: %s\n", strerror(-ok)); } ok = console_make_raw(); if (ok == 0) { console_flush_stdin(); } else { - fprintf(stderr, "[flutter-pi] WARNING: could not make stdin raw\n"); + fprintf(stderr, "[flutter-pi] Could not make stdin raw. Flutter-pi will run without text input support.\n"); sd_event_source_unrefp(&stdin_event_source); } } - flutterpi.input.udev = udev; flutterpi.input.libinput = libinput; flutterpi.input.libinput_event_source = libinput_event_source; flutterpi.input.stdin_event_source = stdin_event_source; @@ -2183,7 +2310,7 @@ static bool setup_paths(void) { asprintf(&kernel_blob_path, "%s/kernel_blob.bin", flutterpi.flutter.asset_bundle_path); asprintf(&app_elf_path, "%s/app.so", flutterpi.flutter.asset_bundle_path); - if (flutterpi.flutter.runtime_mode == kDebug) { + if (flutterpi.flutter.is_aot == false) { if (!PATH_EXISTS(kernel_blob_path)) { fprintf(stderr, "[flutter-pi] Could not find \"kernel.blob\" file inside \"%s\", which is required for debug mode.\n", flutterpi.flutter.asset_bundle_path); return false; @@ -2213,70 +2340,92 @@ static bool setup_paths(void) { static bool parse_cmd_args(int argc, char **argv) { glob_t input_devices_glob = {0}; bool input_specified = false; - int opt, longopt_index = 0, runtime_mode_int = kDebug, disable_text_input_int = false; + int opt; + int longopt_index = 0; + int is_aot_int = false; + int disable_text_input_int = false; struct option long_options[] = { - {"release", no_argument, &runtime_mode_int, kRelease}, - {"profile", no_argument, &runtime_mode_int, kProfile}, - {"debug", no_argument, &runtime_mode_int, kDebug}, - {"input", required_argument, 0, 'i'}, - {"orientation", required_argument, 0, 'o'}, - {"rotation", required_argument, 0, 'r'}, - {"no-text-input", required_argument, &disable_text_input_int, true}, + {"aot", no_argument, &is_aot_int, true}, + {"input", required_argument, NULL, 'i'}, + {"orientation", required_argument, NULL, 'o'}, + {"rotation", required_argument, NULL, 'r'}, + {"no-text-input", no_argument, &disable_text_input_int, true}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - while (1) { + bool finished_parsing_options = false; + while (!finished_parsing_options) { longopt_index = 0; - opt = getopt_long(argc, argv, "+rpdi:o:r:h", long_options, &longopt_index); + opt = getopt_long(argc, argv, "+i:o:r:h", long_options, &longopt_index); - if (opt == -1) { - break; - } else if (((opt == 0) && (longopt_index == 3)) || (opt == 'i')) { - glob(optarg, GLOB_BRACE | GLOB_TILDE | (input_specified ? GLOB_APPEND : 0), NULL, &input_devices_glob); - input_specified = true; - } else if (((opt == 0) && (longopt_index == 4)) || (opt == 'o')) { - if (STREQ(optarg, "portrait_up")) { - flutterpi.view.orientation = kPortraitUp; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "landscape_left")) { - flutterpi.view.orientation = kLandscapeLeft; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "portrait_down")) { - flutterpi.view.orientation = kPortraitDown; - flutterpi.view.has_orientation = true; - } else if (STREQ(optarg, "landscape_right")) { - flutterpi.view.orientation = kLandscapeRight; - flutterpi.view.has_orientation = true; - } else { - fprintf( - stderr, - "ERROR: Invalid argument for --orientation passed.\n" - "Valid values are \"portrait_up\", \"landscape_left\", \"portrait_down\", \"landscape_right\".\n" - "%s", - usage - ); - return false; - } - } else if (((opt == 0) && (longopt_index == 5)) || (opt == 'r')) { - errno = 0; - long rotation = strtol(optarg, NULL, 0); - if ((errno != 0) || ((rotation != 0) && (rotation != 90) && (rotation != 180) && (rotation != 270))) { - fprintf( - stderr, - "ERROR: Invalid argument for --rotation passed.\n" - "Valid values are 0, 90, 180, 270.\n" - "%s", - usage - ); + switch (opt) { + case 0: + // flag was encountered. just continue + break; + case 'i': + glob(optarg, GLOB_BRACE | GLOB_TILDE | (input_specified ? GLOB_APPEND : 0), NULL, &input_devices_glob); + input_specified = true; + break; + + case 'o': + if (STREQ(optarg, "portrait_up")) { + flutterpi.view.orientation = kPortraitUp; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "landscape_left")) { + flutterpi.view.orientation = kLandscapeLeft; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "portrait_down")) { + flutterpi.view.orientation = kPortraitDown; + flutterpi.view.has_orientation = true; + } else if (STREQ(optarg, "landscape_right")) { + flutterpi.view.orientation = kLandscapeRight; + flutterpi.view.has_orientation = true; + } else { + fprintf( + stderr, + "ERROR: Invalid argument for --orientation passed.\n" + "Valid values are \"portrait_up\", \"landscape_left\", \"portrait_down\", \"landscape_right\".\n" + "%s", + usage + ); + return false; + } + break; + + case 'r': + errno = 0; + long rotation = strtol(optarg, NULL, 0); + if ((errno != 0) || ((rotation != 0) && (rotation != 90) && (rotation != 180) && (rotation != 270))) { + fprintf( + stderr, + "ERROR: Invalid argument for --rotation passed.\n" + "Valid values are 0, 90, 180, 270.\n" + "%s", + usage + ); + return false; + } + + flutterpi.view.rotation = rotation; + break; + + case 'h': + printf("%s", usage); return false; - } - flutterpi.view.rotation = rotation; - } else if (((opt == 0) && (longopt_index == 7)) || ((opt == 'h') || (opt == '?') || (opt == ':'))) { - printf("%s", usage); - return false; + case '?': + case ':': + printf("Invalid option specified.\n%s", usage); + return false; + + case -1: + finished_parsing_options = true; + break; + + default: + break; } } @@ -2293,8 +2442,9 @@ static bool parse_cmd_args(int argc, char **argv) { flutterpi.input.use_paths = input_specified; flutterpi.flutter.asset_bundle_path = strdup(argv[optind]); - flutterpi.flutter.runtime_mode = (enum flutter_runtime_mode) runtime_mode_int; + flutterpi.flutter.is_aot = is_aot_int; flutterpi.input.disable_text_input = disable_text_input_int; + flutterpi.input.input_devices_glob = input_devices_glob; argv[optind] = argv[0]; flutterpi.flutter.engine_argc = argc - optind; From caeca281001619043be077eba73c44553d20ff75 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 5 Aug 2020 15:34:36 +0200 Subject: [PATCH 13/14] - include new dependencies in readme - add "Building the app.so" section stub --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2593c6f1..504dd9fd 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,10 @@ If you encounter issues running flutter-pi on any of the supported platforms lis 1. **[Running your App on the Raspberry Pi](#running-your-app-on-the-raspberry-pi)** 1.1 [Configuring your Raspberry Pi](#configuring-your-raspberry-pi) 1.2 [Patching the App](#patching-the-app) -1.3 [Building the Asset bundle](#building-the-asset-bundle) -1.4 [Running your App with flutter-pi](#running-your-app-with-flutter-pi) -2. **[Dependencies](#dependencies)** -2.1 [flutter engine](#flutter-engine) -2.2 [graphics libs](#graphics-libs) -2.3 [fonts](#fonts) +1.3 [Building the Asset bundle](#building-the-asset-bundle) +1.4 [Building the app.so](#building-the-app-so) +1.5 [Running your App with flutter-pi](#running-your-app-with-flutter-pi) +2. **[Dependencies](#dependencies)** 3. **[Compiling flutter-pi (on the Raspberry Pi)](#compiling-flutter-pi-on-the-raspberry-pi)** 4. **[Performance](#performance)** 5. **[Keyboard Input](#keyboard-input)** @@ -88,6 +86,9 @@ 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. +### Building the `app.so` (for running your app in Release/Profile mode) +** WIP ** + ### Running your App with flutter-pi ```txt USAGE: @@ -125,7 +126,7 @@ of the flutter app you're trying to run. flutter-pi needs `libflutter_engine.so` and `flutter_embedder.h` to compile. It also needs the flutter engine's `icudtl.dat` at runtime. You have two options here: -- you build the engine yourself. takes a lot of time, and it most probably won't work on the first try. But once you have it set up, you have unlimited freedom on which engine version you want to use. You can find some rough guidelines [here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1). [Andrew jones](https://github.com/andyjjones28) is working on some more detailed instructions. +- you build the engine yourself. takes a lot of time, and it most probably won't work on the first try. But once you have it set up, you have unlimited freedom on which engine version you want to use. You can find some rough guidelines [here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1). - you can use the pre-built engine binaries I am providing [in the _engine-binaries_ branch of this project.](https://github.com/ardera/flutter-pi/tree/engine-binaries). I will only provide binaries for some engine versions though (most likely the stable ones). ### graphics libs @@ -138,10 +139,11 @@ The flutter engine, by default, uses the _Arial_ font. Since that doesn't come i sudo apt install ttf-mscorefonts-installer fontconfig sudo fc-cache ``` -### libgpiod (for the included GPIO plugin) +### libgpiod (for the included GPIO plugin), libsystemd, libudev ```bash -sudo apt-get install gpiod libgpiod-dev +sudo apt-get install gpiod libgpiod-dev libsystemd-dev libudev-dev ``` + ## Compiling flutter-pi (on the Raspberry Pi) fetch all the dependencies, clone this repo and run ```bash From 0378aef417fdc252de78aeb65ca88e10c2377c64 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Wed, 5 Aug 2020 15:36:33 +0200 Subject: [PATCH 14/14] update usage --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 504dd9fd..0a0f50d7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ If you encounter issues running flutter-pi on any of the supported platforms lis 1.1 [Configuring your Raspberry Pi](#configuring-your-raspberry-pi) 1.2 [Patching the App](#patching-the-app) 1.3 [Building the Asset bundle](#building-the-asset-bundle) -1.4 [Building the app.so](#building-the-app-so) +1.4 [Building the app.so](#building-the-appso-for-running-your-app-in-releaseprofile-mode) 1.5 [Running your App with flutter-pi](#running-your-app-with-flutter-pi) 2. **[Dependencies](#dependencies)** 3. **[Compiling flutter-pi (on the Raspberry Pi)](#compiling-flutter-pi-on-the-raspberry-pi)** @@ -92,27 +92,57 @@ After that `flutter/examples/flutter_gallery/build/flutter_assets` would be a va ### Running your App with flutter-pi ```txt USAGE: - flutter-pi [options] [flutter engine options...] + flutter-pi [options] [flutter engine options] OPTIONS: - -i Appends all files matching this glob pattern - to the list of input (touchscreen, mouse, touchpad) - devices. Brace and tilde expansion is enabled. - Every file that matches this pattern, but is not - a valid touchscreen / -pad or mouse is silently - ignored. - If no -i options are given, all files matching - "/dev/input/event*" will be used as inputs. - This should be what you want in most cases. - Note that you need to properly escape each glob pattern - you use as a parameter so it isn't implicitly expanded - by your shell. - - -h Show this help and exit. + -i, --input Appends all files matching this glob pattern to the + list of input (touchscreen, mouse, touchpad, + keyboard) devices. Brace and tilde expansion is + enabled. + Every file that matches this pattern, but is not + a valid touchscreen / -pad, mouse or keyboard is + silently ignored. + If no -i options are given, flutter-pi will try to + use all input devices assigned to udev seat0. + If that fails, or udev is not installed, flutter-pi + will fallback to using all devices matching + "/dev/input/event*" as inputs. + In most cases, there's no need to specify this + option. + Note that you need to properly escape each glob + pattern you use as a parameter so it isn't + implicitly expanded by your shell. + + --aot Run the app in AOT mode. The AOT snapshot + of the app ("app.so") must be located inside the + asset bundle directory. + + -o, --orientation Start the app in this orientation. Valid + for are: portrait_up, landscape_left, + portrait_down, landscape_right. + For more information about this orientation, see + the flutter docs for the "DeviceOrientation" + enum. + Only one of the --orientation and --rotation + options can be specified. + + -r, --rotation Start the app with this rotation. This is just an + alternative, more intuitive way to specify the + startup orientation. The angle is in degrees and + clock-wise. + Valid values are 0, 90, 180 and 270. + + --no-text-input Disable text input from the console. + This means flutter-pi won't configure the console + to raw/non-canonical mode. + + -h, --help Show this help and exit. EXAMPLES: - flutter-pi -i "/dev/input/event{0,1}" -i "/dev/input/event{2,3}" /home/helloworld_flutterassets + flutter-pi -i "/dev/input/event{0,1}" -i "/dev/input/event{2,3}" /home/pi/helloworld_flutterassets flutter-pi -i "/dev/input/mouse*" /home/pi/helloworld_flutterassets + flutter-pi -o portrait_up ./flutter_assets + flutter-pi -r 90 ./flutter_assets flutter-pi /home/pi/helloworld_flutterassets ```