diff options
-rw-r--r-- | harness/include/foreign_intf.h | 3 | ||||
-rw-r--r-- | harness/include/plugin.h | 23 | ||||
-rw-r--r-- | harness/src/main.c | 5 | ||||
-rw-r--r-- | harness/src/plugin.c | 123 | ||||
-rw-r--r-- | harness/src/wl.c | 25 | ||||
-rw-r--r-- | harness/tools/genbuild.pl | 7 | ||||
-rw-r--r-- | harness/tools/genintf.pl | 6 | ||||
-rw-r--r-- | package.yaml | 2 | ||||
-rw-r--r-- | src/Wetterhorn/Core.hs | 13 | ||||
-rw-r--r-- | src/Wetterhorn/Core/ForeignInterface.hs | 16 | ||||
-rw-r--r-- | src/Wetterhorn/FFI.hs | 18 | ||||
-rw-r--r-- | src/harness_adapter.c | 13 |
12 files changed, 190 insertions, 64 deletions
diff --git a/harness/include/foreign_intf.h b/harness/include/foreign_intf.h index 3a4b2a3..fc079e2 100644 --- a/harness/include/foreign_intf.h +++ b/harness/include/foreign_intf.h @@ -18,6 +18,9 @@ typedef struct FOREIGN_INTERFACE { /* Requests the harness hot reload the current plugin. */ EXPORT(void (*request_hot_reload)(ctx_t ctx)); + /* Requests the harness hot reload the current plugin. */ + EXPORT(void (*do_log)(ctx_t ctx, const char* str)); + } foreign_interface_t; #undef EXPORT diff --git a/harness/include/plugin.h b/harness/include/plugin.h index db56845..1e4a161 100644 --- a/harness/include/plugin.h +++ b/harness/include/plugin.h @@ -7,7 +7,7 @@ #include <stdint.h> #include <stdlib.h> -#include "foreign_intf.h" +#include <foreign_intf.h> /* * Marker macro to define what functions should be exported. This generates the @@ -15,6 +15,12 @@ */ #define EXPORT(a) a +#define EXPORT_INCLUDE(a) + +EXPORT_INCLUDE(<foreign_intf.h>) + +#define MAX_QUEUED_ACTIONS 8 + typedef void *dlhandle_t; /* Opaque state for a plugin. Not to be touched by the harness (not that it @@ -25,7 +31,9 @@ struct PLUGIN; /* This structure represents an action requested by the plugin for the harness. */ typedef struct { - int (*action)(struct PLUGIN *requester); + int (*action)(struct PLUGIN *requester, void* arg); + void (*arg_dtor)(void* arg); + void* arg; } requested_action_t; /* @@ -62,6 +70,9 @@ typedef struct PLUGIN { * outcomes. */ pthread_mutex_t lock; + /** Set to not-zero if this plugin is initialized, otherwise set to zero. */ + int initialized; + /* The handle to the shared library. */ dlhandle_t library_handle; @@ -73,7 +84,7 @@ typedef struct PLUGIN { /** Intializes the plugin with the given argc/argv. This is the first thing * called on the plugin and is called immediately after the library is loaded. */ - EXPORT(void (*plugin_load)(int argc, char **argv, foreign_interface_t *intf)); + EXPORT(void (*plugin_load)(int argc, char** argv, foreign_interface_t *intf)); /* Start the plugin with the marshalled state from the previous plugin. * @@ -122,10 +133,11 @@ typedef struct PLUGIN { * allowed at one time. That should be plenty. The actions should be flushed * after each call to a handler anyway. */ size_t n_requested_actions; - requested_action_t requested_actions[8]; + requested_action_t requested_actions[MAX_QUEUED_ACTIONS]; } plugin_t; #undef EXPORT +#undef EXPORT_INCLUDE /* Reloads the plugin. This tears down the existing plugin, marshals the state * for it and reloads it. @@ -141,6 +153,9 @@ int plugin_hot_reload(int argc, char **argv, const char *filepath, */ int plugin_hot_reload_same_state(plugin_t *plugin); +/* Starts a plugin in a cold state. Called after load_plugin_from_file. */ +void plugin_cold_start(plugin_t* plugin); + /* Reads a plugin from a filename. */ int load_plugin_from_file(int argc, char **argv, const char *filename, plugin_t *plugin); diff --git a/harness/src/main.c b/harness/src/main.c index 995d11e..05b06ff 100644 --- a/harness/src/main.c +++ b/harness/src/main.c @@ -65,12 +65,11 @@ int main_(int argc, char **argv) { } plugin_t plugin; - if (load_plugin_from_file(argv[1], &plugin)) { + if (load_plugin_from_file(argc, argv, argv[1], &plugin)) { fprintf(stderr, "Exiting due to other failures.\n"); return 1; } - plugin.plugin_load(argc, argv); - plugin.state = plugin.plugin_cold_start(); + plugin_cold_start(&plugin); run_plugin(argc, argv, &plugin); diff --git a/harness/src/plugin.c b/harness/src/plugin.c index 808088f..f1cc361 100644 --- a/harness/src/plugin.c +++ b/harness/src/plugin.c @@ -3,6 +3,7 @@ #include <ctype.h> #include <dlfcn.h> +#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -45,31 +46,68 @@ static void shx(uint8_t *state, uint32_t sz) int load_plugin_from_dl_(dlhandle_t dl, plugin_t *plug); +static void lock(plugin_t *plugin) +{ + pthread_mutex_lock(&plugin->lock); +}; + +static void unlock(plugin_t *plugin) +{ + pthread_mutex_unlock(&plugin->lock); +}; + +static int plugin_hot_reload_same_state_action_(plugin_t *plugin, void* ignore) +{ + return plugin_hot_reload_same_state(plugin); +} + void do_request_hot_reload(void *plugv) { plugin_t *plugin = plugv; - if (plugin->n_requested_actions < 8) { - plugin->requested_actions[plugin->n_requested_actions++].action = - plugin_hot_reload_same_state; + size_t n = plugin->n_requested_actions++; + if (n < 8) { + plugin->requested_actions[n].action = plugin_hot_reload_same_state_action_; + plugin->requested_actions[n].arg_dtor = NULL; } } -int load_plugin_from_file(int argc, char **argv, const char *filename, - plugin_t *plugin) +static int plugin_do_log(plugin_t* plugin, void* chrs) +{ + char* str = chrs; + puts(str); + return 0; +} + +void do_request_log(void *plugv, const char* str) +{ + plugin_t *plugin = plugv; + + size_t n = plugin->n_requested_actions++; + if (n < 8) { + plugin->requested_actions[n].action = plugin_do_log; + plugin->requested_actions[n].arg = strdup(str); + plugin->requested_actions[n].arg_dtor = free; + } +} + +static int load_plugin_from_file_(int argc, char **argv, const char *filename, + plugin_t *plugin) { dlhandle_t lib = dlopen(filename, RTLD_LAZY); + int ec = 0; if (!lib) { fprintf(stderr, "Failed to open library: %s: %s\n", filename, dlerror()); - return 1; + ec = 1; + goto end; } printf("Loading file.\n"); - int ec = load_plugin_from_dl_(lib, plugin); + ec = load_plugin_from_dl_(lib, plugin); if (ec) { - return ec; + goto end; } strncpy(plugin->filename, filename, sizeof(plugin->filename)); @@ -78,13 +116,43 @@ int load_plugin_from_file(int argc, char **argv, const char *filename, plugin->foreign_intf.ctx = plugin; plugin->foreign_intf.request_hot_reload = do_request_hot_reload; - return 0; + plugin->foreign_intf.do_log = do_request_log; + + plugin->plugin_load(plugin->argc, plugin->argv, &plugin->foreign_intf); +end: + return ec; +} + +int load_plugin_from_file(int argc, char **argv, const char *filename, + plugin_t *plugin) +{ + memset(plugin, 0, sizeof(*plugin)); + + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) { + perror("pthread_mutexattr_init"); + return 1; + } + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) { + perror("pthread_mutexattr_settype"); + return 1; + } + + if (pthread_mutex_init(&plugin->lock, &attr)) { + pthread_mutexattr_destroy(&attr); + perror("pthread_mutexattr_init"); + return 1; + } + pthread_mutexattr_destroy(&attr); + return load_plugin_from_file_(argc, argv, filename, plugin); } int plugin_hot_reload_same_state(plugin_t *plugin) { - return plugin_hot_reload(plugin->argc, plugin->argv, plugin->filename, - plugin); + char filename_cpy[PATH_MAX]; + strncpy(filename_cpy, plugin->filename, sizeof(filename_cpy)); + return plugin_hot_reload(plugin->argc, plugin->argv, filename_cpy, plugin); } int plugin_hot_reload(int argc, char **argv, const char *filepath, @@ -95,7 +163,7 @@ int plugin_hot_reload(int argc, char **argv, const char *filepath, uint8_t *marshalled_state = NULL; printf("Hot Reloading %s\n", plugin->plugin_name); - pthread_mutex_lock(&plugin->lock); + lock(plugin); printf("Marshalling state ...\n"); marshalled_state = plugin->plugin_marshal_state(plugin->state, &sz); @@ -107,9 +175,11 @@ int plugin_hot_reload(int argc, char **argv, const char *filepath, shx(marshalled_state, sz); printf("Unloading old library handle.\n"); - dlclose(plugin->library_handle); + if (dlclose(plugin->library_handle)) { + printf("Could not close library handle: %s\n", dlerror()); + } - if ((ec = load_plugin_from_file(argc, argv, filepath, plugin))) { + if ((ec = load_plugin_from_file_(argc, argv, filepath, plugin))) { goto fail; } @@ -120,15 +190,32 @@ int plugin_hot_reload(int argc, char **argv, const char *filepath, fail: free(marshalled_state); - pthread_mutex_unlock(&plugin->lock); + unlock(plugin); return ec; } void plugin_run_requested_actions(plugin_t *plugin) { + lock(plugin); + requested_action_t requested_actions[MAX_QUEUED_ACTIONS]; + size_t n_requested_actions = plugin->n_requested_actions; + memcpy(&requested_actions, plugin->requested_actions, + sizeof(requested_actions)); + plugin->n_requested_actions = 0; + unlock(plugin); + size_t i; - for (i = 0; i < plugin->n_requested_actions; ++i) { - plugin->requested_actions[i].action(plugin); + for (i = 0; i < n_requested_actions; ++i) { + requested_actions[i].action(plugin, requested_actions[i].arg); + if (requested_actions[i].arg_dtor) { + requested_actions[i].arg_dtor(requested_actions[i].arg); + } } - plugin->n_requested_actions = 0; +} + +void plugin_cold_start(plugin_t *plugin) +{ + lock(plugin); + plugin->state = plugin->plugin_cold_start(); + unlock(plugin); } diff --git a/harness/src/wl.c b/harness/src/wl.c index 53932e7..474123a 100644 --- a/harness/src/wl.c +++ b/harness/src/wl.c @@ -22,6 +22,7 @@ #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/log.h> +#include <wlr/xwayland.h> #include <xkbcommon/xkbcommon.h> // This macro is responsible for calling a handler on a plugin. This macro will @@ -81,9 +82,6 @@ struct tinywl_server { struct wl_list outputs; struct wl_listener new_output; - int plugin_argc; - char **plugin_argv; - const char *plugin_fpath; plugin_t plugin; }; @@ -189,8 +187,7 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) wl_display_terminate(server->wl_display); break; case XKB_KEY_F5: - plugin_hot_reload(server->plugin_argc, server->plugin_argv, - server->plugin_fpath, &server->plugin); + plugin_hot_reload_same_state(&server->plugin); break; case XKB_KEY_F1: /* Cycle to the next view */ @@ -256,8 +253,12 @@ static void server_new_keyboard(struct tinywl_server *server, /* We need to prepare an XKB keymap and assign it to the keyboard. This * assumes the defaults (e.g. layout = "us"). */ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_rule_names names = (struct xkb_rule_names) { 0 }; + names.layout = "jr"; + names.variant = "jdvprk"; + names.options = "numpad:mac"; struct xkb_keymap *keymap = - xkb_keymap_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); + xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(device->keyboard, keymap); xkb_keymap_unref(keymap); @@ -908,17 +909,13 @@ int main(int argc, char *argv[]) struct tinywl_server server; - if (load_plugin_from_file(plugin, &server.plugin)) { + if (load_plugin_from_file(argc, argv, plugin, &server.plugin)) { fprintf(stderr, "Failed to read plugin from file.\n"); return 1; } // server.plugin.plugin_metaload(argc, argv); - server.plugin.plugin_load(argc, argv); - server.plugin.state = server.plugin.plugin_cold_start(); - server.plugin_fpath = plugin; - server.plugin_argv = argv; - server.plugin_argc = argc; + plugin_cold_start(&server.plugin); /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ @@ -941,7 +938,8 @@ int main(int argc, char *argv[]) * to dig your fingers in and play with their behavior if you want. Note that * the clients cannot set the selection directly without compositor approval, * see the handling of the request_set_selection event below.*/ - wlr_compositor_create(server.wl_display, server.renderer); + struct wlr_compositor* compositor; + compositor = wlr_compositor_create(server.wl_display, server.renderer); wlr_data_device_manager_create(server.wl_display); /* Creates an output layout, which a wlroots utility for working with an @@ -1048,6 +1046,7 @@ int main(int argc, char *argv[]) * loop configuration to listen to libinput events, DRM events, generate * frame events at the refresh rate, and so on. */ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); + wlr_xwayland_create(server.wl_display, compositor, false); wl_display_run(server.wl_display); /* Once wl_display_run returns, we shut down the server. */ diff --git a/harness/tools/genbuild.pl b/harness/tools/genbuild.pl index 1b1a005..7020e81 100644 --- a/harness/tools/genbuild.pl +++ b/harness/tools/genbuild.pl @@ -5,6 +5,7 @@ $comment=""; print "#include <stdio.h>\n"; print "#include <dlfcn.h>\n"; print "#include <pthread.h>\n"; +print "#include <string.h>\n"; print "#include \"plugin.h\"\n\n"; print "int load_plugin_from_dl_(dlhandle_t dl, plugin_t* plug)\n"; @@ -34,11 +35,5 @@ while (<>) { $comment=""; } } -print "\n if (!ret) {\n"; -print " if(pthread_mutex_init(&plug->lock, NULL)) {\n"; -print " fprintf(stderr, \"Failed to initalize plugin lock\");\n"; -print " ret |= 1;\n"; -print " }\n"; -print " }\n"; print "\n return ret;\n"; print "}\n"; diff --git a/harness/tools/genintf.pl b/harness/tools/genintf.pl index 6e9d477..6f53951 100644 --- a/harness/tools/genintf.pl +++ b/harness/tools/genintf.pl @@ -17,10 +17,14 @@ while (<>) { $comment="$comment$_"; } - if (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) { + if (/^\s*EXPORT_INCLUDE\((.*)\)/) { + print "#include $1\n"; + } elsif (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) { print "$comment"; print "$1 $2($3);\n\n"; $comment=""; + } elsif (/^\s*EXPORT\((.*)\)/) { + print "$1\n"; } } print "#endif /* _PLUG_INTF */\n"; diff --git a/package.yaml b/package.yaml index 7ecbbc1..1232b90 100644 --- a/package.yaml +++ b/package.yaml @@ -44,6 +44,7 @@ ghc-options: - -Wpartial-fields - -Wredundant-constraints - -XTupleSections +- -XViewPatterns - -fPIC executables: @@ -60,6 +61,7 @@ executables: - -g3 - -shared - -Iharness/build/ + - -Iharness/include/ tests: wetterhorn-test: diff --git a/src/Wetterhorn/Core.hs b/src/Wetterhorn/Core.hs index d3b3f56..8d4e6b7 100644 --- a/src/Wetterhorn/Core.hs +++ b/src/Wetterhorn/Core.hs @@ -15,14 +15,16 @@ module Wetterhorn.Core readWState, defaultConfig, requestHotReload, + ctxConfig, ) where import Control.Arrow (first) import Control.Exception +import Control.Monad (when) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as CH -import Foreign (Ptr, StablePtr, Word32, newStablePtr, ptrToIntPtr) +import Foreign (Ptr, StablePtr, Word32, newStablePtr, ptrToIntPtr, castForeignPtr) import Numeric (showHex) import Text.Printf import Wetterhorn.Core.ForeignInterface (ForeignInterface) @@ -41,6 +43,11 @@ requestHotReload = do fi <- ctxForeignInterface <$> getWContext wio $ ForeignInterface.requestHotReload fi +requestLog :: String -> W () +requestLog str = do + fi <- ctxForeignInterface <$> getWContext + wio $ ForeignInterface.requestLog fi str + initWetterhorn :: WConfig -> IO Wetterhorn initWetterhorn conf = do foreignInterface <- ForeignInterface.getForeignInterface @@ -64,7 +71,9 @@ defaultConfig = WConfig { keybindingHandler = \sym -> do i <- incrementState - wio (printf "[%d] Got key %d\n" i sym), + wio (printf "[%d] Got key: %d\n" i sym) + when (sym == 111) requestHotReload + when (sym == 112) (requestLog "Hey daddy ths is a log statement.\n"), surfaceHandler = \state ptr -> wio (printf "Surface %s is %s\n" (showHex (ptrToIntPtr ptr) "") (show state)) } diff --git a/src/Wetterhorn/Core/ForeignInterface.hs b/src/Wetterhorn/Core/ForeignInterface.hs index acd98d6..de4779b 100644 --- a/src/Wetterhorn/Core/ForeignInterface.hs +++ b/src/Wetterhorn/Core/ForeignInterface.hs @@ -6,18 +6,24 @@ where import Data.Void (Void) import Foreign (Ptr, Storable (peekByteOff, sizeOf)) +import Foreign.C.String import GHC.Exts (FunPtr) type CtxT = Ptr Void type ForeignCall = CtxT -> IO () +type ForeignCallStr = CtxT -> CString -> IO () + foreign import ccall "get_foreign_interface" foreignInterfacePtr :: IO (Ptr ()) foreign import ccall "dynamic" toForeignCall :: FunPtr ForeignCall -> ForeignCall +foreign import ccall "dynamic" toForeignCallStr :: FunPtr ForeignCallStr -> ForeignCallStr + data ForeignInterface = ForeignInterface - { requestHotReload :: IO () + { requestHotReload :: IO (), + requestLog :: String -> IO () } getForeignInterface :: IO ForeignInterface @@ -26,11 +32,11 @@ getForeignInterface = do ctx <- peekByteOff ptr 0 requestHotReloadFn <- peekByteOff ptr (sizeOf ctx) - - let _ = (ctx :: CtxT) - let _ = (requestHotReloadFn :: FunPtr ForeignCall) + doLogFn <- peekByteOff ptr (sizeOf ctx + sizeOf requestHotReloadFn) return $ ForeignInterface - { requestHotReload = toForeignCall requestHotReloadFn ctx + { requestHotReload = toForeignCall requestHotReloadFn ctx, + requestLog = \str -> + withCString str $ \cs -> toForeignCallStr doLogFn ctx cs } diff --git a/src/Wetterhorn/FFI.hs b/src/Wetterhorn/FFI.hs index 1d74248..3221903 100644 --- a/src/Wetterhorn/FFI.hs +++ b/src/Wetterhorn/FFI.hs @@ -21,27 +21,27 @@ import Wetterhorn.Core runForeign :: (WConfig -> W ()) -> Wetterhorn -> IO Wetterhorn runForeign fn stblptr = do - (conf, st) <- deRefStablePtr stblptr + (ctx, st) <- deRefStablePtr stblptr freeStablePtr stblptr - (_, state') <- runW (fn conf) (conf, st) - newStablePtr (conf, state') + (_, state') <- runW (fn $ ctxConfig ctx) (ctx, st) + newStablePtr (ctx, state') runForeignWithReturn :: (Storable a) => (WConfig -> W a) -> Ptr a -> Wetterhorn -> IO Wetterhorn runForeignWithReturn fn ptr stableptr = do - (conf, st) <- deRefStablePtr stableptr + (ctx, st) <- deRefStablePtr stableptr freeStablePtr stableptr - (val, state') <- runW (fn conf) (conf, st) + (val, state') <- runW (fn $ ctxConfig ctx) (ctx, st) poke ptr val - newStablePtr (conf, state') + newStablePtr (ctx, state') runForeignWithReturn2 :: (Storable a, Storable b) => (WConfig -> W (a, b)) -> Ptr a -> Ptr b -> Wetterhorn -> IO Wetterhorn runForeignWithReturn2 fn ptrA ptrB stableptr = do - (conf, st) <- deRefStablePtr stableptr + (ctx, st) <- deRefStablePtr stableptr freeStablePtr stableptr - ((vA, vB), state') <- runW (fn conf) (conf, st) + ((vA, vB), state') <- runW (fn $ ctxConfig ctx) (ctx, st) poke ptrA vA poke ptrB vB - newStablePtr (conf, state') + newStablePtr (ctx, state') -- | This function is the implementation of the "hotstart" mechanism. It gives a -- pointer to the previously marshalled state and the length of that array and diff --git a/src/harness_adapter.c b/src/harness_adapter.c index aa45ce6..8585d7e 100644 --- a/src/harness_adapter.c +++ b/src/harness_adapter.c @@ -5,7 +5,7 @@ #include "HsFFI.h" #include "plugin_interface.h" -#include <stdio.h> +#include <unistd.h> const char *plugin_name = "Wetterhorn"; @@ -16,11 +16,18 @@ void* get_foreign_interface() return foreign_interface; } -void plugin_load(int argc, char **argv, void* fintf) { +void plugin_load(int argc, char **argv, foreign_interface_t* fintf) { hs_init(&argc, &argv); foreign_interface = fintf; } -void plugin_teardown(opqst_t st) { hs_exit(); } +void plugin_teardown(opqst_t st) { + hs_exit(); + + // There's a race condition between when this shared library is unloaded and + // when the haskell runtime actually exits. Don't have a good solution for + // this at the moment, so just sleep for a second. + sleep(1); +} static const char msg[] = "Wetterhorn Plugin v 0.01\n\n" |