diff --git a/lib/flutter_embedder.ex b/lib/flutter_embedder.ex index 8c0cbd1..b580931 100644 --- a/lib/flutter_embedder.ex +++ b/lib/flutter_embedder.ex @@ -1,5 +1,5 @@ defmodule FlutterEmbedder do - @moduledoc File.read!("README.md") + # @moduledoc File.read!("README.md") alias FlutterEmbedder.{PlatformChannelMessage, StandardMessageCodec, StandardMethodCall} import StandardMessageCodec, only: [is_valid_dart_value: 1] defstruct [:port, :uri, :module] @@ -17,19 +17,30 @@ defmodule FlutterEmbedder do } end - def start_link(args, opts \\ []) do + def start_link(args, opts \\ [name: __MODULE__]) do GenServer.start_link(__MODULE__, args, opts) end + def send_platform_message(embedder \\ __MODULE__, channel, data) + when is_valid_dart_value(data) do + GenServer.cast(embedder, {:send_platform_message, channel, data}) + end + + def observatory_url(embedder \\ __MODULE__) do + GenServer.call(embedder, :observatory_url) + end + @impl GenServer def init(args) do case sanity_check(args) do - {:ok, args} -> - Logger.info("#{port_executable()} #{Enum.join(args, " ")}") + {:ok, flutter_args} -> + Logger.info("flutter args: #{port_executable()} #{Enum.join(flutter_args, " ")}") + + # LD_LIBRARY_PATH=/srv/erlang/lib/flutter_embedder-0.1.0/priv/ /srv/erlang/lib/flutter_embedder-0.1.0/priv/flutter_embedder /srv/erlang/lib/firmware-0.1.0/priv/flutter_assets /srv/erlang/lib/flutter_embedder-0.1.0/priv/icudtl.dat --disable-service-auth-codes --observatory-host 0.0.0.0 --observatory-port 43403 --disable-service-auth-codes --enable-service-port-fallback port = Port.open({:spawn_executable, port_executable()}, [ - {:args, args}, + {:args, flutter_args}, :binary, :exit_status, {:packet, 4}, @@ -38,28 +49,32 @@ defmodule FlutterEmbedder do [{'LD_LIBRARY_PATH', to_charlist(Application.app_dir(:flutter_embedder, ["priv"]))}]} ]) - {:ok, %__MODULE__{port: port, module: FlutterEmbedder.StubMethodCallHandler}} + {:ok, + %__MODULE__{ + port: port, + module: args[:method_call_handler] || FlutterEmbedder.StubMethodCallHandler + }} end end @impl true def terminate(_, state) do - remove_mdns_service(state) end @impl GenServer def handle_info({port, {:exit_status, status}}, %{port: port} = state) do - {:stop, {:flutter_embedder_crash, status}, state} + # {:stop, {:flutter_embedder_crash, status}, state} + Logger.error("Flutter embedder crashed: #{status}") + {:noreply, state} end - def handle_info({port, {:data, <<1, _::32, log::binary>>}}, %{port: port} = state) do + def handle_info({port, {:data, <<1, log::binary>>}}, %{port: port} = state) do Logger.info(log) case log do "flutter: Observatory listening on " <> uri -> uri = URI.parse(String.trim(uri)) state = %{state | uri: uri} - # add_mdns_service(state) {:noreply, state} _ -> @@ -67,19 +82,23 @@ defmodule FlutterEmbedder do end end - def handle_info({port, {:data, <<0, data::binary>>}}, %{port: port} = state) do + def handle_info({port, {:data, <<2, log::binary>>}}, %{port: port} = state) do + Logger.error(log) + {:noreply, state} + end + + def handle_info({port, {:data, data}}, %{port: port} = state) do platform_channel_message = PlatformChannelMessage.decode(data) - # Logger.info("#{inspect(platform_channel_message)}") + Logger.info("incomming call #{inspect(platform_channel_message)}") case StandardMethodCall.decode(platform_channel_message) do {:ok, call} -> + Logger.info("handling call: #{inspect(call)}") handle_standard_call(platform_channel_message, call, state) {:error, reason} -> Logger.error( - "Could not decode #{platform_channel_message.channel} message as StandardMethodCall: #{ - reason - } (this is probably ok)" + "Could not decode #{platform_channel_message.channel} message as StandardMethodCall: #{reason} (this is probably ok)" ) reply_bin = @@ -90,6 +109,26 @@ defmodule FlutterEmbedder do end end + @impl GenServer + def handle_cast({:send_platform_message, channel, data}, state) do + message_bin = + %FlutterEmbedder.PlatformChannelMessage{ + channel: channel, + type: 0x0, + message: <<0x0::8, FlutterEmbedder.StandardMessageCodec.encode_value(data)::binary>>, + cookie: 255 + } + |> FlutterEmbedder.PlatformChannelMessage.encode() + + true = Port.command(state.port, message_bin) + {:noreply, state} + end + + @impl GenServer + def handle_call(:observatory_url, _from, state) do + {:reply, state.uri, state} + end + def handle_standard_call( %PlatformChannelMessage{channel: channel} = call, %StandardMethodCall{method: method, args: args}, @@ -116,6 +155,11 @@ defmodule FlutterEmbedder do :not_implemented -> reply_bin = PlatformChannelMessage.encode_response(call, :not_implemented) true = Port.command(state.port, reply_bin) + + error -> + Logger.error( + "Failed to handle response from message handler: invalid value: #{inspect(error)}" + ) end {:noreply, state} @@ -132,7 +176,18 @@ defmodule FlutterEmbedder do icudtl_file = args[:icudtl_file] || Application.app_dir(:flutter_embedder, ["priv", "icudtl.dat"]) - {:ok, ["#{flutter_assets}", "#{icudtl_file}"]} + {:ok, + [ + "#{flutter_assets}", + "#{icudtl_file}", + "--disable-service-auth-codes", + "--observatory-host", + "0.0.0.0", + "--observatory-port", + "43403", + "--disable-service-auth-codes", + "--enable-service-port-fallback" + ]} end @doc false @@ -150,22 +205,4 @@ defmodule FlutterEmbedder do Logger.info("Using #{exe} for flutter_embedder") exe end - - def add_mdns_service(%{uri: uri}) do - services = [ - %{ - name: "Flutter Observatory", - protocol: "dartobservatory", - transport: "tcp", - port: uri.port, - txt_payload: [URI.encode_query(%{path: uri.path, port: uri.port})] - } - ] - - MdnsLite.add_mdns_services(services) - end - - def remove_mdns_service(_state) do - MdnsLite.remove_mdns_services("Flutter Observatory") - end end diff --git a/lib/flutter_embedder/mdns_client.ex b/lib/flutter_embedder/mdns_client.ex deleted file mode 100644 index aad63c2..0000000 --- a/lib/flutter_embedder/mdns_client.ex +++ /dev/null @@ -1,143 +0,0 @@ -defmodule FlutterEmbedder.MDNSClient do - @moduledoc "Simple MDNS Client to discover networked Nerves devices" - use GenServer - require Logger - @mdns_group {224, 0, 0, 251} - @mdns_port 5353 - - defmodule State do - defstruct mdns_socket: nil, discovered: [] - end - - @query_packet %DNS.Record{ - header: %DNS.Header{}, - qdlist: [] - } - - def start_link(args) do - GenServer.start_link(__MODULE__, args, name: __MODULE__) - end - - def discover(pid) do - query(pid, :ptr, '_dartobservatory._tcp.local') - end - - def query(client, type, domain) do - GenServer.call(client, {:query, type, domain}) - end - - @impl GenServer - def init(_args) do - send(self(), :open_mdns) - {:ok, %State{}} - end - - @impl GenServer - def handle_call({:query, type, query}, from, state) do - send_query(state.mdns_socket, type, query) - Process.send_after(self(), {:query_result, from}, 1500) - {:noreply, state} - end - - @impl GenServer - def handle_info({:query_result, from}, state) do - results = Enum.map(state.discovered, fn {_ref, data} -> data end) - GenServer.reply(from, {:ok, results}) - {:noreply, state} - end - - def handle_info({:ttl, ref}, state) do - discovered = - Enum.reject(state.discovered, fn - {^ref, _} -> true - _ -> false - end) - - {:noreply, %{state | discovered: discovered}} - end - - def handle_info(:open_mdns, state) do - udp_options = [ - :binary, - broadcast: true, - active: true, - ip: {0, 0, 0, 0}, - ifaddr: {0, 0, 0, 0}, - add_membership: {@mdns_group, {0, 0, 0, 0}}, - multicast_loop: true, - multicast_ttl: 32, - reuseaddr: true - ] - - case :gen_udp.open(0, udp_options) do - {:ok, socket} -> - {:noreply, %State{state | mdns_socket: socket}} - - error -> - {:stop, {:multicast, error}, state} - end - end - - def handle_info({:udp, socket, ip, _port, packet}, %{mdns_socket: socket} = state) do - record = DNS.Record.decode(packet) - state = handle_mdns(record.anlist, ip, state) - {:noreply, state} - end - - # i'm so sorry about this. It's not the correct way to do this. - # I don't think an MDNS client is the solution to this long term - # since it won't work on macos - defp handle_mdns([%{type: :txt, domain: domain, data: data, ttl: ttl} | rest], ip, state) do - state = - case String.split(to_string(domain), ".") do - [host, "_dartobservatory", "_tcp", "local"] -> - case URI.decode_query(to_string(data)) do - %{"port" => port, "path" => path} -> - port = String.to_integer(port) - uri = %URI{scheme: "http", host: "#{host}.local", port: port, path: path} - - duplicate_uri = - Enum.find(state.discovered, fn - {_, ^uri} -> true - _ -> false - end) - - if duplicate_uri do - state - else - ref = make_ref() - Process.send_after(self(), {:ttl, ref}, ttl * 1000) - %{state | discovered: [{ref, uri} | state.discovered]} - end - - _ -> - state - end - - _ -> - state - end - - handle_mdns(rest, ip, state) - end - - defp handle_mdns([_unknown | rest], ip, state) do - handle_mdns(rest, ip, state) - end - - defp handle_mdns([], _ip, state) do - state - end - - defp send_query(socket, type, domain) do - packet = %DNS.Record{ - @query_packet - | :qdlist => [ - %DNS.Query{domain: domain, type: type, class: :in} - ] - } - - p = DNS.Record.encode(packet) - :ok = :gen_udp.send(socket, @mdns_group, @mdns_port, p) - end -end diff --git a/lib/flutter_embedder/platform_channel_message.ex b/lib/flutter_embedder/platform_channel_message.ex index 8f55171..e3d7c2c 100644 --- a/lib/flutter_embedder/platform_channel_message.ex +++ b/lib/flutter_embedder/platform_channel_message.ex @@ -1,10 +1,12 @@ defmodule FlutterEmbedder.PlatformChannelMessage do - defstruct [:cookie, :channel, :message] + defstruct [:type, :cookie, :channel, :message] + @type msg_type :: 0..255 @type cookie :: 0..255 @type channel :: String.t() @type message :: binary() @type t() :: %__MODULE__{ + type: msg_type(), cookie: cookie(), channel: channel(), message: message() @@ -16,27 +18,40 @@ defmodule FlutterEmbedder.PlatformChannelMessage do @spec decode(binary()) :: t() def decode( - <> = data ) do %__MODULE__{ + type: type, cookie: cookie, channel: channel, message: message } end + def decode(_) do + {:error, "platform message decode fail"} + end + + def encode(%__MODULE__{cookie: cookie, type: type, message: message, channel: channel}) do + channel_length = byte_size(channel) + message_length = byte_size(message) + + <> + end + @spec encode_response(t(), ok_response() | error_response() | not_implemented_response()) :: binary() def encode_response(%__MODULE__{cookie: cookie}, {:ok, value}) when is_binary(value) do - <> + <<0x1::8, cookie::8, 0, value::binary>> end def encode_response(%__MODULE__{cookie: cookie}, {:error, value}) when is_binary(value) do - <> + <<0x1::8, cookie::8, 1, value::binary>> end def encode_response(%__MODULE__{cookie: cookie}, :not_implemented) do - <> + <<0x1::8, cookie::8>> end end diff --git a/lib/flutter_embedder/standard_message_codec.ex b/lib/flutter_embedder/standard_message_codec.ex index 1959f12..dedce40 100644 --- a/lib/flutter_embedder/standard_message_codec.ex +++ b/lib/flutter_embedder/standard_message_codec.ex @@ -4,7 +4,11 @@ defmodule FlutterEmbedder.StandardMessageCodec do @kStdFalse 2 @kStdInt32 3 @kStdInt64 4 - # @kStdLargeInt 5 # not used? + + # we don't need largint? + # https://github.com/flutter/flutter/blob/841beff5204ebff30b297cf6d4342b6b6db1bb39/packages/flutter/lib/src/services/message_codecs.dart#L359-L365 + # @kStdLargeInt 5 + @kStdFloat64 6 @kStdString 7 @kStdUInt8Array 8 @@ -34,37 +38,164 @@ defmodule FlutterEmbedder.StandardMessageCodec do is_float(value) or is_boolean(value) or is_map(value) or - is_list(value) + is_list(value) or + is_nil(value) + + defmodule Buffer do + def new() do + <<>> + end + + def put_uint8(buffer, value) do + buffer <> <> + end + + def put_uint16(buffer, value) do + buffer <> <> + end + + def put_uint32(buffer, value) do + buffer <> <> + end + + def put_int32(buffer, value) do + buffer <> <> + end + + def put_int64(buffer, value) do + buffer <> <> + end + + def put_float64(buffer, value) do + buffer = align_to(buffer, 8) + buffer <> <> + end + + def put_uint8_list(buffer, list) do + buffer <> list + end + + defp mod(x, y) when x > 0, do: rem(x, y) + defp mod(x, y) when x < 0, do: rem(x, y) + y + defp mod(0, _y), do: 0 + + def align_to(buffer, alignment) do + mod = mod(byte_size(buffer) + 1, alignment) + if mod != 0 do + pad = alignment - mod + buffer <> <<0::size(pad)-unit(8)>> + else + buffer + end + end + + def write_size(buffer, size) when size < 254 do + put_uint8(buffer, size) + end + + def write_size(buffer, size) when size <= 0xFFFF do + buffer + |> put_uint8(254) + |> put_uint16(size) + end + + def write_size(buffer, size) when size <= 0xFFFFFFFF do + buffer + |> put_uint8(255) + |> put_uint32(size) + end + end @spec encode_value(value()) :: binary() - def encode_value(nil), do: <<@kStdNull>> - def encode_value(true), do: <<@kStdTrue>> - def encode_value(false), do: <<@kStdFalse>> + def encode_value(value) do + write_value(value, Buffer.new()) + end + + # https://github.com/flutter/flutter/blob/841beff5204ebff30b297cf6d4342b6b6db1bb39/packages/flutter/lib/src/services/message_codecs.dart#L366 + def write_value(value, buffer) do + cond do + is_nil(value) -> + Buffer.put_uint8(buffer, @kStdNull) + + value == true -> + Buffer.put_uint8(buffer, @kStdTrue) + + value == false -> + Buffer.put_uint8(buffer, @kStdFalse) + + is_float(value) -> + buffer + |> Buffer.put_uint8(@kStdFloat64) + |> Buffer.put_float64(value) + + is_integer(value) and abs(value) <= 0x7FFFFFFF -> + buffer + |> Buffer.put_uint8(@kStdInt32) + |> Buffer.put_int32(value) + + is_integer(value) and abs(value) <= 0x7FFFFFFFFFFFFFFF -> + buffer + |> Buffer.put_uint8(@kStdInt32) + |> Buffer.put_int64(value) + + is_binary(value) -> + buffer + |> Buffer.put_uint8(@kStdString) + |> Buffer.write_size(byte_size(value)) + |> Buffer.put_uint8_list(value) + + is_list(value) -> + buffer + |> Buffer.put_uint8(@kStdList) + |> Buffer.write_size(length(value)) + |> (fn buffer -> + Enum.reduce(value, buffer, &write_value/2) + end).() + + is_map(value) -> + buffer + |> Buffer.put_uint8(@kStdMap) + |> Buffer.write_size(map_size(value)) + |> (fn buffer -> + Enum.reduce(value, buffer, fn {key, value}, acc -> + acc = write_value(to_string(key), acc) + write_value(value, acc) + end) + end).() + + true -> + raise ArgumentError, value: value + end + end + + def encode_term(nil), do: <<@kStdNull>> + def encode_term(true), do: <<@kStdTrue>> + def encode_term(false), do: <<@kStdFalse>> - def encode_value(int32) when is_integer(int32) and abs(int32) <= 0x7FFFFFFF, + def encode_term(int32) when is_integer(int32) and abs(int32) <= 0x7FFFFFFF, do: <<@kStdInt32, int32::signed-native-32>> - def encode_value(int64) when is_integer(int64) and abs(int64) <= 0x7FFFFFFFFFFFFFFF, + def encode_term(int64) when is_integer(int64) and abs(int64) <= 0x7FFFFFFFFFFFFFFF, do: <<@kStdInt64, int64::signed-native-64>> - def encode_value(float64) when is_float(float64), + def encode_term(float64) when is_float(float64), do: <<@kStdFloat64, 0::6*8, float64::signed-native-float-64>> - def encode_value(string) when is_binary(string) and byte_size(string) < 254 do + def encode_term(string) when is_binary(string) and byte_size(string) < 254 do <<@kStdString, byte_size(string)::8, string::binary>> end - def encode_value(string) when is_binary(string) and byte_size(string) < 0xFFFF do + def encode_term(string) when is_binary(string) and byte_size(string) < 0xFFFF do <<@kStdString, 254, byte_size(string)::native-16, string::binary>> end # TODO encode @kStdUInt8Array, @kStdInt32Array, @kStdInt64Array, @kStdFloat64Array - def encode_value(value) when is_list(value) do + def encode_term(value) when is_list(value) do acc = <<@kStdList, length(value)::8>> Enum.reduce(value, acc, fn value, acc when is_valid_dart_value(value) -> - acc <> encode_value(value) + acc <> encode_term(value) _invalid, _acc -> raise ArgumentError @@ -72,13 +203,13 @@ defmodule FlutterEmbedder.StandardMessageCodec do end # i don't think Dart actually allows for maps as return values via PlatformChannel - def encode_value(%{} = map) do + def encode_term(%{} = map) do acc = <<@kStdMap, map_size(map)::8>> Enum.reduce(map, acc, fn # Dart only allows string keys {key, value}, acc when is_binary(key) and is_valid_dart_value(value) -> - acc <> encode_value(key) <> encode_value(value) + acc <> encode_term(key) <> encode_term(value) {_key, _value}, _acc -> raise ArgumentError diff --git a/lib/flutter_embedder/stub_handler.ex b/lib/flutter_embedder/stub_handler.ex index 67e8923..1fe4dbe 100644 --- a/lib/flutter_embedder/stub_handler.ex +++ b/lib/flutter_embedder/stub_handler.ex @@ -1,12 +1,8 @@ defmodule FlutterEmbedder.StubMethodCallHandler do require Logger - def handle_std_call("samples.flutter.io/battery", "getBatteryLevel", _args) do - {:ok, 69} - end - def handle_std_call(channel, method, args) do - Logger.error("Unhandled std method call #{channel}:#{method}(#{inspect(args)}") + Logger.error("Unhandled std method call #{channel}:#{method}(#{inspect(args)})") :not_implemented end end diff --git a/lib/mix.tasks.flutter.discover.ex b/lib/mix.tasks.flutter.discover.ex deleted file mode 100644 index 8e4e5d8..0000000 --- a/lib/mix.tasks.flutter.discover.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Mix.Tasks.Flutter.Discover do - use Mix.Task - alias FlutterEmbedder.MDNSClient - - def json(uri) do - %{ - name: "Nerves Flutter (#{uri.host})", - request: "attach", - deviceId: "flutter-tester", - observatoryUri: to_string(%{uri | host: "localhost"}), - type: "dart", - program: "lib/main.dart" - } - |> Jason.encode!() - end - - def run(_) do - Mix.shell().info("Discovering devices via MDNS") - - with {:ok, pid} <- MDNSClient.start_link([]), - {:ok, discovered} <- MDNSClient.discover(pid) do - for uri <- discovered do - cmd = "ssh -L #{uri.port}:localhost:#{uri.port} #{uri.host}" - - info = """ - ============================================================= - - Found Flutter Observatory: #{uri.host} - tunnel: #{cmd} - url: #{to_string(%{uri | host: "localhost"})} - launch.json: #{json(uri)} - ============================================================= - """ - - Mix.shell().info(info) - end - else - {:error, reason} -> Mix.raise("Failed to discover via MDNS: #{inspect(reason)}") - end - end -end diff --git a/src/Makefile b/src/Makefile index fbedb99..3f21bf9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ PREFIX = $(MIX_APP_PATH)/priv BUILD = $(MIX_APP_PATH)/obj -SRC = erlcmd.c embedder_platform_message.c +SRC = erlcmd.c embedder_platform_message.c util.c ifeq ($(CROSSCOMPILE),) # Not Crosscompiled build @@ -21,7 +21,7 @@ CFLAGS += -g LDFLAGS += $(shell pkg-config libdrm --libs) # LDFLAGS += -ldrm -ldl -lgbm -lGLESv2 -lEGL -lglfw -pthread LDFLAGS += -ldrm -ldl -lgbm -lGLESv2 -lEGL -pthread -LDFLAGS += -L$(PREFIX) -lflutter_engine +LDFLAGS += -L$(PREFIX) -lflutter_engine -lpthread endif all: $(PREFIX)/flutter_embedder diff --git a/src/embedder_drm.c b/src/embedder_drm.c index 6cdd80f..39221c7 100644 --- a/src/embedder_drm.c +++ b/src/embedder_drm.c @@ -1,4 +1,4 @@ -#define _GNU_SOURCE +#define _GNU_SOURCE #include #include @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -40,40 +41,61 @@ #include #include -#include "flutter_embedder.h" - #define DEBUG +#include "flutter_embedder.h" +#include "flutter_embedder.h" +#include "embedder_platform_message.h" #ifdef DEBUG -// #define LOG_PATH "/tmp/log.txt" +#define LOG_PATH "/tmp/log.txt" #define log_location stderr -#define debug(...) do { fprintf(log_location, __VA_ARGS__); fprintf(log_location, "\r\n"); fflush(log_location); } while(0) -#define error(...) do { debug(__VA_ARGS__); } while (0) +#define debug(...) \ + do \ + { \ + fprintf(log_location, __VA_ARGS__); \ + fprintf(log_location, "\r\n"); \ + fflush(log_location); \ + } while (0) +#define error(...) \ + do \ + { \ + debug(__VA_ARGS__); \ + } while (0) #else #define debug(...) -#define error(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, ""); } while(0) +#define error(...) \ + do \ + { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, ""); \ + } while (0) #endif -#define EGL_PLATFORM_GBM_KHR 0x31D7 +#define EGL_PLATFORM_GBM_KHR 0x31D7 -typedef enum { +typedef enum +{ kVBlankRequest, kVBlankReply, kFlutterTask } engine_task_type; -struct engine_task { +struct engine_task +{ struct engine_task *next; engine_task_type type; - union { + union + { FlutterTask task; - struct { + struct + { uint64_t vblank_ns; intptr_t baton; }; - struct { + struct + { char *channel; - const FlutterPlatformMessageResponseHandle *responsehandle; + const FlutterPlatformMessageResponseHandle *response_handle; size_t message_size; uint8_t *message; }; @@ -81,12 +103,14 @@ struct engine_task { uint64_t target_time; }; -struct drm_fb { +struct drm_fb +{ struct gbm_bo *bo; uint32_t fb_id; }; -struct pageflip_data { +struct pageflip_data +{ struct gbm_bo *releaseable_bo; intptr_t next_baton; }; @@ -113,7 +137,8 @@ static uint32_t refresh_period_ns = 16666666; /// allowing you to hardcode values. static double pixel_ratio = 0.0; -static struct { +static struct +{ char device[PATH_MAX]; bool has_device; int fd; @@ -125,21 +150,23 @@ static struct { drmEventContext evctx; } drm = {0}; -static struct { - struct gbm_device *device; +static struct +{ + struct gbm_device *device; struct gbm_surface *surface; - uint32_t format; - uint64_t modifier; + uint32_t format; + uint64_t modifier; } gbm = {0}; -static struct { +static struct +{ EGLDisplay display; - EGLConfig config; + EGLConfig config; EGLContext context; EGLSurface surface; - bool modifiers_supported; - char *renderer; + 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, @@ -148,7 +175,8 @@ static struct { const EGLint *attrib_list); } egl = {0}; -static struct { +static struct +{ char asset_bundle_path[240]; char kernel_blob_path[256]; char executable_path[256]; @@ -171,32 +199,49 @@ static pthread_t platform_thread_id; static struct engine_task *tasklist = NULL; static pthread_mutex_t tasklist_lock = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t task_added = PTHREAD_COND_INITIALIZER; +static pthread_cond_t task_added = PTHREAD_COND_INITIALIZER; static FlutterEngine engine; -static _Atomic bool engine_running = false; +static _Atomic bool engine_running = false; // IO stuff // 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 { +struct mousepointer_mtslot +{ // the MT tracking ID used to track this touch. - int id; + int id; int32_t flutter_slot_id; - double x, y; + double x, y; FlutterPointerPhase phase; }; static struct mousepointer_mtslot mousepointer; static pthread_t io_thread_id; +static pthread_t comms_thread_id; #define MAX_EVENTS_PER_READ 64 static struct input_event io_input_buffer[MAX_EVENTS_PER_READ]; +static plat_msg_queue_t queue; +static struct erlcmd handler; + +#define ERLCMD_FD_POLL 0 +#define EVENTFD_FD_POLL 1 +#define ENGINE_STDOUT_FD_POLL 2 +#define ENGINE_STDERR_FD_POLL 3 +#define NUM_POLLFDS 4 +static int num_pollfds = NUM_POLLFDS; +static struct pollfd fdset[NUM_POLLFDS]; +static int capstdout[2]; +static int capstderr[2]; +static char capstdoutbuffer[ERLCMD_BUF_SIZE]; + static bool make_current(void *userdata) { - if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) { + if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) + { debug("make_current: could not make the context current."); return false; } @@ -206,7 +251,8 @@ static bool make_current(void *userdata) static bool clear_current(void *userdata) { - if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { + if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + { debug("clear_current: could not clear the current context."); return false; } @@ -217,7 +263,7 @@ static bool clear_current(void *userdata) static void pageflip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *userdata) { FlutterEngineTraceEventInstant("pageflip"); - post_platform_task(&(struct engine_task) { + post_platform_task(&(struct engine_task){ .type = kVBlankReply, .target_time = 0, .vblank_ns = sec * 1000000000ull + usec * 1000ull, @@ -239,7 +285,8 @@ static struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo) // 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 (fb) + return fb; // if there's no framebuffer for the bo, we need to create one. fb = calloc(1, sizeof(struct drm_fb)); @@ -254,7 +301,8 @@ static struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo) const int num_planes = gbm_bo_get_plane_count(bo); uint32_t strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}; - for (int i = 0; i < num_planes; i++) { + 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); @@ -262,31 +310,30 @@ static struct drm_fb *drm_fb_get_from_bo(struct gbm_bo *bo) } uint32_t flags = 0; - if (modifiers[0]) { + if (modifiers[0]) + { flags = DRM_MODE_FB_MODIFIERS; } int ok = drmModeAddFB2WithModifiers(drm.fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); - if (ok) { + if (ok) + { if (flags) debug("drm_fb_get_from_bo: modifiers failed!"); - memcpy(handles, (uint32_t [4]) { - gbm_bo_get_handle(bo).u32, 0, 0, 0 - }, 16); + 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); + 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) { + if (ok) + { debug("drm_fb_get_from_bo: failed to create fb: %s\n", strerror(errno)); free(fb); return NULL; @@ -306,7 +353,8 @@ static bool present(void *userdata) struct drm_fb *fb = drm_fb_get_from_bo(next_bo); int ok = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL); - if (ok) { + if (ok) + { perror("failed to queue page flip"); return false; } @@ -327,17 +375,17 @@ static uint32_t fbo_callback(void *userdata) static void cut_word_from_string(char *string, char *word) { size_t word_length = strlen(word); - char *word_in_str = strstr(string, word); + char *word_in_str = strstr(string, word); // check if the given word is surrounded by spaces in the string - if (word_in_str - && ((word_in_str == string) || (word_in_str[-1] == ' ')) - && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' ')) - ) { - if (word_in_str[word_length] == ' ') word_length++; + if (word_in_str && ((word_in_str == string) || (word_in_str[-1] == ' ')) && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' '))) + { + if (word_in_str[word_length] == ' ') + word_length++; int i = 0; - do { + do + { word_in_str[i] = word_in_str[i + word_length]; } while (word_in_str[i++ + word_length] != 0); } @@ -350,11 +398,13 @@ static const GLubyte *hacked_glGetString(GLenum name) if (name != GL_EXTENSIONS) return glGetString(name); - if (extensions == NULL) { - GLubyte *orig_extensions = (GLubyte *) glGetString(GL_EXTENSIONS); + if (extensions == NULL) + { + GLubyte *orig_extensions = (GLubyte *)glGetString(GL_EXTENSIONS); extensions = malloc(strlen((const char *)orig_extensions) + 1); - if (!extensions) { + if (!extensions) + { debug("Could not allocate memory for modified GL_EXTENSIONS string"); return NULL; } @@ -430,7 +480,7 @@ static const GLubyte *hacked_glGetString(GLenum name) static void *proc_resolver(void *userdata, const char *name) { static int is_VC4 = -1; - void *address; + void *address; /* * The mesa V3D driver reports some OpenGL ES extensions as supported and working @@ -443,8 +493,8 @@ static void *proc_resolver(void *userdata, const char *name) // 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)) - 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."); + 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."); // if we do, and the symbol to resolve is glGetString, we return our hacked_glGetString. if (is_VC4 && (strcmp(name, "glGetString") == 0)) @@ -460,11 +510,10 @@ static void *proc_resolver(void *userdata, const char *name) static void vsync_callback(void *userdata, intptr_t baton) { - post_platform_task(&(struct engine_task) { + post_platform_task(&(struct engine_task){ .type = kVBlankRequest, .target_time = 0, - .baton = baton - }); + .baton = baton}); } /************************ @@ -478,7 +527,8 @@ static bool init_message_loop() static bool run_message_loop(void) { - while (true) { + while (true) + { pthread_mutex_lock(&tasklist_lock); // wait for a task to be inserted into the list @@ -487,13 +537,14 @@ static bool run_message_loop(void) // wait for a task to be ready to be run uint64_t currenttime; - while (tasklist->target_time > (currenttime = FlutterEngineGetCurrentTime())) { + while (tasklist->target_time > (currenttime = FlutterEngineGetCurrentTime())) + { struct timespec abstargetspec; clock_gettime(CLOCK_REALTIME, &abstargetspec); uint64_t abstarget = abstargetspec.tv_nsec + abstargetspec.tv_sec * 1000000000ull + (tasklist->target_time - currenttime); abstargetspec.tv_nsec = abstarget % 1000000000; - abstargetspec.tv_sec = abstarget / 1000000000; + abstargetspec.tv_sec = abstarget / 1000000000; pthread_cond_timedwait(&task_added, &tasklist_lock, &abstargetspec); } @@ -502,26 +553,35 @@ static bool run_message_loop(void) tasklist = tasklist->next; pthread_mutex_unlock(&tasklist_lock); - if (task->type == kVBlankRequest) { - if (scheduled_frames == 0) { + if (task->type == kVBlankRequest) + { + if (scheduled_frames == 0) + { uint64_t ns; drmCrtcGetSequence(drm.fd, drm.crtc_id, NULL, &ns); FlutterEngineOnVsync(engine, task->baton, ns, ns + refresh_period_ns); - } else { + } + else + { batons[(i_batons + (scheduled_frames - 1)) & 63] = task->baton; - } scheduled_frames++; - } else if (task->type == kVBlankReply) { - if (scheduled_frames > 1) { + } + else if (task->type == kVBlankReply) + { + if (scheduled_frames > 1) + { intptr_t baton = batons[i_batons]; i_batons = (i_batons + 1) & 63; uint64_t ns = task->vblank_ns; FlutterEngineOnVsync(engine, baton, ns, ns + refresh_period_ns); } scheduled_frames--; - } else if (task->type == kFlutterTask) { - if (FlutterEngineRunTask(engine, &task->task) != kSuccess) { + } + else if (task->type == kFlutterTask) + { + if (FlutterEngineRunTask(engine, &task->task) != kSuccess) + { debug("Error running platform task"); return false; } @@ -536,17 +596,22 @@ static bool run_message_loop(void) static void post_platform_task(struct engine_task *task) { struct engine_task *to_insert = malloc(sizeof(struct engine_task)); - if (!to_insert) return; + if (!to_insert) + return; memcpy(to_insert, task, sizeof(struct engine_task)); pthread_mutex_lock(&tasklist_lock); - if (tasklist == NULL || to_insert->target_time < tasklist->target_time) { + if (tasklist == NULL || to_insert->target_time < tasklist->target_time) + { to_insert->next = tasklist; tasklist = to_insert; - } else { + } + else + { struct engine_task *prev = tasklist; struct engine_task *current = tasklist->next; - while (current != NULL && to_insert->target_time > current->target_time) { + while (current != NULL && to_insert->target_time > current->target_time) + { prev = current; current = current->next; } @@ -560,11 +625,10 @@ static void post_platform_task(struct engine_task *task) static void flutter_post_platform_task(FlutterTask task, uint64_t target_time, void *userdata) { - post_platform_task(&(struct engine_task) { + post_platform_task(&(struct engine_task){ .type = kFlutterTask, .task = task, - .target_time = target_time - }); + .target_time = target_time}); } static bool runs_platform_tasks_on_current_thread(void *userdata) @@ -579,19 +643,22 @@ static bool path_exists(const char *path) static bool init_paths(void) { - if (!path_exists(flutter.asset_bundle_path)) { + if (!path_exists(flutter.asset_bundle_path)) + { debug("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)) { + if (!path_exists(flutter.kernel_blob_path)) + { debug("Kernel blob does not exist inside Asset Bundle Directory."); return false; } - if (!path_exists(flutter.icu_data_path)) { + if (!path_exists(flutter.icu_data_path)) + { debug("ICU Data file not find at %s.\n", flutter.icu_data_path); return false; } @@ -609,60 +676,69 @@ static bool init_display(void) drmModeEncoder *encoder = NULL; int ok; - if (!drm.has_device) { + if (!drm.has_device) + { debug("Finding a suitable DRM device, since none is given..."); - drmDevicePtr devices[64] = { NULL }; + drmDevicePtr devices[64] = {NULL}; int fd = -1; int num_devices = drmGetDevices2(0, devices, sizeof(devices) / sizeof(drmDevicePtr)); - if (num_devices < 0) { + if (num_devices < 0) + { debug("could not query drm device list: %s\n", strerror(-num_devices)); return false; } debug("looking for a suitable DRM device from %d available DRM devices...\n", num_devices); - for (int i = 0; i < num_devices; i++) { + for (int i = 0; i < num_devices; i++) + { drmDevicePtr device = devices[i]; debug(" devices[%d]: \n", i); debug(" available nodes: "); - if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) debug("DRM_NODE_PRIMARY, "); - if (device->available_nodes & (1 << DRM_NODE_CONTROL)) debug("DRM_NODE_CONTROL, "); - if (device->available_nodes & (1 << DRM_NODE_RENDER)) debug("DRM_NODE_RENDER"); - - for (int j = 0; j < DRM_NODE_MAX; j++) { - if (device->available_nodes & (1 << j)) { + if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) + debug("DRM_NODE_PRIMARY, "); + if (device->available_nodes & (1 << DRM_NODE_CONTROL)) + debug("DRM_NODE_CONTROL, "); + if (device->available_nodes & (1 << DRM_NODE_RENDER)) + debug("DRM_NODE_RENDER"); + + for (int j = 0; j < DRM_NODE_MAX; j++) + { + if (device->available_nodes & (1 << j)) + { debug(" 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] - ); + 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]); } } debug(" 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" - ); - - if (device->bustype == DRM_BUS_PLATFORM) { + 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"); + + if (device->bustype == DRM_BUS_PLATFORM) + { debug(" businfo.fullname: %s\n", device->businfo.platform->fullname); // seems like deviceinfo.platform->compatible is not really used. //debug(" deviceinfo.compatible: %s\n", device->deviceinfo.platform->compatible); } // we want a device that's DRM_NODE_PRIMARY and that we can call a drmModeGetResources on. - if (drm.has_device) continue; - if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) continue; + if (drm.has_device) + continue; + if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) + continue; debug(" opening DRM device candidate at \"%s\"...\n", device->nodes[DRM_NODE_PRIMARY]); fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR); - if (fd < 0) { + if (fd < 0) + { debug(" could not open DRM device candidate at \"%s\": %s\n", device->nodes[DRM_NODE_PRIMARY], strerror(errno)); continue; @@ -670,11 +746,14 @@ static bool init_display(void) debug(" getting resources of DRM device candidate at \"%s\"...\n", device->nodes[DRM_NODE_PRIMARY]); resources = drmModeGetResources(fd); - if (resources == NULL) { + if (resources == NULL) + { debug(" could not query DRM resources for DRM device candidate at \"%s\":", device->nodes[DRM_NODE_PRIMARY]); - if ((errno = EOPNOTSUPP) || (errno = EINVAL)) debug("doesn't look like a modeset device."); - else debug("%s\n", strerror(errno)); + if ((errno = EOPNOTSUPP) || (errno = EINVAL)) + debug("doesn't look like a modeset device."); + else + debug("%s\n", strerror(errno)); close(fd); continue; } @@ -686,25 +765,30 @@ static bool init_display(void) snprintf(drm.device, sizeof(drm.device) - 1, "%s", device->nodes[DRM_NODE_PRIMARY]); } - if (!drm.has_device) { + if (!drm.has_device) + { debug("couldn't find a usable DRM device"); return false; } } - if (drm.fd <= 0) { + if (drm.fd <= 0) + { debug("Opening DRM device..."); drm.fd = open(drm.device, O_RDWR); - if (drm.fd < 0) { + if (drm.fd < 0) + { debug("Could not open DRM device"); return false; } } - if (!resources) { + if (!resources) + { debug("Getting DRM resources..."); resources = drmModeGetResources(drm.fd); - if (resources == NULL) { + if (resources == NULL) + { if ((errno == EOPNOTSUPP) || (errno = EINVAL)) debug("%s doesn't look like a modeset device\n", drm.device); else @@ -716,71 +800,81 @@ static bool init_display(void) debug("Finding a connected connector from %d available connectors...\n", resources->count_connectors); connector = NULL; - for (int i = 0; i < resources->count_connectors; i++) { + for (int i = 0; i < resources->count_connectors; i++) + { drmModeConnector *conn = drmModeGetConnector(drm.fd, resources->connectors[i]); debug(" 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->connection == DRM_MODE_CONNECTED) ? "yes" : (conn->connection == DRM_MODE_DISCONNECTED) ? "no" + : "unknown", conn->connector_type, - (conn->connector_type == DRM_MODE_CONNECTOR_HDMIA) ? " (HDMI-A)" : - (conn->connector_type == DRM_MODE_CONNECTOR_HDMIB) ? " (HDMI-B)" : - (conn->connector_type == DRM_MODE_CONNECTOR_DSI) ? " (DSI)" : - (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ? " (DisplayPort)" : "", - conn->mmWidth, conn->mmHeight - ); - - if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) { + (conn->connector_type == DRM_MODE_CONNECTOR_HDMIA) ? " (HDMI-A)" : (conn->connector_type == DRM_MODE_CONNECTOR_HDMIB) ? " (HDMI-B)" + : (conn->connector_type == DRM_MODE_CONNECTOR_DSI) ? " (DSI)" + : (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ? " (DisplayPort)" + : "", + conn->mmWidth, conn->mmHeight); + + if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) + { connector = conn; // 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_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)) { + } + 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 { + } + else + { width_mm = conn->mmWidth; height_mm = conn->mmHeight; } } - } else { + } + else + { drmModeFreeConnector(conn); } } - if (!connector) { + if (!connector) + { debug("could not find a connected connector!"); return false; } debug("Choosing DRM mode from %d available modes...\n", connector->count_modes); bool found_preferred = false; - for (int i = 0, area = 0; i < connector->count_modes; i++) { + for (int i = 0, area = 0; i < connector->count_modes; i++) + { drmModeModeInfo *current_mode = &connector->modes[i]; debug(" 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 - ); + current_mode->vrefresh, current_mode->type, current_mode->flags); - if (found_preferred) continue; + 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)) { + 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; @@ -791,25 +885,32 @@ static bool init_display(void) area = current_area; // if the preferred DRM mode is bogus, we're screwed. - if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) + { debug(" this mode is preferred by DRM. (DRM_MODE_TYPE_PREFERRED)"); found_preferred = true; } } } - if (!drm.mode) { + if (!drm.mode) + { debug("could not find a suitable DRM mode!"); return false; } // calculate the pixel ratio - if (pixel_ratio == 0.0) { - if ((width_mm == 0) || (height_mm == 0)) { + if (pixel_ratio == 0.0) + { + if ((width_mm == 0) || (height_mm == 0)) + { pixel_ratio = 1.0; - } else { + } + else + { pixel_ratio = (10.0 * width) / (width_mm * 38.0); - if (pixel_ratio < 1.0) pixel_ratio = 1.0; + if (pixel_ratio < 1.0) + pixel_ratio = 1.0; } } @@ -817,7 +918,8 @@ static bool init_display(void) refresh_rate, width_mm, height_mm, pixel_ratio); debug("Finding DRM encoder..."); - for (int i = 0; i < resources->count_encoders; i++) { + for (int i = 0; i < resources->count_encoders; i++) + { encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]); if (encoder->encoder_id == connector->encoder_id) break; @@ -825,15 +927,20 @@ static bool init_display(void) encoder = NULL; } - if (encoder) { + if (encoder) + { drm.crtc_id = encoder->crtc_id; - } else { + } + else + { debug("could not find a suitable crtc!"); return false; } - for (int i = 0; i < resources->count_crtcs; i++) { - if (resources->crtcs[i] == drm.crtc_id) { + for (int i = 0; i < resources->count_crtcs; i++) + { + if (resources->crtcs[i] == drm.crtc_id) + { drm.crtc_index = i; break; } @@ -854,8 +961,10 @@ static bool init_display(void) gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); - if (!gbm.surface) { - if (gbm.modifier != DRM_FORMAT_MOD_LINEAR) { + if (!gbm.surface) + { + if (gbm.modifier != DRM_FORMAT_MOD_LINEAR) + { debug("GBM Surface creation modifiers requested but not supported by GBM"); return false; } @@ -863,7 +972,8 @@ static bool init_display(void) GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); } - if (!gbm.surface) { + if (!gbm.surface) + { debug("failed to create GBM surface"); return false; } @@ -875,8 +985,7 @@ static bool init_display(void) static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; + EGL_NONE}; const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -886,27 +995,30 @@ static bool init_display(void) EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, - EGL_NONE - }; + EGL_NONE}; const char *egl_exts_client, *egl_exts_dpy, *gl_exts; debug("Querying EGL client extensions..."); egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - egl.eglGetPlatformDisplayEXT = (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); + egl.eglGetPlatformDisplayEXT = (void *)eglGetProcAddress("eglGetPlatformDisplayEXT"); debug("Getting EGL display for GBM device..."); - if (egl.eglGetPlatformDisplayEXT) egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.device, - NULL); - else egl.display = eglGetDisplay((void *) gbm.device); - - if (!egl.display) { + if (egl.eglGetPlatformDisplayEXT) + egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.device, + NULL); + else + egl.display = eglGetDisplay((void *)gbm.device); + + if (!egl.display) + { debug("Couldn't get EGL display"); return false; } debug("Initializing EGL..."); - if (!eglInitialize(egl.display, &major, &minor)) { + if (!eglInitialize(egl.display, &major, &minor)) + { debug("failed to initialize EGL"); return false; } @@ -925,44 +1037,53 @@ static bool init_display(void) debug("==================================="); debug("Binding OpenGL ES API..."); - if (!eglBindAPI(EGL_OPENGL_ES_API)) { + if (!eglBindAPI(EGL_OPENGL_ES_API)) + { debug("failed to bind OpenGL ES API"); return false; } - debug("Choosing EGL config..."); EGLint count = 0, matched = 0; EGLConfig *configs; bool _found_matching_config = false; - if (!eglGetConfigs(egl.display, NULL, 0, &count) || count < 1) { + if (!eglGetConfigs(egl.display, NULL, 0, &count) || count < 1) + { debug("No EGL configs to choose from."); return false; } configs = malloc(count * sizeof(EGLConfig)); - if (!configs) return false; + if (!configs) + return false; debug("Finding EGL configs with appropriate attributes..."); - if (!eglChooseConfig(egl.display, config_attribs, configs, count, &matched) || !matched) { + if (!eglChooseConfig(egl.display, config_attribs, configs, count, &matched) || !matched) + { debug("No EGL configs with appropriate attributes."); free(configs); return false; } debug("eglChooseConfig done"); - if (!gbm.format) { + if (!gbm.format) + { debug("!gbm.format"); _found_matching_config = true; - } else { + } + else + { debug("gbm.format"); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) + { EGLint id; debug("checking id=%d\n", id); - if (!eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) continue; + if (!eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) + continue; - if (id == gbm.format) { + if (id == gbm.format) + { debug("gbm.format=%d\n", id); egl.config = configs[i]; @@ -973,33 +1094,37 @@ static bool init_display(void) } free(configs); - if (!_found_matching_config) { + if (!_found_matching_config) + { debug("Could not find context with appropriate attributes and matching native visual ID."); return false; } debug("Creating EGL context..."); egl.context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); - if (egl.context == NULL) { + if (egl.context == NULL) + { debug("failed to create EGL context"); return false; } debug("Creating EGL window surface..."); - egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType) gbm.surface, NULL); - if (egl.surface == EGL_NO_SURFACE) { + egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType)gbm.surface, NULL); + if (egl.surface == EGL_NO_SURFACE) + { debug("failed to create EGL window surface"); return false; } - if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) { + if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) + { debug("Could not make EGL context current to get OpenGL information"); return false; } - egl.renderer = (char *) glGetString(GL_RENDERER); + egl.renderer = (char *)glGetString(GL_RENDERER); - gl_exts = (char *) glGetString(GL_EXTENSIONS); + gl_exts = (char *)glGetString(GL_EXTENSIONS); debug("==================================="); debug("OpenGL ES information:"); debug(" version: \"%s\"\n", glGetString(GL_VERSION)); @@ -1012,13 +1137,12 @@ static bool init_display(void) if (strncmp(egl.renderer, "llvmpipe", sizeof("llvmpipe") - 1) == 0) debug("Detected llvmpipe (ie. software rendering) as the OpenGL ES renderer. Make sure to run as root"); - drm.evctx = (drmEventContext) { + drm.evctx = (drmEventContext){ .version = 4, .vblank_handler = NULL, .page_flip_handler = pageflip_handler, .page_flip_handler2 = NULL, - .sequence_handler = NULL - }; + .sequence_handler = NULL}; debug("Swapping buffers..."); eglSwapBuffers(egl.display, egl.surface); @@ -1028,20 +1152,23 @@ static bool init_display(void) debug("getting new framebuffer for BO..."); struct drm_fb *fb = drm_fb_get_from_bo(drm.previous_bo); - if (!fb) { + if (!fb) + { debug("failed to get a new framebuffer BO"); return false; } debug("Setting CRTC..."); ok = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, &drm.connector_id, 1, drm.mode); - if (ok) { + if (ok) + { debug("failed to set mode: %s\n", strerror(errno)); return false; } debug("Clearing current context..."); - if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) + { debug("Could not clear EGL context"); return false; } @@ -1056,52 +1183,94 @@ static void destroy_display(void) debug("destroy_display not yet implemented"); } +static void on_platform_message( + const FlutterPlatformMessage *message, + void *userdata) +{ + debug("got platform message"); + plat_msg_push(&queue, message); + eventfd_write(fdset[EVENTFD_FD_POLL].fd, 1); + debug("platform message complete"); +} + +void platch_on_response_internal(const uint8_t *buffer, size_t size, void *userdata) +{ +} + +static void send_platform_message( + const char *channel, + const uint8_t *restrict message, + size_t message_size) +{ + FlutterEngineResult result; + FlutterPlatformMessageResponseHandle *response_handle = NULL; + result = FlutterPlatformMessageCreateResponseHandle(engine, platch_on_response_internal, NULL, &response_handle); + if (result != kSuccess) + { + error("FlutterPlatformMessageCreateResponseHandle"); + return; + } + result = FlutterEngineSendPlatformMessage(engine, + &(const FlutterPlatformMessage){ + .struct_size = sizeof(FlutterPlatformMessage), + .channel = channel, + .message = message, + .message_size = message_size, + .response_handle = response_handle}); + if (result != kSuccess) + error("FlutterEngineSendPlatformMessage"); + + FlutterPlatformMessageReleaseResponseHandle(engine, response_handle); + debug("send_platform_message complete"); +} + static bool init_application(void) { // 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.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 = NULL; // 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.struct_size = sizeof(FlutterProjectArgs); + flutter.args.assets_path = flutter.asset_bundle_path; + flutter.args.icu_data_path = flutter.icu_data_path; flutter.args.isolate_snapshot_data_size = 0; - flutter.args.isolate_snapshot_data = NULL; + flutter.args.isolate_snapshot_data = NULL; flutter.args.isolate_snapshot_instructions_size = 0; - flutter.args.isolate_snapshot_instructions = NULL; - flutter.args.vm_snapshot_data_size = 0; - flutter.args.vm_snapshot_data = NULL; + flutter.args.isolate_snapshot_instructions = NULL; + flutter.args.vm_snapshot_data_size = 0; + flutter.args.vm_snapshot_data = NULL; flutter.args.vm_snapshot_instructions_size = 0; - flutter.args.vm_snapshot_instructions = NULL; - flutter.args.command_line_argc = flutter.engine_argc; - flutter.args.command_line_argv = flutter.engine_argv; - flutter.args.platform_message_callback = NULL; // Not needed yet. + flutter.args.vm_snapshot_instructions = NULL; + flutter.args.command_line_argc = flutter.engine_argc; + flutter.args.command_line_argv = flutter.engine_argv; + flutter.args.platform_message_callback = on_platform_message; // Not needed yet. flutter.args.vsync_callback = vsync_callback; // See flutter-pi fix if display driver doesn't provide vblank timestamps - flutter.args.custom_task_runners = &(FlutterCustomTaskRunners) { + flutter.args.custom_task_runners = &(FlutterCustomTaskRunners){ .struct_size = sizeof(FlutterCustomTaskRunners), - .platform_task_runner = &(FlutterTaskRunnerDescription) { + .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 - } - }; + .post_task_callback = &flutter_post_platform_task}}; // spin up the engine FlutterEngineResult _result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &flutter.renderer_config, &flutter.args, NULL, &engine); - if (_result != kSuccess) { + if (_result != kSuccess) + { debug("Could not run the flutter engine"); return false; - } else { + } + else + { debug("flutter engine successfully started up."); } @@ -1110,12 +1279,11 @@ static bool init_application(void) // update window size int ok = FlutterEngineSendWindowMetricsEvent( engine, - &(FlutterWindowMetricsEvent) { - .struct_size = sizeof(FlutterWindowMetricsEvent), .width = width, .height = height, .pixel_ratio = pixel_ratio - } - ) == kSuccess; + &(FlutterWindowMetricsEvent){ + .struct_size = sizeof(FlutterWindowMetricsEvent), .width = width, .height = height, .pixel_ratio = pixel_ratio}) == kSuccess; - if (!ok) { + if (!ok) + { debug("Could not update Flutter application size."); return false; } @@ -1125,7 +1293,8 @@ static bool init_application(void) static void destroy_application(void) { - if (engine != NULL) { + if (engine != NULL) + { if (FlutterEngineShutdown(engine) != kSuccess) debug("Could not shutdown the flutter engine."); @@ -1140,24 +1309,23 @@ static bool init_io(void) int n_flutter_slots = 0; // add the mouse slot - mousepointer = (struct mousepointer_mtslot) { + mousepointer = (struct mousepointer_mtslot){ .id = 0, .flutter_slot_id = n_flutter_slots++, - .x = 0, .y = 0, - .phase = kCancel - }; + .x = 0, + .y = 0, + .phase = kCancel}; - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { + flutterevents[i_flutterevent++] = (FlutterPointerEvent){ .struct_size = sizeof(FlutterPointerEvent), .phase = kAdd, - .timestamp = (size_t) (FlutterEngineGetCurrentTime() * 1000), + .timestamp = (size_t)(FlutterEngineGetCurrentTime() * 1000), .x = 0, .y = 0, .signal_kind = kFlutterPointerSignalKindNone, .device_kind = kFlutterPointerDeviceKindTouch, .device = mousepointer.flutter_slot_id, - .buttons = 0 - }; + .buttons = 0}; return FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent) == kSuccess; } @@ -1168,71 +1336,88 @@ static void process_io_events(int fd) if (rd < 0) error("read failed"); if (rd % sizeof(struct input_event)) - error("read returned %d which is not a multiple of %d!", (int) rd, (int) sizeof(struct input_event)); + error("read returned %d which is not a multiple of %d!", (int)rd, (int)sizeof(struct input_event)); FlutterPointerEvent flutterevents[64] = {0}; size_t i_flutterevent = 0; size_t event_count = rd / sizeof(struct input_event); - for (size_t i = 0; i < event_count; i++) { - if (io_input_buffer[i].type == EV_ABS) { - if (io_input_buffer[i].code == ABS_X) { + for (size_t i = 0; i < event_count; i++) + { + if (io_input_buffer[i].type == EV_ABS) + { + if (io_input_buffer[i].code == ABS_X) + { mousepointer.x = io_input_buffer[i].value; - } else if (io_input_buffer[i].code == ABS_Y) { + } + else if (io_input_buffer[i].code == ABS_Y) + { mousepointer.y = io_input_buffer[i].value; - } else if (io_input_buffer[i].code == ABS_MT_TRACKING_ID && io_input_buffer[i].value == -1) { + } + else if (io_input_buffer[i].code == ABS_MT_TRACKING_ID && io_input_buffer[i].value == -1) + { } - if (mousepointer.phase == kDown) { - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { + if (mousepointer.phase == kDown) + { + flutterevents[i_flutterevent++] = (FlutterPointerEvent){ .struct_size = sizeof(FlutterPointerEvent), .phase = kMove, .timestamp = io_input_buffer[i].time.tv_sec * 1000000 + io_input_buffer[i].time.tv_usec, - .x = mousepointer.x, .y = mousepointer.y, + .x = mousepointer.x, + .y = mousepointer.y, .device = mousepointer.flutter_slot_id, .signal_kind = kFlutterPointerSignalKindNone, .device_kind = kFlutterPointerDeviceKindTouch, - .buttons = 0 - }; + .buttons = 0}; } - - } else if (io_input_buffer[i].type == EV_KEY) { - if (io_input_buffer[i].code == BTN_TOUCH) { + } + else if (io_input_buffer[i].type == EV_KEY) + { + if (io_input_buffer[i].code == BTN_TOUCH) + { mousepointer.phase = io_input_buffer[i].value ? kDown : kUp; - } else { + } + else + { debug("unknown EV_KEY code=%d value=%d\r\n", io_input_buffer[i].code, io_input_buffer[i].value); } - } else if (io_input_buffer[i].type == EV_SYN && io_input_buffer[i].code == SYN_REPORT) { + } + else if (io_input_buffer[i].type == EV_SYN && io_input_buffer[i].code == SYN_REPORT) + { // we don't want to send an event to flutter if nothing changed. - if (mousepointer.phase == kCancel) continue; + if (mousepointer.phase == kCancel) + continue; - flutterevents[i_flutterevent++] = (FlutterPointerEvent) { + flutterevents[i_flutterevent++] = (FlutterPointerEvent){ .struct_size = sizeof(FlutterPointerEvent), .phase = mousepointer.phase, .timestamp = io_input_buffer[i].time.tv_sec * 1000000 + io_input_buffer[i].time.tv_usec, - .x = mousepointer.x, .y = mousepointer.y, + .x = mousepointer.x, + .y = mousepointer.y, .device = mousepointer.flutter_slot_id, .signal_kind = kFlutterPointerSignalKindNone, .device_kind = kFlutterPointerDeviceKindTouch, - .buttons = 0 - }; + .buttons = 0}; if (mousepointer.phase == kUp) mousepointer.phase = kCancel; - } else { + } + else + { debug("unknown input_event type=%d\r\n", io_input_buffer[i].type); } } - if (i_flutterevent == 0) return; + if (i_flutterevent == 0) + return; // now, send the data to the flutter engine - if (FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent) != kSuccess) { + if (FlutterEngineSendPointerEvent(engine, flutterevents, i_flutterevent) != kSuccess) + { debug("could not send pointer events to flutter engine\r\n"); } } -// cmd("/root/flutter_embedder /srv/erlang/lib/nerves_example-0.1.0/priv/flutter_assets /srv/erlang/lib/flutter_embedder-0.1.0/priv/icudtl.dat") -// cmd("/srv/erlang/lib/flutter_embedder-0.1.0/priv/flutter_embedder /srv/erlang/lib/nerves_example-0.1.0/priv/flutter_assets /srv/erlang/lib/flutter_embedder-0.1.0/priv/icudtl.dat") static void *io_loop(void *userdata) { const char *input_path = "/dev/input/event0"; @@ -1240,8 +1425,10 @@ static void *io_loop(void *userdata) if (errno == EACCES && getuid() != 0) error("You do not have access to %s.", input_path); - while (engine_running) { + while (engine_running) + { // debug("io poll"); + // NOTE: this is not the same fdset as the globally named one struct pollfd fdset[2]; fdset[0].fd = fd; @@ -1268,13 +1455,15 @@ static void *io_loop(void *userdata) static bool run_io_thread(void) { int ok = pthread_create(&io_thread_id, NULL, &io_loop, NULL); - if (ok != 0) { + if (ok != 0) + { error("couldn't create io thread: [%s]", strerror(ok)); return false; } ok = pthread_setname_np(io_thread_id, "io"); - if (ok != 0) { + if (ok != 0) + { error("couldn't set name of io thread: [%s]", strerror(ok)); return false; } @@ -1282,6 +1471,139 @@ static bool run_io_thread(void) return true; } +static void handle_from_elixir(const uint8_t *buffer, size_t length, void *cookie) +{ + uint8_t type = buffer[sizeof(uint32_t)]; + // response + if (type == 0x1) + { + debug("found cookie=%d", buffer[sizeof(uint32_t) + 1]); + debug("message length=%zu", length - 4); + plat_msg_process(&queue, engine, buffer, length); + return; + } + if (type == 0x0) + { + uint8_t cd1 = buffer[sizeof(uint32_t) + 1 + 1]; + uint8_t cd2 = buffer[sizeof(uint32_t) + 1 + 1 + 1]; + uint16_t channel_size = ((uint16_t)cd2 << 8) | cd1; + debug("response channel_size=%d", channel_size); + + uint8_t md1 = buffer[sizeof(uint32_t) + 1 + 1 + sizeof(uint16_t) + channel_size]; + uint8_t md2 = buffer[sizeof(uint32_t) + 1 + 1 + sizeof(uint16_t) + channel_size + 1]; + uint16_t message_size = ((uint16_t)md2 << 8) | md1; + debug("response message_size=%d", message_size); + + const char *channel = malloc(channel_size + 1); + memset((void *)channel, 0x0, channel_size + 1); + memcpy(channel, buffer + sizeof(uint32_t) + 1 + 1 + sizeof(uint16_t), channel_size); + + const char *message = malloc(message_size); + memset((void *)message, 0x0, message_size); + memcpy(message, buffer + sizeof(uint32_t) + 1 + 1 + sizeof(uint16_t) + channel_size + sizeof(uint16_t), message_size); + + send_platform_message(channel, message, message_size); + } +} + +void *comms_thread(void *vargp) +{ + for (;;) + { + for (int i = 0; i < num_pollfds; i++) + fdset[i].revents = 0; + int rc = poll(fdset, num_pollfds, 0); + if (rc < 0) + { + // Retry if EINTR + if (errno == EINTR) + continue; + error("poll failed with %d", errno); + } + + // Erlang closed the port + if (fdset[ERLCMD_FD_POLL].revents & POLLHUP) + exit(2); + + // from elixir + if (fdset[ERLCMD_FD_POLL].revents & POLLIN) + erlcmd_process(&handler); + + // trigger from other thread about messages being ready for dispatch + if (fdset[EVENTFD_FD_POLL].revents & (POLLIN | POLLHUP)) + { + eventfd_t event; + eventfd_read(fdset[EVENTFD_FD_POLL].fd, &event); + size_t r; + r = plat_msg_dispatch_all(&queue, &handler); + if (r < 0) + error("Failed to dispatch platform messages: %zu", r); + } + + // Engine STDOUT + if (fdset[ENGINE_STDOUT_FD_POLL].revents & POLLIN) + { + memset(capstdoutbuffer, 0, ERLCMD_BUF_SIZE); + capstdoutbuffer[sizeof(uint32_t)] = 1; + size_t nbytes = read(fdset[ENGINE_STDOUT_FD_POLL].fd, capstdoutbuffer + sizeof(uint32_t) + sizeof(uint32_t), + ERLCMD_BUF_SIZE - sizeof(uint32_t) - sizeof(uint32_t)); + debug("stdout data from engine: %.*s", (int)nbytes, capstdoutbuffer + sizeof(uint32_t) + sizeof(uint32_t)); + if (nbytes < 0) + error("Failed to read engine log buffer"); + erlcmd_send(&handler, capstdoutbuffer, nbytes + sizeof(uint32_t) + sizeof(uint32_t)); + } + // Engine STDERR + if (fdset[ENGINE_STDERR_FD_POLL].revents & POLLIN) + { + memset(capstdoutbuffer, 0, ERLCMD_BUF_SIZE); + capstdoutbuffer[sizeof(uint32_t)] = 2; + size_t nbytes = read(fdset[ENGINE_STDERR_FD_POLL].fd, capstdoutbuffer + sizeof(uint32_t) + sizeof(uint32_t), + ERLCMD_BUF_SIZE - sizeof(uint32_t) - sizeof(uint32_t)); + debug("stderr data from engine: %.*s", (int)nbytes, capstdoutbuffer + sizeof(uint32_t) + sizeof(uint32_t)); + if (nbytes < 0) + error("Failed to read engine log buffer"); + erlcmd_send(&handler, capstdoutbuffer, nbytes + sizeof(uint32_t) + sizeof(uint32_t)); + } + } +} + +static bool init_comms(void) +{ + fdset[EVENTFD_FD_POLL].fd = eventfd(0, 0); + fdset[EVENTFD_FD_POLL].events = POLLIN; + fdset[EVENTFD_FD_POLL].revents = 0; + + // setup for capturing stdout from the engine + if (pipe2(capstdout, O_NONBLOCK) < 0) + { + error("pipe2"); + error("plat_msg_init"); + } + dup2(capstdout[1], STDOUT_FILENO); + + fdset[ENGINE_STDOUT_FD_POLL].fd = capstdout[0]; + fdset[ENGINE_STDOUT_FD_POLL].events = POLLIN; + fdset[ENGINE_STDOUT_FD_POLL].revents = 0; + + // setup for capturing stderr from the engine + if (pipe2(capstderr, O_NONBLOCK) < 0) + { + error("pipe2"); + error("plat_msg_init"); + } + dup2(capstderr[1], STDERR_FILENO); + fdset[ENGINE_STDERR_FD_POLL].fd = capstderr[0]; + fdset[ENGINE_STDERR_FD_POLL].events = POLLIN; + fdset[ENGINE_STDERR_FD_POLL].revents = 0; + return true; +} + +static bool run_comms_thread(void) +{ + pthread_create(&comms_thread_id, NULL, comms_thread, NULL); + return true; +} + int main(int argc, char **argv) { @@ -1291,7 +1613,8 @@ int main(int argc, char **argv) #endif #endif - if (argc < 3) { + if (argc < 3) + { error("flutter_embedder [other args]"); exit(EXIT_FAILURE); } @@ -1299,40 +1622,77 @@ int main(int argc, char **argv) snprintf(flutter.asset_bundle_path, sizeof(flutter.asset_bundle_path), "%s", argv[1]); snprintf(flutter.icu_data_path, sizeof(flutter.icu_data_path), "%s", argv[2]); - argv[2] = argv[0]; - flutter.engine_argc = argc - 2; - flutter.engine_argv = (const char *const *) & (argv[2]); + flutter.engine_argc = argc; + flutter.engine_argv = (const char *const *)argv; + for (int i = 0; i < flutter.engine_argc; i++) + { + debug("engine argv[%d]: %s", i, flutter.engine_argv[i]); + } + + int erlcmd_writefd = dup(STDOUT_FILENO); + int erlcmd_readfd = dup(STDIN_FILENO); + debug("using %d %d for erlcmd", erlcmd_writefd, erlcmd_readfd); + + erlcmd_init(&handler, erlcmd_readfd, erlcmd_writefd, handle_from_elixir, NULL); + if (plat_msg_queue_init(&queue) < 0) + { + error("plat_msg_init"); + exit(EXIT_FAILURE); + } + + // Initialize the file descriptor set for polling + memset(fdset, -1, sizeof(fdset)); + fdset[ERLCMD_FD_POLL].fd = erlcmd_readfd; + fdset[ERLCMD_FD_POLL].events = POLLIN; + fdset[ERLCMD_FD_POLL].revents = 0; + + debug("Initializing Comms with Elixir..."); + if (!init_comms()) + { + error("init_comms failed"); + return EXIT_FAILURE; + } // check if asset bundle path is valid - if (!init_paths()) { + if (!init_paths()) + { error("init_paths"); return EXIT_FAILURE; } - if (!init_message_loop()) { + if (!init_message_loop()) + { error("init_message_loop"); return EXIT_FAILURE; } // initialize display debug("initializing display..."); - if (!init_display()) { + if (!init_display()) + { + error("init_display failed"); return EXIT_FAILURE; } // initialize application debug("Initializing Application..."); - if (!init_application()) { - debug("init_application failed"); + if (!init_application()) + { + error("init_application failed"); return EXIT_FAILURE; } debug("Initializing Input devices..."); - if (!init_io()) { - debug("init_io failed"); + if (!init_io()) + { + error("init_io failed"); return EXIT_FAILURE; } + // read events from elixir + debug("Running Comms thread..."); + run_comms_thread(); + // read input events debug("Running IO thread..."); run_io_thread(); @@ -1342,6 +1702,8 @@ int main(int argc, char **argv) run_message_loop(); // exit + pthread_join(comms_thread_id, NULL); + plat_msg_queue_destroy(&queue); destroy_application(); destroy_display(); diff --git a/src/embedder_platform_message.c b/src/embedder_platform_message.c index a37676b..17416ed 100644 --- a/src/embedder_platform_message.c +++ b/src/embedder_platform_message.c @@ -1,9 +1,20 @@ #include #include +#include "util.h" #include "erlcmd.h" #include "embedder_platform_message.h" #include "flutter_embedder.h" +#ifdef DEBUG +#define LOG_PATH "/tmp/log.txt" +#define log_location stderr +#define debug(...) do { fprintf(log_location, __VA_ARGS__); fprintf(log_location, "\r\n"); fflush(log_location); } while(0) +#define error(...) do { debug(__VA_ARGS__); } while (0) +#else +#define debug(...) +#define error(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, ""); } while(0) +#endif + /** * Initialize queue and pthread lock * @param queue the queue @@ -68,16 +79,18 @@ void plat_msg_process(plat_msg_queue_t *queue, // having to track current, previous and head all at the same time // https://codereview.stackexchange.com/posts/539/revisions for (plat_msg_container_t **current = &queue->messages; *current; current = &(*current)->next) { - if ((*current)->cookie == buffer[sizeof(uint32_t)]) { + if ((*current)->cookie == buffer[sizeof(uint32_t)+1]) { + // I have no idea if this is correct. there are little docs for it. // What docs to exist say `FlutterEngineSendPlatformMessageResponse` must ALWAYS // be called, but i'm not really sure what to call it with if the // platform message has no response. // FlutterPi doesn't call anything if there's no listener. - FlutterEngineSendPlatformMessageResponse(engine, - (*current)->response_handle, - buffer + sizeof(uint32_t) +1, - length - sizeof(uint8_t)); + if(!(length <= 4)) + FlutterEngineSendPlatformMessageResponse(engine, + (*current)->response_handle, + buffer + sizeof(uint32_t) +1+1, + length - 2); plat_msg_container_t *next = (*current)->next; free((uint8_t *)(*current)->message); free((char *)(*current)->channel); @@ -88,7 +101,6 @@ void plat_msg_process(plat_msg_queue_t *queue, break; } } -cleanup: pthread_mutex_unlock(&queue->lock); return; } @@ -135,6 +147,7 @@ size_t plat_msg_dispatch(plat_msg_container_t *container, struct erlcmd *handler memset(buffer, 0, buffer_length); + // request type buffer[sizeof(uint32_t)] = 0x0; buffer[sizeof(uint32_t) +1] = container->cookie; memcpy(buffer + sizeof(uint32_t) +2, &container->channel_size, sizeof(uint16_t)); diff --git a/src/embedder_platform_message.h b/src/embedder_platform_message.h index 275525e..2ad4a43 100644 --- a/src/embedder_platform_message.h +++ b/src/embedder_platform_message.h @@ -4,6 +4,7 @@ #include #include #include +#include "erlcmd.h" #include "flutter_embedder.h" typedef struct platform_message diff --git a/src/erlcmd.h b/src/erlcmd.h index 560240f..42eb325 100644 --- a/src/erlcmd.h +++ b/src/erlcmd.h @@ -28,8 +28,9 @@ * The buffer needs to be large enough to hold the biggest individual * message from Erlang. */ -#define ERLCMD_BUF_SIZE (8192) -struct erlcmd { +#define ERLCMD_BUF_SIZE (16384) +struct erlcmd +{ uint8_t buffer[ERLCMD_BUF_SIZE]; size_t index; diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..408a76a --- /dev/null +++ b/src/util.c @@ -0,0 +1 @@ +#include "util.h" diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..8cca07a --- /dev/null +++ b/src/util.h @@ -0,0 +1,8 @@ +#ifndef FLUTTER_EMBEDDER_UTIL_H +#define FLUTTER_EMBEDDER_UTIL_H + +#include +#include +#include + +#endif \ No newline at end of file