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 7bdfb0f4..b765a615 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 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/elm327plugin.c - src/plugins/services.c - src/plugins/testplugin.c - src/plugins/text_input.c - src/plugins/raw_keyboard.c - src/plugins/spidev.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) @@ -121,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 @@ -141,10 +153,14 @@ target_compile_options(flutter-pi PRIVATE ${GBM_CFLAGS} ${DRM_CFLAGS} ${GLESV2_CFLAGS} ${EGL_CFLAGS} ${GPIOD_CFLAGS} -ggdb - -DBUILD_TEXT_INPUT_PLUGIN - -DBUILD_ELM327_PLUGIN - -DBUILD_SPIDEV_PLUGIN - -DBUILD_TEST_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/Makefile b/Makefile index e0ddb566..bdb74622 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,46 @@ -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 libinput libudev) \ + -DBUILD_TEXT_INPUT_PLUGIN \ + -DBUILD_GPIOD_PLUGIN \ + -DBUILD_SPIDEV_PLUGIN \ + -DBUILD_TEST_PLUGIN \ + -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ + -O0 -ggdb \ + $(CFLAGS) + +REAL_LDFLAGS = \ + $(shell pkg-config --libs gbm libdrm glesv2 egl libsystemd libinput libudev) \ + -lrt \ + -lflutter_engine \ + -lpthread \ + -ldl \ + -lm \ + -rdynamic \ + $(LDFLAGS) + +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/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/gpiod.c \ + src/plugins/spidev.c \ + src/plugins/omxplayer_video_player.c -SOURCES = src/flutter-pi.c 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 \ - src/plugins/raw_keyboard.c src/plugins/gpiod.c src/plugins/spidev.c OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) 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/README.md b/README.md index 2593c6f1..0a0f50d7 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-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)** 4. **[Performance](#performance)** 5. **[Keyboard Input](#keyboard-input)** @@ -88,30 +86,63 @@ 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: - 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 ``` @@ -125,7 +156,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 +169,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 diff --git a/include/aot.h b/include/aot.h deleted file mode 100644 index 892bbdbc..00000000 --- a/include/aot.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef _AOT_H -#define _AOT_H - -#if defined(__cplusplus) -extern "C" { -#endif -/* AOT START*/ -void* _kDartIsolateSnapshotData = NULL; -void* _kDartIsolateSnapshotInstructions = NULL; -void* _kDartVmSnapshotData = NULL; -void* _kDartVmSnapshotInstructions = NULL; - -long _kDartIsolateSnapshotDataSize = 0; -long _kDartIsolateSnapshotInstructionsSize = 0; -long _kDartVmSnapshotDataSize = 0; -long _kDartVmSnapshotInstructionsSize = 0; -/* AOT END */ -#if defined(__cplusplus) -} -#endif - -#endif diff --git a/include/collection.h b/include/collection.h new file mode 100644 index 00000000..2242f157 --- /dev/null +++ b/include/collection.h @@ -0,0 +1,407 @@ +#ifndef _COLLECTION_H +#define _COLLECTION_H + +#include +#include +#include +#include + +#include + +struct queue { + size_t start_index; + size_t length; + size_t size; + void *elements; + + size_t max_queue_size; + size_t element_size; +}; + +struct concurrent_queue { + pthread_mutex_t mutex; + pthread_cond_t is_dequeueable; + pthread_cond_t is_enqueueable; + struct queue queue; +}; + +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 + +#define QUEUE_INITIALIZER(element_type, _max_size) \ + ((struct queue) { \ + .start_index = 0, \ + .length = 0, \ + .size = 0, \ + .elements = NULL, \ + .max_queue_size = _max_queue_size, \ + .element_size = sizeof(element_type) \ + }) + +#define CQUEUE_DEFAULT_MAX_SIZE 64 + +#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) \ + }) + +#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, \ + .set = { \ + .count_pointers = 0, \ + .size = 0, \ + .pointers = NULL, \ + .max_size = _max_size, \ + .is_static = false \ + } \ + }) + + +int queue_init( + struct queue *queue, + size_t element_size, + size_t max_queue_size +); + +int queue_deinit( + struct queue *queue +); + +int queue_enqueue( + struct queue *queue, + const void *p_element +); + +int queue_dequeue( + struct queue *queue, + void *element_out +); + +int queue_peek( + struct queue *queue, + void **pelement_out +); + + +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); +} + +static inline int cqueue_unlock(struct concurrent_queue * const queue) { + return pthread_mutex_unlock(&queue->mutex); +} + +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 +); + +/* + * 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 +); + +void pset_deinit( + struct pointer_set *set +); + +int pset_put( + struct pointer_set *set, + void *pointer +); + +bool pset_contains( + const struct pointer_set *set, + const void *pointer +); + +int pset_remove( + struct pointer_set *set, + const void *pointer +); + +static inline int pset_get_count_pointers( + const struct pointer_set *set +) { + return set->count_pointers; +} + +/** + * @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; +} + +int pset_copy( + const struct pointer_set *src, + struct pointer_set *dest +); + +void pset_intersect( + struct pointer_set *src_dest, + const struct pointer_set *b +); + +int pset_union( + struct pointer_set *src_dest, + const struct pointer_set *b +); + +int pset_subtract( + struct pointer_set *minuend_difference, + const struct pointer_set *subtrahend +); + +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 *set) { + return pthread_mutex_lock(&set->mutex); +} + +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 *set, + void *pointer +) { + return pset_put(&set->set, pointer); +} + +static inline int cpset_put_( + struct concurrent_pointer_set *set, + void *pointer +) { + int ok; + + cpset_lock(set); + ok = pset_put(&set->set, pointer); + cpset_unlock(set); + + return ok; +} + +static inline bool cpset_contains_locked( + struct concurrent_pointer_set *set, + const void *pointer +) { + return pset_contains(&set->set, pointer); +} + +static inline bool cpset_contains_( + struct concurrent_pointer_set *set, + const void *pointer +) { + bool result; + + cpset_lock(set); + result = pset_contains(&set->set, pointer); + cpset_unlock(set); + + return result; +} + +static inline int cpset_remove_locked( + struct concurrent_pointer_set *set, + const void *pointer +) { + return pset_remove(&set->set, pointer); +} + +static inline int cpset_remove_( + struct concurrent_pointer_set *set, + const void *pointer +) { + int ok; + + cpset_lock(set); + ok = cpset_remove_locked(set, pointer); + cpset_unlock(set); + + return ok; +} + +static inline int cpset_get_count_pointers_locked( + const struct concurrent_pointer_set *set +) { + return set->set.count_pointers; +} + +/** + * @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; +} + +static inline int cpset_copy_into_pset_locked( + struct concurrent_pointer_set *src, + struct pointer_set *dest +) { + return pset_copy(&src->set, dest); +} + +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_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; + + if ((src == NULL) || (n == 0)) return NULL; + + dest = malloc(n); + if (dest == NULL) return NULL; + + 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 new file mode 100644 index 00000000..17deb66e --- /dev/null +++ b/include/compositor.h @@ -0,0 +1,249 @@ +#ifndef _COMPOSITOR_H +#define _COMPOSITOR_H + +#include + +#include +#include + +#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, + const FlutterPlatformViewMutation **mutations, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +); + +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 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. + */ + + 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; + GLuint gl_rbo_id; + uint32_t gem_handle; + uint32_t gem_stride; + uint32_t drm_fb_id; +}; + +struct drm_fb { + struct gbm_bo *bo; + uint32_t fb_id; +}; + +/* +struct drm_fb_backing_store { + struct compositor *compositor; + + 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; +}; + +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; + }; +}; +*/ + +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; + +int compositor_on_page_flip( + uint32_t sec, + uint32_t usec +); + +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 +); + +int compositor_remove_view_callbacks( + int64_t view_id +); + +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 +); + + +#endif \ No newline at end of file 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/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..6306825c 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -5,174 +5,435 @@ #include #include #include -#include -#include #include -#include #include #include +#include +#include +#include + +#include +#include +#include +#include +#include +//#define EGL_EGLEXT_PROTOTYPES +#include +#include +//#define GL_GLEXT_PROTOTYPES +#include +#include -#define EGL_PLATFORM_GBM_KHR 0x31D7 +#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 }; +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 : \ (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; - -typedef enum { - kVBlankRequest, - kVBlankReply, - kUpdateOrientation, - kSendPlatformMessage, - kRespondToPlatformMessage, - kFlutterTask -} 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; - }; - }; - 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); + +#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}) + +#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, + 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; + } } -struct drm_fb { - struct gbm_bo *bo; - uint32_t fb_id; +#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)) + +#define LIBINPUT_EVENT_IS_KEYBOARD(event_type) (\ + ((event_type) == LIBINPUT_EVENT_KEYBOARD_KEY)) + +enum frame_state { + kFramePending, + kFrameRendering, + kFrameRendered }; -struct pageflip_data { - struct gbm_bo *releaseable_bo; - intptr_t next_baton; -}; +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; -// 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; + /// The baton to be returned to the flutter engine when the frame can be rendered. + intptr_t baton; }; +struct compositor; + +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; + bool disable_text_input; + glob_t input_devices_glob; +# ifndef BUILD_WITHOUT_UDEV_SUPPORT + struct libudev libudev; +# endif + struct libinput *libinput; + 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 + 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; + bool is_aot; + 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/include/modesetting.h b/include/modesetting.h new file mode 100644 index 00000000..1c022c75 --- /dev/null +++ b/include/modesetting.h @@ -0,0 +1,212 @@ +#ifndef _MODESETTING_H +#define _MODESETTING_H + +#include +#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 { + int type; + 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; + + void *available_planes_storage[32]; + struct pointer_set available_planes; +}; + +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 +); + +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, + 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)) + +#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/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/pluginregistry.h b/include/pluginregistry.h index c2626d8d..3d009a48 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) @@ -41,14 +44,29 @@ 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. /// 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 +); + +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/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 " +#include +#include +#include +#include + +#include +#include + +#include + +#define DBUS_OMXPLAYER_OBJECT "/org/mpris/MediaPlayer2" +#define DBUS_OMXPLAYER_PLAYER_FACE "org.mpris.MediaPlayer2.Player" +#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" + +struct omxplayer_mgr; + +struct omxplayer_video_player { + int64_t player_id; + char event_channel_name[256]; + char video_uri[256]; + + bool has_view; + int64_t view_id; + + 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 { + int orientation; + char *omxplayer_dbus_name; + bool omxplayer_online; + }; + bool loop; + float volume; + int64_t position; + struct { + bool visible; + 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/collection.c b/src/collection.c new file mode 100644 index 00000000..8d814e19 --- /dev/null +++ b/src/collection.c @@ -0,0 +1,528 @@ +#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); +} + + +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 new file mode 100644 index 00000000..16fbc1fc --- /dev/null +++ b/src/compositor.c @@ -0,0 +1,1230 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include +#define GL_GLEXT_PROTOTYPES +#include +#include + +#include +#include +#include +#include + +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; + int last_zpos; + FlutterSize last_size; + FlutterPoint last_offset; + int last_num_mutations; + FlutterPlatformViewMutation last_mutations[16]; +}; + +/* +struct plane_data { + int type; + const struct drm_plane *plane; + bool is_reserved; + int zpos; +}; +*/ + +struct compositor compositor = { + .drmdev = NULL, + .cbs = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .has_applied_modeset = false, + .should_create_window_surface_backing_store = true, + .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) { + struct view_cb_data *data; + + for_each_pointer_in_cpset(&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(&compositor.cbs); + data = get_cbs_for_view_id_locked(view_id); + cpset_unlock(&compositor.cbs); + + return data; +} + +/** + * @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 +) { + struct drm_fb *fb = userdata; + + if (fb && fb->fb_id) + drmModeRmFB(flutterpi.drm.drmdev->fd, fb->fb_id); + + free(fb); +} + +/** + * @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; + + // 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(flutterpi.drm.drmdev->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(flutterpi.drm.drmdev->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; +} + + +/** + * @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, + struct drm_rbo *out +) { + struct drm_rbo fbo; + EGLint egl_error; + GLenum gl_error; + int ok; + + eglGetError(); + glGetError(); + + 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, + 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 EINVAL; + } + + 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; + } + + 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 EINVAL; + } + + glBindRenderbuffer(GL_RENDERBUFFER, fbo.gl_rbo_id); + if (gl_error = glGetError()) { + fprintf(stderr, "[compositor] error binding renderbuffer, glBindRenderbuffer: %d\n", gl_error); + return EINVAL; + } + + 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; + } + + /* + 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 EINVAL; + } + + 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 EINVAL; + } + + 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 EINVAL; + } + + GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + */ + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + // glBindFramebuffer(GL_FRAMEBUFFER, 0); + + ok = drmModeAddFB2( + flutterpi.drm.drmdev->fd, + width, + height, + DRM_FORMAT_ARGB8888, + (const uint32_t*) &(uint32_t[4]) { + fbo.gem_handle, + 0, + 0, + 0 + }, + (const uint32_t*) &(uint32_t[4]) { + fbo.gem_stride, 0, 0, 0 + }, + (const uint32_t*) &(uint32_t[4]) { + 0, 0, 0, 0 + }, + &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; +} + +/** + * @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 +) { + 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; +} + +/** + * @brief 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(flutterpi.drm.drmdev->fd, rbo->drm_fb_id); + if (ok < 0) { + fprintf(stderr, "[compositor] error removing DRM FB, drmModeRmFB: %s\n", strerror(errno)); + } + + 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); + } +} + +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; + + gbm_target = &target->gbm; + + 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); + + 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); + + // 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; + + return 0; +} + +/** + * @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 rendertarget *target; + int ok; + + target = calloc(1, sizeof *target); + if (target == NULL) { + *out = NULL; + return ENOMEM; + } + + *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 + }; + + *out = target; + + 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; +} + +/** + * @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 rendertarget *target; + EGLint egl_error; + GLenum gl_error; + int ok; + + target = calloc(1, sizeof *target); + if (target == NULL) { + return ENOMEM; + } + + target->is_gbm = false; + target->compositor = compositor; + target->destroy = rendertarget_nogbm_destroy; + target->present = rendertarget_nogbm_present; + + eglGetError(); + glGetError(); + + 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); + ok = EINVAL; + goto fail_free_target; + } + + ok = create_drm_rbo( + flutterpi.display.width, + flutterpi.display.height, + target->nogbm.rbos + 0 + ); + if (ok != 0) { + goto fail_delete_fb; + } + + ok = create_drm_rbo( + flutterpi.display.width, + flutterpi.display.height, + target->nogbm.rbos + 1 + ); + if (ok != 0) { + goto fail_destroy_drm_rbo_0; + } + + ok = attach_drm_rbo_to_fbo(target->nogbm.gl_fbo_id, target->nogbm.rbos + target->nogbm.current_front_rbo); + if (ok != 0) { + goto fail_destroy_drm_rbo_1; + } + + target->gl_fbo_id = target->nogbm.gl_fbo_id; + + *out = target; + return 0; + + + fail_destroy_drm_rbo_1: + destroy_drm_rbo(target->nogbm.rbos + 1); + + fail_destroy_drm_rbo_0: + destroy_drm_rbo(target->nogbm.rbos + 0); + + fail_delete_fb: + glDeleteFramebuffers(1, &target->nogbm.gl_fbo_id); + + fail_free_target: + free(target); + *out = NULL; + return ok; +} + +/** + * @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; + + printf("on_destroy_backing_store_gl_fb(is_gbm: %s, backing_store: %p)\n", store->target->is_gbm ? "true" : "false", store); + + cpset_put_(&compositor->stale_rendertargets, store->target); + + if (store->should_free_on_next_destroy) { + free(store); + } else { + store->should_free_on_next_destroy = true; + } +} + +/** + * @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 flutterpi_backing_store *store; + struct compositor *compositor; + + store = backing_store->user_data; + compositor = store->target->compositor; + + printf("on_collect_backing_store(is_gbm: %s, backing_store: %p)\n", store->target->is_gbm ? "true" : "false", store); + + cpset_put_(&compositor->stale_rendertargets, store->target); + + if (store->should_free_on_next_destroy) { + free(store); + } else { + store->should_free_on_next_destroy = true; + } + + return true; +} + +/** + * @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 flutterpi_backing_store *store; + struct rendertarget *target; + struct compositor *compositor; + int ok; + + compositor = userdata; + + store = calloc(1, sizeof *store); + if (store == NULL) { + return false; + } + + printf("on_create_backing_store(%f x %f): %p\n", config->size.width, config->size.height, store); + + // 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"); + + if (ok != 0) { + free(store); + return false; + } + + compositor->should_create_window_surface_backing_store = false; + } else { + printf("Creating a No-GBM rendertarget.\n"); + + FlutterEngineTraceEventDurationBegin("rendertarget_nogbm_new"); + ok = rendertarget_nogbm_new( + &target, + compositor + ); + FlutterEngineTraceEventDurationEnd("rendertarget_nogbm_new"); + + if (ok != 0) { + free(store); + return false; + } + } + } + + 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 + }; + + memcpy(backing_store_out, &store->flutter_backing_store, sizeof(FlutterBackingStore)); + + return true; +} + +/// PRESENT FUNCS +static bool on_present_layers( + const FlutterLayer **layers, + size_t layers_count, + void *userdata +) { + struct drmdev_atomic_req *req; + struct view_cb_data *cb_data; + struct compositor *compositor; + struct drm_plane *plane; + uint32_t req_flags; + int ok; + + compositor = userdata; + + drmdev_new_atomic_req(compositor->drmdev, &req); + + cpset_lock(&compositor->cbs); + + eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); + eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); + + 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; + } + + // 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)); + } + + 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); + } + } + + for_each_pointer_in_pset(&unmounted_views, cb_data) { + 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. unmount: %s\n", strerror(ok)); + } + } + } + + for_each_pointer_in_pset(&updated_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; + break; + } + } + + 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)); + } + + 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_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; + break; + } + } + + 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)); + } + } + + 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++) { + if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { + 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) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { + drmdev_atomic_req_reserve_plane(req, plane); + break; + } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { + drmdev_atomic_req_reserve_plane(req, plane); + break; + } + } + 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; + } + + struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; + struct rendertarget *target = store->target; + + ok = target->present( + target, + req, + plane->plane->plane_id, + 0, + 0, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, + 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)); + } + } 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, + cb_data->userdata + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not present platform view. platform_view->present: %s\n", strerror(ok)); + } + } + } + } + + 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->cbs); +} + +int compositor_on_page_flip( + uint32_t sec, + uint32_t usec +) { + return 0; +} + +/// 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 +) { + struct view_cb_data *entry; + + 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(&compositor.cbs); + return ENOMEM; + } + + cpset_put_locked(&compositor.cbs, entry); + } + + entry->view_id = view_id; + entry->mount = mount; + entry->unmount = unmount; + entry->update_view = update_view; + entry->present = present; + entry->userdata = userdata; + + return cpset_unlock(&compositor.cbs); +} + +int compositor_remove_view_callbacks(int64_t view_id) { + struct view_cb_data *entry; + + cpset_lock(&compositor.cbs); + + entry = get_cbs_for_view_id_locked(view_id); + if (entry == NULL) { + return EINVAL; + } + + cpset_remove_locked(&compositor.cbs, entry); + + return cpset_unlock(&compositor.cbs); +} + +/// DRM HARDWARE PLANE RESERVATION + +/// COMPOSITOR INITIALIZATION +int compositor_initialize(struct drmdev *drmdev) { + //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_data)); + if (data == NULL) { + for_each_pointer_in_cpset(&compositor.planes, data) + free(data); + cpset_unlock(&compositor.planes); + 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(&compositor.planes, data); + } + + cpset_unlock(&compositor.planes); + */ + + compositor.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; + 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; + } + + 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; + } + + compositor.cursor.x = x; + compositor.cursor.y = y; + + return 0; +} + +const FlutterCompositor flutter_compositor = { + .struct_size = sizeof(FlutterCompositor), + .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/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 a2ba78cc..508eedcb 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -13,63 +13,100 @@ #include #include #include -#include #include #include #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #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 #include #include -#include -#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\ - -a Enable AOT\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\ + --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\ + -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\ + --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\ 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\ @@ -77,245 +114,94 @@ 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\ "; -/// 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; - -/// AOT library path, for example './libapp.so' -char* aot_library = NULL; - -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 { - 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 * *********************/ -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"); +/// Called on some flutter internal thread when the flutter +/// rendering EGLContext should be made current. +static bool on_make_current(void* userdata) { + 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; } -bool 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"); + +/// Called on some flutter internal thread to +/// clear the EGLContext. +static bool on_clear_current(void* userdata) { + 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; } -void pageflip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *userdata) { - FlutterEngineTraceEventInstant("pageflip"); - post_platform_task(&(struct flutterpi_task) { - .type = kVBlankReply, - .target_time = 0, - .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); +/// 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) { + // no-op + return true; } -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; +/// 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; } -bool present(void* userdata) { - fd_set fds; - struct gbm_bo *next_bo; - struct drm_fb *fb; - int ok; - FlutterEngineTraceEventDurationBegin("present"); +/// Called on some flutter internal thread when the flutter +/// resource uploading EGLContext should be made current. +static bool on_make_resource_current(void *userdata) { + EGLint egl_error; - eglSwapBuffers(egl.display, egl.surface); - next_bo = gbm_surface_lock_front_buffer(gbm.surface); - fb = drm_fb_get_from_bo(next_bo); + eglGetError(); - // 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; - } + 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; } - - gbm_surface_release_buffer(gbm.surface, drm.previous_bo); - drm.previous_bo = (struct gbm_bo *) next_bo; - - FlutterEngineTraceEventDurationEnd("present"); return true; } -uint32_t fbo_callback(void* userdata) { - return 0; -} -void cut_word_from_string(char* string, char* word) { + +/// Cut a word from a string, mutating "string" +static void cut_word_from_string( + char* string, + const char* word +) { size_t word_length = strlen(word); char* word_in_str = strstr(string, word); @@ -332,7 +218,10 @@ void cut_word_from_string(char* string, char* word) { } while (word_in_str[i++ + word_length] != 0); } } -const GLubyte *hacked_glGetString(GLenum name) { + +/// An override for glGetString since the real glGetString +/// won't work. +static const GLubyte *hacked_glGetString(GLenum name) { static GLubyte *extensions = NULL; if (name != GL_EXTENSIONS) @@ -414,7 +303,12 @@ const GLubyte *hacked_glGetString(GLenum name) { return extensions; } -void *proc_resolver(void* userdata, const char* name) { + +/// Called by flutter +static void *proc_resolver( + void* userdata, + const char* name +) { static int is_VC4 = -1; void *address; @@ -428,7 +322,7 @@ void *proc_resolver(void* userdata, const char* name) { 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"); @@ -439,204 +333,383 @@ void *proc_resolver(void* userdata, const char* name) { 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; } -void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { + +static void on_platform_message( + const FlutterPlatformMessage* 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)); + } +} + +/// 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 +) { + FlutterEngineResult result; + struct frame *peek; int ok; - if ((ok = plugin_registry_on_platform_message((FlutterPlatformMessage *)message)) != 0) - fprintf(stderr, "plugin_registry_on_platform_message failed: %s\n", strerror(ok)); + + 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; + } + + 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; } -void vsync_callback(void* userdata, intptr_t baton) { - post_platform_task(&(struct flutterpi_task) { - .type = kVBlankRequest, - .target_time = 0, - .baton = 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); + + ok = cqueue_peek_locked(&flutterpi.frame_queue, (void**) &peek); + if ((ok == 0) || (ok == EAGAIN)) { + bool reply_instantly = ok == EAGAIN; + + 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; + } + + 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)); + } + + cqueue_unlock(&flutterpi.frame_queue); } -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; + +static FlutterTransformation on_get_transformation(void *userdata) { + //return FLUTTER_ROTZ_TRANSFORMATION(FlutterEngineGetCurrentTime() % 10000000000000); + return flutterpi.view.view_to_display_transform; } +/// platform tasks +static int on_execute_platform_task( + sd_event_source *s, + void *userdata +) { + struct platform_task *task; + int ok; -/************************ - * PLATFORM TASK-RUNNER * - ************************/ -bool init_message_loop() { - platform_thread_id = pthread_self(); - return true; + 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; } -bool message_loop(void) { - struct timespec abstargetspec; - uint64_t currenttime, abstarget; - intptr_t baton; - while (true) { - pthread_mutex_lock(&tasklist_lock); +static int post_platform_task( + int (*callback)(void *userdata), + void *userdata +) { + struct platform_task *task; + int ok; - // 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); + 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; } + } - struct flutterpi_task *task = tasklist.next; - tasklist.next = tasklist.next->next; + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + } - 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.fd, drm.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--; - } + return 0; - if (has_baton) { - FlutterEngineOnVsync(engine, baton, ns, ns + (1000000000ull / refresh_rate)); - } - - } 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 - }); - - } 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 - ); - } + fail_unlock_event_loop: + if (pthread_self() != flutterpi.event_loop_thread) { + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + } - free(task->message); - } else if (FlutterEngineRunTask(engine, &task->task) != kSuccess) { - fprintf(stderr, "Error running platform task\n"); - return false; - }; + return ok; +} - free(task); +/// 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)); } - return true; + 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(struct flutterpi_task)); - if (!to_insert) return; - - 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)) - this = this->next; - - to_insert->next = this->next; - this->next = to_insert; - pthread_mutex_unlock(&tasklist_lock); - pthread_cond_signal(&task_added); + 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); + } + + 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; + } + + free(task); + + return 0; } -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 - }); + +static void on_post_flutter_task( + FlutterTask task, + uint64_t target_time, + void *userdata +) { + 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); + } } -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(const char *channel, - const uint8_t *restrict message, - size_t message_size, - FlutterPlatformMessageResponseHandle *responsehandle) { - struct flutterpi_task *task; + +int flutterpi_send_platform_message( + const char *channel, + const uint8_t *restrict message, + size_t message_size, + FlutterPlatformMessageResponseHandle *responsehandle +) { + 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, @@ -645,370 +718,654 @@ int flutterpi_send_platform_message(const char *channel, .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 + ); + if (ok != 0) { + if (message && message_size) { + free(msg->message); + } + free(msg->target_channel); + free(msg); + return ok; + } } return 0; } -int flutterpi_respond_to_platform_message(FlutterPlatformMessageResponseHandle *handle, - const uint8_t *restrict message, - size_t message_size) { - struct flutterpi_task *task; + +int flutterpi_respond_to_platform_message( + FlutterPlatformMessageResponseHandle *handle, + const uint8_t *restrict message, + size_t message_size +) { + 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 * - ******************/ -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"); - - if (!PATH_EXISTS(flutter.icu_data_path)) { - fprintf(stderr, "ICU Data file not find at %s.\n", flutter.icu_data_path); - return false; - } + evloop_fd = ok; - //snprintf(drm.device, sizeof(drm.device), "/dev/dri/card0"); + { + fd_set fds; + int state; + FD_ZERO(&fds); + FD_SET(evloop_fd, &fds); - return true; + const fd_set const_fds = fds; - #undef PATH_EXISTS -} + 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; + } -bool init_display(void) { - /********************** - * DRM INITIALIZATION * - **********************/ + break; + case SD_EVENT_ARMED: + pthread_mutex_unlock(&flutterpi.event_loop_mutex); - drmModeRes *resources = NULL; - drmModeConnector *connector; - drmModeEncoder *encoder = NULL; - int i, ok, area; - - 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; - } - - 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] - ); - } - } + 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; + } - 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" - ); + 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; + } - 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); + 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); - // 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; - } + pthread_mutex_unlock(&flutterpi.event_loop_mutex); + } - 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; - } + pthread_mutex_destroy(&flutterpi.event_loop_mutex); + sd_event_unrefp(&flutterpi.event_loop); - // 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]); - } + return 0; +} - 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; - } +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; } - 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; - } + return 0; +} + +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; } - 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; - } + 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; + } - 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 - ); + flutterpi.wakeup_event_loop_fd = wakeup_fd; - if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) { - connector = conn; + return 0; +} - // 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 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)) { - // 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; - } +/************************** + * 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 { - drmModeFreeConnector(conn); + fprintf(stderr, "[flutter-pi] frame queue in inconsistent state. aborting\n"); + abort(); } } - if (!connector) { - fprintf(stderr, "could not find a connected connector!\n"); - return false; + + 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)); } - 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]; + return; - 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; + 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 (!drm.mode) { - fprintf(stderr, "could not find a suitable DRM mode!\n"); - return false; + 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; + } + + if (flutterpi.view.rotation == 0) { + flutterpi.view.view_to_display_transform = FLUTTER_TRANSLATION_TRANSFORMATION(0, 0); + + 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; + + 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; + + 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 = FLUTTER_ROTZ_TRANSFORMATION(-270); + flutterpi.view.display_to_view_transform.transX = flutterpi.display.height; + } + + 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; + drmDevicePtr devices[64]; + EGLint egl_error; + int ok, num_devices; + + /********************** + * DRM INITIALIZATION * + **********************/ + + 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; } - // calculate the pixel ratio - 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; + // find a GPU that has a primary node + flutterpi.drm.drmdev = NULL; + for (int i = 0; i < num_devices; i++) { + drmDevicePtr device; + + device = devices[i]; + + if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) { + // We need a primary node. + continue; } + + 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; + } + + break; } - 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); + 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 ENOENT; + } + + // find a connected 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 ((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; + } - printf("Finding DRM encoder...\n"); - for (i = 0; i < resources->count_encoders; i++) { - encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]); - if (encoder->encoder_id == connector->encoder_id) break; - drmModeFreeEncoder(encoder); - encoder = NULL; + } + } + + if (connector == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a connected connector!\n"); + return EINVAL; } - if (encoder) { - drm.crtc_id = encoder->crtc_id; + // 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; + } + } + } + + if (mode == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a preferred output mode!\n"); + return EINVAL; + } + + flutterpi.display.width = mode->hdisplay; + flutterpi.display.height = mode->vdisplay; + flutterpi.display.refresh_rate = mode->vrefresh; + + 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 { - fprintf(stderr, "could not find a suitable crtc!\n"); - return false; + 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 (i = 0; i < resources->count_crtcs; i++) { - if (resources->crtcs[i] == drm.crtc_id) { - drm.crtc_index = i; + for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->connector->encoder_id) { break; } } - drmModeFreeResources(resources); - - drm.connector_id = connector->connector_id; + if (encoder == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a suitable DRM encoder.\n"); + return EINVAL; + } + for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { + if (crtc->crtc->crtc_id == encoder->encoder->crtc_id) { + break; + } + } + if (crtc == NULL) { + fprintf(stderr, "[flutter-pi] Could not find a suitable DRM CRTC.\n"); + return EINVAL; + } - /********************** - * GBM INITIALIZATION * - **********************/ - printf("Creating GBM device\n"); - gbm.device = gbm_create_device(drm.fd); - gbm.format = DRM_FORMAT_XRGB8888; - gbm.surface = NULL; - gbm.modifier = DRM_FORMAT_MOD_LINEAR; + ok = drmdev_configure(flutterpi.drm.drmdev, connector->connector->connector_id, encoder->encoder->encoder_id, crtc->crtc->crtc_id, mode); + if (ok != 0) return ok; - gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); + // 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 (!gbm.surface) { - if (gbm.modifier != DRM_FORMAT_MOD_LINEAR) { - fprintf(stderr, "GBM Surface creation modifiers requested but not supported by GBM\n"); - return false; + 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" + ); } - 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; + 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" + " resolution: %u x %u\n" + " refresh rate: %uHz\n" + " physical size: %umm x %umm\n" + " flutter device pixel ratio: %f\n" + "===================================\n", + flutterpi.display.width, flutterpi.display.height, + flutterpi.display.refresh_rate, + flutterpi.display.width_mm, flutterpi.display.height_mm, + flutterpi.display.pixel_ratio + ); + + /********************** + * GBM INITIALIZATION * + **********************/ + 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; + + 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; } /********************** @@ -1023,10 +1380,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 @@ -1034,119 +1387,144 @@ 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); - egl.eglGetPlatformDisplayEXT = (void*) eglGetProcAddress("eglGetPlatformDisplayEXT"); - printf("Getting EGL display for GBM device...\n"); - if (egl.eglGetPlatformDisplayEXT) egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); - else egl.display = eglGetDisplay((void*) gbm.device); - - if (!egl.display) { - fprintf(stderr, "Couldn't get EGL display\n"); - return false; + 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; } - - printf("Initializing EGL...\n"); - if (!eglInitialize(egl.display, &major, &minor)) { - fprintf(stderr, "failed to initialize EGL\n"); - return false; - } + eglGetError(); - 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; +#ifdef EGL_KHR_platform_gbm + 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 + 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(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(flutterpi.egl.display, EGL_EXTENSIONS); - printf("Using display %p with EGL version %d.%d\n", egl.display, major, minor); - printf("===================================\n"); printf("EGL information:\n"); - printf(" version: %s\n", eglQueryString(egl.display, EGL_VERSION)); - printf(" vendor: \"%s\"\n", eglQueryString(egl.display, EGL_VENDOR)); + printf(" 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"); - - 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(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; } configs = malloc(count * sizeof(EGLConfig)); - if (!configs) return false; + if (!configs) return ENOMEM; - 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(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; } - 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(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 == flutterpi.gbm.format) { + flutterpi.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; + } + + /**************************** + * 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; } + 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; + } - printf("Creating EGL context...\n"); - egl.context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); - if (egl.context == NULL) { - fprintf(stderr, "failed to create EGL context\n"); - return false; + 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; } + 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; + } - 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; + 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; } - if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) { - fprintf(stderr, "Could not make EGL context current to get OpenGL information\n"); - return false; + 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"); @@ -1154,613 +1532,493 @@ 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(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" " 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_handler2 = NULL, - .sequence_handler = NULL - }; - - printf("Swapping buffers...\n"); - eglSwapBuffers(egl.display, egl.surface); - - printf("Locking front buffer...\n"); - drm.previous_bo = gbm_surface_lock_front_buffer(gbm.surface); - - printf("getting new framebuffer for BO...\n"); - struct drm_fb *fb = drm_fb_get_from_bo(drm.previous_bo); - if (!fb) { - fprintf(stderr, "failed to get a new framebuffer BO\n"); - return false; + 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 clear OpenGL ES context. eglMakeCurrent: 0x%08X\n", egl_error); + return EIO; } - printf("Setting CRTC...\n"); - ok = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok) { - fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); - return false; + /// miscellaneous initialization + /// initialize the compositor + ok = compositor_initialize(flutterpi.drm.drmdev); + if (ok != 0) { + return ok; } - 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; + /// initialize the frame queue + ok = cqueue_init(&flutterpi.frame_queue, sizeof(struct frame), QUEUE_DEFAULT_MAX_SIZE); + if (ok != 0) { + return ok; } - printf("finished display setup!\n"); + /// We're starting without any rotation by default. + flutterpi_fill_view_properties(false, 0, false, 0); - return true; -} -void destroy_display(void) { - fprintf(stderr, "Deinitializing display not yet implemented\n"); -} - -bool init_aot(void){ - int fd = open(aot_library, O_RDONLY); - if (!fd) { - printf("Error open '%s'\n", aot_library); - return false; - } - struct stat statbuf; - fstat(fd, & statbuf); - char * fbase = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); - close(fd); - - Elf32_Ehdr * ehdr = (Elf32_Ehdr * ) fbase; - Elf32_Shdr * sects = (Elf32_Shdr * )(fbase + ehdr -> e_shoff); - int shsize = ehdr -> e_shentsize; - int shnum = ehdr -> e_shnum; - int shstrndx = ehdr -> e_shstrndx; - - Elf32_Shdr * shstrsect = & sects[shstrndx]; - char * shstrtab = fbase + shstrsect -> sh_offset; - - int i, ii; - Elf32_Shdr dynamic_str; - Elf32_Shdr dynamic_sym; - for (i = 0; i < shnum; i++) { - if (!strcmp(shstrtab + sects[i].sh_name, ".dynstr")) { - dynamic_str = sects[i]; // count == sects[i].sh_size - }else - if (!strcmp(shstrtab + sects[i].sh_name, ".dynsym")) { - dynamic_sym = sects[i]; //unsigned long number = dynamic_sym.sh_size / dynamic_sym.sh_entsize; - } - } - - char * d_strs = (char * )(fbase + dynamic_str.sh_offset); - Elf32_Sym * d_syms = (Elf32_Sym * )(fbase + dynamic_sym.sh_offset); - unsigned long number = dynamic_sym.sh_size / dynamic_sym.sh_entsize; - - for (ii = 0; ii < number; ii++) { - printf("Symbol value:%i, size:%i name:%s\n", d_syms[ii].st_value, d_syms[ii].st_size, d_strs + d_syms[ii].st_name); - if (!strcmp(d_strs + d_syms[ii].st_name, "_kDartIsolateSnapshotData")) { - _kDartIsolateSnapshotDataSize = d_syms[ii].st_size; - } - else if (!strcmp(d_strs + d_syms[ii].st_name, "_kDartIsolateSnapshotInstructions")) { - _kDartIsolateSnapshotInstructionsSize = d_syms[ii].st_size; - } - else if (!strcmp(d_strs + d_syms[ii].st_name, "_kDartVmSnapshotData")) { - _kDartVmSnapshotDataSize = d_syms[ii].st_size; - } - else if (!strcmp(d_strs + d_syms[ii].st_name, "_kDartVmSnapshotInstructions")) { - _kDartVmSnapshotInstructionsSize = d_syms[ii].st_size; - } - } - - munmap(fbase,statbuf.st_size); - printf("Done\n"); - return true; + return 0; } +/************************** + * FLUTTER INITIALIZATION * + **************************/ +static int init_application(void) { + FlutterRendererConfig renderer_config = {0}; + FlutterEngineResult engine_result; + FlutterProjectArgs project_args = {0}; + void *app_elf_handle; + int ok; -bool init_application(void) { - 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; - } - - if (aot_library){ - printf("Initializing AOT...\n"); - if (!init_aot()){ - fprintf(stderr, "Could not initialize AOT\n"); - return false; - } - void *library_handler; - - library_handler = dlopen(aot_library,RTLD_LAZY); - if (!library_handler){ - fprintf(stderr,"dlopen() error: %s\n", dlerror()); - return false; - }; - _kDartIsolateSnapshotData = dlsym(library_handler,"_kDartIsolateSnapshotData"); - _kDartIsolateSnapshotInstructions = dlsym(library_handler,"_kDartIsolateSnapshotInstructions"); - _kDartVmSnapshotData = dlsym(library_handler,"_kDartVmSnapshotData"); - _kDartVmSnapshotInstructions = dlsym(library_handler,"_kDartVmSnapshotInstructions"); - } else { - printf("AOT skipped\n"); + fprintf(stderr, "[flutter-pi] Could not initialize plugin registry: %s\n", strerror(ok)); + return ok; } // configure flutter rendering - flutter.renderer_config.type = kOpenGL; - flutter.renderer_config.open_gl.struct_size = sizeof(flutter.renderer_config.open_gl); - flutter.renderer_config.open_gl.make_current = make_current; - flutter.renderer_config.open_gl.clear_current = clear_current; - flutter.renderer_config.open_gl.present = present; - flutter.renderer_config.open_gl.fbo_callback = fbo_callback; - flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; - flutter.renderer_config.open_gl.surface_transformation = transformation_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 = _kDartIsolateSnapshotDataSize; - flutter.args.isolate_snapshot_data = _kDartIsolateSnapshotData; - flutter.args.isolate_snapshot_instructions_size = _kDartIsolateSnapshotInstructionsSize; - flutter.args.isolate_snapshot_instructions = _kDartIsolateSnapshotInstructions; - flutter.args.vm_snapshot_data_size = _kDartVmSnapshotDataSize; - flutter.args.vm_snapshot_data = _kDartVmSnapshotData; - flutter.args.vm_snapshot_instructions_size = _kDartVmSnapshotInstructionsSize; - flutter.args.vm_snapshot_instructions = _kDartVmSnapshotInstructions; - 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 + 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, } }; - - // 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; - - 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 - 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_running = true; - - // update window size - ok = 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; - } - - return true; -} -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)); - } -} - -/**************** - * Input-Output * - ****************/ -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 + // configure the project + project_args = (FlutterProjectArgs) { + .struct_size = sizeof(FlutterProjectArgs), + .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_data_size = 0, + .vm_snapshot_instructions = NULL, + .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 = on_post_flutter_task + } + }, + .shutdown_dart_vm_when_done = true, + .compositor = &flutter_compositor }; - 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]); - 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) { - perror("\n error opening the input device"); - continue; + if (flutterpi.flutter.is_aot) { + const uint8_t *vm_instr, *vm_data, *isolate_instr, *isolate_data; + + 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; } - // 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; + 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; } - 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); - 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; + 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); + return errno; } - // 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; + 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; } - 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; - } + + 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; } - // check if the device is multitouch (so a multitouch touchscreen or touchpad) - if (ISSET(absbits, ABS_MT_SLOT)) { - struct input_absinfo slotinfo; + project_args.vm_snapshot_instructions = vm_instr; + project_args.vm_snapshot_instructions_size = 0; - // 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; - } + project_args.isolate_snapshot_instructions = isolate_instr; + project_args.isolate_snapshot_instructions_size = 0; - // 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; - } + project_args.vm_snapshot_data = vm_data; + project_args.vm_snapshot_data_size = 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; + project_args.isolate_snapshot_data = isolate_data; + project_args.isolate_snapshot_data_size = 0; + } - close_continue: - close(dev.fd); - dev.fd = -1; + // spin up the 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. FlutterEngineRun: %s\n", FLUTTER_RESULT_TO_STRING(engine_result)); + return EINVAL; } - if (n_input_devices == 0) - printf("Warning: No evdev input devices configured.\n"); - - // configure the console - ok = console_make_raw(); - if (ok != 0) { - printf("[flutter-pi] warning: could not make stdin raw\n"); + // update window size + engine_result = FlutterEngineSendWindowMetricsEvent( + flutterpi.flutter.engine, + &(FlutterWindowMetricsEvent) { + .struct_size = sizeof(FlutterWindowMetricsEvent), + .width = flutterpi.view.width, + .height = flutterpi.view.height, + .pixel_ratio = flutterpi.display.pixel_ratio + } + ); + if (engine_result != kSuccess) { + fprintf(stderr, "[flutter-pi] Could not send window metrics to flutter engine.\n"); + return EINVAL; } + + return 0; +} - console_flush_stdin(); +/************** + * USER INPUT * + **************/ +static int libinput_interface_on_open(const char *path, int flags, void *userdata) { + return open(path, flags | O_CLOEXEC); +} - // 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"); +static void libinput_interface_on_close(int fd, void *userdata) { + close(fd); } -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; +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; + + 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; + } - mousepointer.x += relx; - mousepointer.y += rely; + while (event = libinput_get_event(flutterpi.input.libinput), event != NULL) { + enum libinput_event_type type = libinput_event_get_type(event); - // 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; + 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)) { + printf("pointer device was added\n"); + + 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 + }; + + 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); + + 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); - } else if (e->type == EV_ABS) { + struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); - if (e->code == ABS_MT_SLOT) { + 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; + } - // select a new active mtslot. - device->i_active_mtslot = e->value; - active_mtslot = &device->mtslots[device->i_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); - } 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; + apply_flutter_transformation(flutterpi.view.display_to_view_transform, &x, &y); + + 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); } - // if the device is associated with the mouse pointer (touchpad), update that pointer. - if (relx != 0 || rely != 0) { - struct mousepointer_mtslot *slot = active_mtslot; + 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 + }; + + data->x = x; + data->y = y; + data->timestamp = libinput_event_touch_get_time_usec(touch_event); + } else { + printf("reporting touch: up\n"); - if (device->is_pointer) { - mousepointer.x += relx; - mousepointer.y += rely; - slot = &mousepointer; - } + pointer_events[n_pointer_events++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kUp, + .timestamp = libinput_event_touch_get_time_usec(touch_event), + .x = data->x, + .y = data->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 + }; + } + } + } 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 (slot->phase == kCancel) - slot->phase = device->active_buttons ? kMove : kHover; - } - } else if ((e->code == ABS_MT_TRACKING_ID) && (active_mtslot->id == -1 || e->value == -1)) { + if (type == LIBINPUT_EVENT_POINTER_MOTION) { + double dx = libinput_event_pointer_get_dx(pointer_event); + double dy = libinput_event_pointer_get_dy(pointer_event); - // id -1 means no id, or no touch. one tracking id is equivalent one continuous touch contact. - bool before = device->active_buttons && true; + data->timestamp = libinput_event_pointer_get_time_usec(pointer_event); - 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); - } + 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 (!before != !device->active_buttons) - active_mtslot->phase = before ? kUp : kDown; + if (newx < 0) { + newx = 0; + } else if (newx > flutterpi.display.width - 1) { + newx = flutterpi.display.width - 1; } - } 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; - } + if (newy < 0) { + newy = 0; + } else if (newy > flutterpi.display.height - 1) { + newy = flutterpi.display.height - 1; + } - 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); + 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, + .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 + }; + + compositor_set_cursor_pos((int) round(x), (int) round(y)); + } 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) { + 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 = 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 = x, + .y = 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) { + } + } 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); } } - if (i_flutterevent == 0) return; - - // now, send the data to the flutter engine - 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; } -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'; @@ -1775,109 +2033,406 @@ 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; - } + } } } -void *io_loop(void *userdata) { - int n_ready_fds; - fd_set fds; - int nfds; +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; - // put all file-descriptors in the `fds` fd set - nfds = 0; - FD_ZERO(&fds); + 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; + } - for (int i = 0; i < n_input_devices; i++) { - FD_SET(input_devices[i].fd, &fds); - if (input_devices[i].fd + 1 > nfds) nfds = input_devices[i].fd + 1; + 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 NULL; } - FD_SET(drm.fd, &fds); - if (drm.fd + 1 > nfds) nfds = drm.fd + 1; - - //FD_SET(STDIN_FILENO, &fds); + libinput = libinput_udev_create_context( + &(const struct libinput_interface) { + .open_restricted = libinput_interface_on_open, + .close_restricted = libinput_interface_on_close + }, + udev, + udev + ); + if (libinput == NULL) { + perror("[flutter-pi] Could not create libinput instance. libinput_udev_create_context"); + libudev->udev_unref(udev); + return NULL; + } - const fd_set const_fds = 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); + libudev->udev_unref(udev); + return NULL; + } - 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); + return libinput; - 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; +#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 (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) + ); } - - if (FD_ISSET(drm.fd, &fds)) { - drmHandleEvent(drm.fd, &drm.evctx); - FD_CLR(drm.fd, &fds); - n_ready_fds--; + } + + 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; } - - if (FD_ISSET(STDIN_FILENO, &fds)) { - on_console_input(); - FD_CLR(STDIN_FILENO, &fds); - n_ready_fds--; + } 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, + &stdin_event_source, + STDIN_FILENO, + EPOLLIN, + on_stdin_ready, + NULL + ); + if (ok < 0) { + fprintf(stderr, "[flutter-pi] Could not add callback for console input. sd_event_add_io: %s\n", strerror(-ok)); } - if (n_ready_fds > 0) { - on_evdev_input(fds, n_ready_fds); + ok = console_make_raw(); + if (ok == 0) { + console_flush_stdin(); + } else { + 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); } - - fds = const_fds; } - return NULL; + flutterpi.input.libinput = libinput; + flutterpi.input.libinput_event_source = libinput_event_source; + flutterpi.input.stdin_event_source = stdin_event_source; + + return 0; } -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)); + + +static bool setup_paths(void) { + char *kernel_blob_path, *icu_data_path, *app_elf_path; + #define PATH_EXISTS(path) (access((path),R_OK)==0) + + 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.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; + } + } 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 +} -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}; + int opt; + int longopt_index = 0; + int is_aot_int = false; + int disable_text_input_int = false; + + struct option long_options[] = { + {"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} + }; + + bool finished_parsing_options = false; + while (!finished_parsing_options) { + longopt_index = 0; + opt = getopt_long(argc, argv, "+i:o:r:h", long_options, &longopt_index); - while ((opt = getopt(argc, (char *const *) argv, "+i:a:h")) != -1) { - index++; - switch(opt) { + switch (opt) { + case 0: + // flag was encountered. just continue + break; case 'i': - input_specified = true; glob(optarg, GLOB_BRACE | GLOB_TILDE | (input_specified ? GLOB_APPEND : 0), NULL, &input_devices_glob); - index++; + input_specified = true; break; - case 'a': - aot_library = optarg; - index++; + + 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': - default: 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; } } - 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 (optind >= argc) { fprintf(stderr, "error: expected asset bundle path after options.\n"); @@ -1885,57 +2440,78 @@ 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.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]; - 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; - for (int i=0; i +#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; + } + + 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; + 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; + struct drm_plane *plane; + 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->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; +} + +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 +) { + struct drmdev_atomic_req *augment; + int ok; + + 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) { + 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) { + drmdev_destroy_atomic_req(augment); + return ok; + } + + ok = drmdev_atomic_req_put_crtc_property(req, "ACTIVE", 1); + if (ok != 0) { + drmdev_destroy_atomic_req(augment); + return ok; + } + + ok = drmModeAtomicMerge(req->atomic_req, augment->atomic_req); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not apply modesetting properties to atomic request. drmModeAtomicMerge"); + drmdev_destroy_atomic_req(augment); + return ok; + } + + drmdev_destroy_atomic_req(augment); + + 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/platformchannel.c b/src/platformchannel.c index f0adf61e..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 { @@ -1010,43 +1008,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,10 +1099,15 @@ 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); - - if (buffer != NULL) free(buffer); - + 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: %s\n", FLUTTER_RESULT_TO_STRING(result)); + } + + if (buffer != NULL) { + free(buffer); + } + return (result == kSuccess) ? 0 : EINVAL; } @@ -1095,6 +1119,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 +1168,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 +1215,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 +1335,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..9778302f 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 @@ -15,30 +21,31 @@ #ifdef BUILD_TEST_PLUGIN # include #endif -#ifdef BUILD_ELM327_PLUGIN -# include -#endif #ifdef BUILD_GPIOD_PLUGIN # include #endif #ifdef BUILD_SPIDEV_PLUGIN # include #endif +#ifdef BUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN +# include +#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 { + +struct plugin_registry { + 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; - -} pluginregistry; + struct concurrent_pointer_set platch_obj_cbs; +} plugin_registry; /// array of plugins that are statically included in flutter-pi. struct flutterpi_plugin hardcoded_plugins[] = { @@ -53,136 +60,188 @@ 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 #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 }; +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(&plugin_registry.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(&plugin_registry.platch_obj_cbs); + data = plugin_registry_get_cb_data_by_channel_locked(channel); + cpset_unlock(&plugin_registry.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) { - ok = pluginregistry.plugins[i].init(); - if (ok != 0) return ok; + 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 < 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", plugin_registry.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(&plugin_registry.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(&plugin_registry.platch_obj_cbs); + return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle*) message->response_handle); + } - platch_free_obj(&object); - return 0; - } + data_copy = *data; + cpset_unlock(&plugin_registry.platch_obj_cbs); + + ok = platch_decode((uint8_t*) message->message, message->message_size, data_copy.codec, &object); + if (ok != 0) { + 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. + ok = data_copy.callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); //, data->userdata); + if (ok != 0) { + platch_free_obj(&object); + return ok; + } - return platch_respond_not_implemented((FlutterPlatformMessageResponseHandle *) message->response_handle); + platch_free_obj(&object); + + 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(&plugin_registry.platch_obj_cbs); + + channel_dup = strdup(channel); + if (channel_dup == NULL) { + cpset_unlock(&plugin_registry.platch_obj_cbs); + return ENOMEM; + } + + 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(&plugin_registry.platch_obj_cbs); + return ENOMEM; } + + cpset_put_locked(&plugin_registry.platch_obj_cbs, data); } - - /// 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; + + data->channel = channel_dup; + data->codec = codec; + data->callback = callback; + //data->userdata = userdata; + + cpset_unlock(&plugin_registry.platch_obj_cbs); + + 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; + } } - 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; + return false; +} + +int plugin_registry_remove_receiver(const char *channel) { + struct platch_obj_cb_data *data; + + cpset_lock(&plugin_registry.platch_obj_cbs); + + data = plugin_registry_get_cb_data_by_channel_locked(channel); + if (data == NULL) { + cpset_unlock(&plugin_registry.platch_obj_cbs); + return EINVAL; } + cpset_remove_locked(&plugin_registry.platch_obj_cbs, data); + + free(data->channel); + free(data); + + cpset_unlock(&plugin_registry.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++) { - if (pluginregistry.plugins[i].deinit) { - ok = pluginregistry.plugins[i].deinit(); - if (ok != 0) return ok; + 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", plugin_registry.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(&plugin_registry.platch_obj_cbs, data) { + cpset_remove_(&plugin_registry.platch_obj_cbs, data); + if (data != NULL) { + free(data->channel); + free(data); + } } - /// free the rest - free(pluginregistry.platch_obj_cbs); + cpset_deinit(&plugin_registry.platch_obj_cbs); return 0; } 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/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c new file mode 100644 index 00000000..c781e149 --- /dev/null +++ b/src/plugins/omxplayer_video_player.c @@ -0,0 +1,1468 @@ +#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; + */ + + 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 = { + .initialized = false, + .next_unused_player_id = 1, + .players = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE) +}; + +/// Add a player instance to the player collection. +int add_player(struct omxplayer_video_player *player) { + return cpset_put_(&omxpvidpp.players, player); +} + +/// Get a player instance by its id. +struct omxplayer_video_player *get_player_by_id(int64_t player_id) { + 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; + } + } + + cpset_unlock(&omxpvidpp.players); + return NULL; +} + +/// Get a player instance by its 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); + for_each_pointer_in_cpset(&omxpvidpp.players, player) { + if (strcmp(player->event_channel_name, event_channel_name) == 0) { + cpset_unlock(&omxpvidpp.players); + return player; + } + } + + cpset_unlock(&omxpvidpp.players); + return NULL; +} + +/// Remove a player instance from the player collection. +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. +/// (*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, + 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; +} + +/// 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, + 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; +} + +/// 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; + + if (zpos == 1) { + zpos = -126; + } + + return cqueue_enqueue( + &player->mgr->task_queue, + &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .responsehandle = NULL, + .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, + size_t num_mutations, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + void *userdata +) { + struct omxplayer_video_player *player = userdata; + + if (zpos == 1) { + zpos = -126; + } + + return cqueue_enqueue( + &player->mgr->task_queue, + &(struct omxplayer_mgr_task) { + .type = kUpdateView, + .responsehandle = NULL, + .offset_x = offset_x, + .offset_y = offset_y, + .width = width, + .height = height, + .zpos = zpos + } + ); +} + +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( + 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 = 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 = 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)); + sd_bus_message_unref(msg); + return -ok; + } + + sd_bus_message_unref(msg); + + return 0; +} + +/// 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 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 = sd_bus_message_get_sender(m); + member = sd_bus_message_get_member(m); + + if (STREQ(sender, "org.freedesktop.DBus") && STREQ(member, "NameOwnerChanged")) { + ok = 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; + } + } + + return 0; +} + +/// The entry function of the manager thread. +/// 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; + 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, 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)); + 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"); + 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. + // 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), + "org.mpris.MediaPlayer2.omxplayer_%d_%lld", + (int) getpid(), + mgr->player->player_id + ); + + // open the session dbus + 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); + goto fail_remove_evch_listener; + } + + // register a callbacks that tells us when + // omxplayer has registered to the dbus + task.omxplayer_online = false; + task.omxplayer_dbus_name = dbus_name; + ok = sd_bus_match_signal( + bus, + &slot, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + 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 = -128; + pid_t me = fork(); + if (me == 0) { + char orientation_str[16] = {0}; + snprintf(orientation_str, sizeof orientation_str, "%d", task.orientation); + + // I'm the child! + prctl(PR_SET_PDEATHSIG, SIGKILL); + int _ok = execvp( + "omxplayer.bin", + (char*[]) { + "omxplayer.bin", + "--nohdmiclocksync", + "--no-osd", + "--no-keys", + "--loop", + "--layer", "-128", + "--win", "0,0,1,1", + "--orientation", orientation_str, + "--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. + ok = errno; + perror("[omxplayer_video_player plugin] Could not spawn omxplayer subprocess. fork"); + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_unref_slot; + } + + while (!task.omxplayer_online) { + 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)); + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_kill_unregistered_player; + } + + 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)); + platch_respond_native_error_std(task.responsehandle, ok); + goto fail_kill_unregistered_player; + } + } + + 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) { + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; + } + + // wait for the first frame to appear + { + struct timespec delta = { + .tv_sec = 0, + .tv_nsec = 300*1000*1000 + }; + while (nanosleep(&delta, &delta)); + } + + // pause right on the first frame + ok = 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 initial pause message: %s, %s\n", err.name, err.message); + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; + } + + sd_bus_message_unref(msg); + msg = NULL; + + // get the video duration + 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) { + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; + } + + // get the video width + 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) { + respond_sd_bus_error(task.responsehandle, &err); + goto fail_kill_registered_player; + } + + // get the video width + 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) { + 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. + 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 == 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) { + if (mgr->player->has_view) { + 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); + + mgr->player->has_view = false; + mgr->player->view_id = -1; + } + + // tell omxplayer to quit + ok = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_ROOT_FACE, + "Quit", + &err, + NULL, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send Quit message to omxplayer: %s, %s\n", err.name, err.message); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + ok = (int) waitpid(omxplayer_pid, NULL, 0); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] omxplayer quit with exit code %d\n", ok); + } + + sd_bus_unref(bus); + + plugin_registry_remove_receiver(mgr->player->event_channel_name); + + 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); + + break; + } 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(is_stream? INT64_MAX : 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) { + ok = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Play", + &err, + NULL, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send play message: %s, %s\n", err.name, err.message); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + platch_respond_success_std(task.responsehandle, NULL); + } else if (task.type == kPause) { + has_scheduled_pause_time = false; + + ok = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "Pause", + &err, + NULL, + "" + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not send pause message: %s, %s\n", err.name, err.message); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + 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 = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "VideoPos", + &err, + NULL, + "os", + "/obj/not/used", + video_pos_str + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer viewport. %s, %s\n", err.name, err.message); + continue; + } + + if (current_zpos != task.zpos) { + printf("setting omxplayer layer to %d\n", task.zpos); + ok = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetLayer", + &err, + NULL, + "x", + (int64_t) task.zpos + ); + if (ok < 0) { + fprintf(stderr, "[omxplayer_video_player plugin] Could not update omxplayer layer. %s, %s\n", err.name, err.message); + continue; + } + + current_zpos = task.zpos; + } + } 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); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + position = position / 1000; + + platch_respond_success_std(task.responsehandle, &STDINT64(position)); + } else if (task.type == kSetPosition) { + if (is_stream) { + if (task.position == -1) { + // 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).\n"); + + platch_respond_error_std( + task.responsehandle, + "state-error", + "Attempted to seek on non-seekable video (a stream)", + NULL + ); + } + } else { + ok = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_OMXPLAYER_PLAYER_FACE, + "SetPosition", + &err, + 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, %s\n", strerror(-ok), err.name, err.message); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + 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 = sd_bus_call_method( + bus, + dbus_name, + DBUS_OMXPLAYER_OBJECT, + DBUS_PROPERTY_FACE, + DBUS_PROPRETY_SET, + &err, + NULL, + "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); + respond_sd_bus_error(task.responsehandle, &err); + continue; + } + + platch_respond_success_std(task.responsehandle, NULL); + } + } + + 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: + sd_bus_slot_unref(slot); + slot = NULL; + + fail_close_dbus: + 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. +static int ensure_binding_initialized(void) { + int ok; + + 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; + } + + omxpvidpp.initialized = true; + + return 0; +} + +/// Respond to the handle with a "initialization failed" message. +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_SIZE); + if (ok != 0) { + goto fail_free_mgr; + } + + // Allocate the player metadata + player = calloc(1, sizeof(*player)); + if (player == NULL) { + goto fail_deinit_task_queue; + } + + 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, + .orientation = flutterpi.view.rotation + }); + + if (ok != 0) { + goto fail_free_player; + } + + 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) { + goto fail_free_player; + } + + // set a receiver on the videoEvents event channel + ok = plugin_registry_set_receiver( + player->event_channel_name, + kStandardMethodCall, + on_receive_evch + ); + if (ok != 0) { + goto fail_remove_player; + } + + ok = pthread_create(&mgr->thread, NULL, mgr_entry, mgr); + if (ok != 0) { + 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( + 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; + + 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; + + 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." + ); + } + + if (player->has_view) { + 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_mount, + on_unmount, + on_update_view, + NULL, + 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( + 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." + ); + } + + 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. +/// 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); +} + +int8_t omxpvidpp_is_present(void) { + return plugin_registry_is_plugin_present("omxplayer_video_player"); +} + +int omxpvidpp_init(void) { + int ok; + + ok = plugin_registry_set_receiver("flutter.io/omxplayerVideoPlayer", kStandardMethodCall, on_receive_mch); + if (ok != 0) return ok; + + return 0; +} + +int omxpvidpp_deinit(void) { + plugin_registry_remove_receiver("flutter.io/omxplayerVideoPlayer"); + + 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 bb9169f2..1eba5802 100644 --- a/src/plugins/services.c +++ b/src/plugins/services.c @@ -3,31 +3,35 @@ #include #include +#include #include -struct { +static struct { char label[256]; uint32_t primary_color; // ARGB8888 (blue is the lowest byte) char isolate_id[32]; } 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) { - memset(&(services.isolate_id), sizeof(services.isolate_id), 0); - memcpy(services.isolate_id, object->binarydata, object->binarydata_size); +static int on_receive_isolate(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { + 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); } -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; - + if (strcmp(object->method, "Clipboard.setData") == 0) { /* * Clipboard.setData(Map data) @@ -95,7 +99,7 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt } // 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; } @@ -106,14 +110,32 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt // 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); + + 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), + .width = flutterpi.view.width, + .height = flutterpi.view.height, + .pixel_ratio = flutterpi.display.pixel_ratio }); - return 0; + 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); } } + + 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) @@ -161,54 +183,93 @@ int services_on_receive_platform(char *channel, struct platch_obj *object, Flutt * 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) { + // do nothing } 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); } +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; + + 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) { int ok; - 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; + 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, 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; + goto fail_remove_navigation_receiver; } - 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; + goto fail_remove_isolate_receiver; } - 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; + goto fail_remove_platform_receiver; } - printf("[services] Done.\n"); + 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)); + goto fail_remove_accessibility_receiver; + } 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/texture_registry.c b/src/texture_registry.c new file mode 100644 index 00000000..0e56bce1 --- /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(flutterpi.flutter.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(flutterpi.flutter.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(flutterpi.flutter.engine, texture_id); + if (engine_result != kSuccess) { + return EINVAL; + } +} \ No newline at end of file