├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── c ├── .gitignore ├── Cargo.toml ├── bindgen_backup.rs ├── build.rs ├── src │ ├── application.h │ ├── application │ │ ├── cef.cpp │ │ ├── cef.h │ │ ├── cef_window.cpp │ │ ├── cef_window.h │ │ ├── common.c │ │ ├── edge2.c │ │ ├── edge2.h │ │ ├── gtk.c │ │ ├── gtk.h │ │ ├── impl.h │ │ ├── other.c │ │ ├── win32.c │ │ └── win32.h │ ├── assert.h │ ├── bindings.rs │ ├── bool.h │ ├── browser_window.h │ ├── browser_window │ │ ├── cef.cpp │ │ ├── cef.h │ │ ├── common.c │ │ ├── edge2.c │ │ ├── edge2.cpp.old │ │ ├── edge2.h │ │ └── impl.h │ ├── cef │ │ ├── app_handler.hpp │ │ ├── bw_handle_map.cpp │ │ ├── bw_handle_map.hpp │ │ ├── client_handler.cpp │ │ ├── client_handler.hpp │ │ ├── exception.cpp │ │ ├── exception.hpp │ │ ├── external_invocation_handler.hpp │ │ ├── seperate_executable.cpp │ │ ├── util.cpp │ │ ├── util.hpp │ │ └── v8_to_string.hpp │ ├── common.h │ ├── cookie.h │ ├── cookie │ │ ├── cef.cpp │ │ ├── cef.h │ │ └── unsupported.c │ ├── debug.h │ ├── err.c │ ├── err.h │ ├── event.c │ ├── event.h │ ├── gl_surface.h │ ├── gl_surface │ │ ├── common.c │ │ ├── glx.c │ │ └── impl.h │ ├── lib.rs │ ├── posix.h │ ├── string.c │ ├── string.h │ ├── win32.c │ ├── win32.h │ ├── window.h │ └── window │ │ ├── cef.cpp │ │ ├── cef.h │ │ ├── common.c │ │ ├── gtk.c │ │ ├── gtk.h │ │ ├── impl.h │ │ ├── win32.c │ │ └── win32.h └── update_bindgen_backup.sh ├── docs ├── GETTING-STARTED.md └── ISSUE-DIAGNOSIS.md ├── examples ├── authentication.rs ├── northern-lights.txt ├── preview-art.sh ├── resources │ ├── OFL.txt │ ├── main.js │ ├── style.css │ └── terminal.html └── terminal.rs ├── get-cef.ps1 ├── get-cef.sh ├── preview.png ├── rustfmt.toml ├── setup-cef-files.bat ├── setup-cef-files.sh └── src ├── application.rs ├── application └── settings.rs ├── browser.rs ├── browser └── builder.rs ├── common.rs ├── cookie.rs ├── core.rs ├── core ├── application.rs ├── application │ ├── c.rs │ └── gtk.rs ├── browser_window.rs ├── browser_window │ ├── c.rs │ ├── edge2.rs │ └── webkit.rs ├── cookie.rs ├── cookie │ ├── c.rs │ └── unsupported.rs ├── error.rs ├── error │ ├── c.rs │ └── common.rs ├── prelude.rs ├── window.rs └── window │ ├── c.rs │ └── gtk.rs ├── delegate.rs ├── error.rs ├── event.rs ├── javascript.rs ├── lib.rs ├── prelude.rs ├── rc.rs ├── tests.rs ├── window.rs └── window └── builder.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.woff binary 4 | *.png binary 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: bamidev 2 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master", "dev" ] 6 | pull_request: 7 | branches: [ "master", "dev" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - name: Install WebkitGTK development files 22 | run: sudo apt-get install -y libwebkit2gtk-4.1-dev mingw-w64 xvfb 23 | - uses: actions/checkout@v3 24 | - name: Set up CEF 25 | run: ${GITHUB_WORKSPACE}/get-cef.sh 26 | - name: Build with CEF 27 | run: CEF_PATH="${GITHUB_WORKSPACE}/cef/$(ls cef)" cargo build --release --verbose --features cef 28 | - name: Build with CEF (threadsafe) 29 | run: CEF_PATH="${GITHUB_WORKSPACE}/cef/$(ls cef)" cargo build --release --verbose --features cef,threadsafe 30 | - name: Run tests with CEF 31 | run: | 32 | CEF_PATH="${GITHUB_WORKSPACE}/cef/$(ls cef)" ${GITHUB_WORKSPACE}/setup-cef-files.sh 33 | CEF_PATH="${GITHUB_WORKSPACE}/cef/$(ls cef)" RUST_BACKTRACE=full RUST_LOG=debug xvfb-run cargo test --verbose --features cef,threadsafe --lib --bins --tests 34 | - name: Build with WebkitGTK 35 | run: cargo build --release --verbose --features webkitgtk 36 | - name: Build with WebkitGTK (threadsafe) 37 | run: cargo build --release --verbose --features webkitgtk,threadsafe 38 | - name: Run tests with WebkitGTK 39 | run: RUST_BACKTRACE=full RUST_LOG=debug xvfb-run cargo test --verbose --features webkitgtk,threadsafe 40 | - name: Add x86_64-pc-windows-gnu target for Rust 41 | run: rustup target add x86_64-pc-windows-gnu 42 | - name: Build with Edge WebView2 (MinGW) 43 | run: cargo build --release --verbose --features edge2 --target=x86_64-pc-windows-gnu 44 | 45 | build-windows: 46 | runs-on: windows-latest 47 | steps: 48 | - uses: actions-rs/toolchain@v1 49 | with: 50 | toolchain: stable 51 | - uses: actions/checkout@v3 52 | #- name: Set up CEF 53 | # shell: pwsh 54 | # run: .\get-cef.ps1 55 | - uses: actions-rs/cargo@v1 56 | with: 57 | command: build 58 | args: --release --features edge2 59 | - uses: actions-rs/cargo@v1 60 | with: 61 | command: build 62 | args: --release --features edge2,threadsafe 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | 4 | # CEF generated files 5 | GPUCache 6 | 7 | # Visual Studio project files 8 | .vs 9 | 10 | # Visual Studio Code project files 11 | .vscode 12 | 13 | # CEF's bin and resource files 14 | chrome-sandbox 15 | *.bin 16 | *.dat 17 | *.pak 18 | *.so 19 | 20 | # The CEF dir when using the get-cef-* scripts 21 | cef 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "browser-window" 3 | version = "0.12.3" 4 | description = "A simple, optionally async, optionally threadsafe, electron-like browser toolkit for Rust." 5 | edition = "2018" 6 | authors = ["Bamidev"] 7 | license = "MIT" 8 | publish = true 9 | keywords = ["browser", "chromium", "electron", "gui", "webview"] 10 | categories = ["asynchronous", "gui", "web-programming"] 11 | readme = "README.md" 12 | documentation = "https://docs.rs/browser-window/" 13 | repository = "https://github.com/bamidev/browser-window/" 14 | 15 | [features] 16 | cef = ["browser-window-c/cef"] 17 | edge2 = ["dep:webview2", "dep:winapi", "browser-window-c/edge2"] 18 | gtk = [] 19 | webkitgtk = ["dep:glib", "dep:gtk", "dep:javascriptcore-rs", "dep:webkit2gtk", "gtk"] 20 | threadsafe = [] 21 | 22 | [lib] 23 | name = "browser_window" 24 | path = "src/lib.rs" 25 | 26 | [[example]] 27 | name = "terminal" 28 | path = "examples/terminal.rs" 29 | 30 | [[example]] 31 | name = "authentication" 32 | path = "examples/authentication.rs" 33 | 34 | [dependencies] 35 | browser-window-c = { path = "c", version = "=0.3.2" } 36 | futures-channel = { version = "^0.3" } 37 | glib = { version = "0.18", optional = true } 38 | gtk = { version = "0.18", optional = true } 39 | javascriptcore-rs = { version = "1", optional = true } 40 | json = "0.12" 41 | lazy_static = "1" 42 | num-bigfloat = "1" 43 | unsafe-send-sync = "^0.1" 44 | webkit2gtk = { version = "2.0", optional = true, features = ["v2_40"] } 45 | webview2 = { version = "0.1", optional = true } 46 | winapi = { version = "0.3", optional = true } 47 | 48 | 49 | [dev-dependencies] 50 | serde_json = "^1.0" 51 | tokio = { version = "^1.0", features = ["rt", "rt-multi-thread"] } 52 | 53 | [package.metadata.docs.rs] 54 | features = ["threadsafe"] 55 | 56 | [workspace] 57 | members = ["c"] 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2021-2024 Bamidev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BrowserWindow 2 | 3 | _BrowserWindow_ is a simple Rust crate for utilizing a browser engine to create a graphical user interface. 4 | Just like [Electron.js](https://www.electronjs.org/), you can use it to build a GUI with HTML, CSS & JS, or just to have some basic browser functionality at your disposal. 5 | 6 | ![](preview.png) 7 | 8 | ## Introduction 9 | 10 | _BrowserWindow_ is designed to be easy to use, and work cross-platform. It utilizes the async/await 11 | syntax & it even has optional thread-safe handles. There are currently three different underlying 12 | browser frameworks that can be selected: CEF, WebkitGTK or Edge WebView2. 13 | Each framework has their pros and cons, but CEF will be the most feature complete because it has the 14 | best cross-platform support and the browser engine is regularely updated. 15 | However, if you only need some very basic functionality, the other frameworks are easier to set up. 16 | 17 | You can look at some [examples](https://github.com/bamilab/browser-window/tree/master/examples) to 18 | get an idea how you can use the api. 19 | 20 | ## Getting Started 21 | 22 | The underlying browser framework does need some things to be set up on your system (although Edge 23 | WebView2 may out-of-the-box on newer Windows installations). 24 | But otherwise there is a [guide](./docs/GETTING-STARTED.md) to get you started with using 25 | _BrowserWindow_ on your system. 26 | 27 | ## License 28 | 29 | This software is available as open source software under a MIT license, for maximum freedom and 30 | minimum restrictions. 31 | 32 | ## Missing a feature? 33 | 34 | At the moment, there is a decent set of basic functionality available, but if you need something 35 | that isn't there yet, please [submit an issue](https://github.com/bamidev/browser-window/issues) for it. -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | // For the MSVC compiler, it seems that sometimes linking errors occur, as a 5 | // result of compiling browser-window-c for a different architecture then the 6 | // main package. Adding browser-window-c.lib to the linker manually, at least 7 | // causes a meaningful error to be shown. 8 | let target = env::var("TARGET").unwrap(); 9 | if target.ends_with("msvc") { 10 | println!("cargo:rustc-link-lib=static=browser-window-c"); 11 | } 12 | 13 | // Make sure one of the browser frameworks is actually selected. 14 | if env::var("DOCS_RS").is_err() 15 | && !cfg!(feature = "cef") 16 | && !cfg!(feature = "webkitgtk") 17 | && !cfg!(feature = "edge2") 18 | { 19 | panic!( 20 | "No browser framework has been specified. Enable either feature `webkitgtk`, `cef` or \ 21 | `edge2`." 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /c/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | seperate_executable.obj -------------------------------------------------------------------------------- /c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "browser-window-c" 3 | version = "0.3.2" 4 | description = "Browser framework functionality for BrowserWindow written in C/C++." 5 | edition = "2018" 6 | authors = ["Bamidev"] 7 | license = "MIT" 8 | publish = true 9 | categories = [] 10 | repository = "https://github.com/bamilab/browser-window/tree/master/c" 11 | 12 | [features] 13 | cef = [] 14 | edge2 = [] 15 | 16 | [lib] 17 | name = "browser_window_c" 18 | path = "src/lib.rs" 19 | crate-type = ["rlib"] 20 | 21 | [build-dependencies] 22 | bindgen = "^0.69" 23 | cc = "^1.0" 24 | pkg-config = "^0.3" 25 | -------------------------------------------------------------------------------- /c/src/application.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_H 2 | #define BW_APPLICATION_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "bool.h" 9 | #include "string.h" 10 | 11 | 12 | 13 | typedef struct bw_Application bw_Application; 14 | typedef void (*bw_ApplicationDispatchFn)( struct bw_Application* app, void* data ); 15 | typedef bw_ApplicationDispatchFn bw_ApplicationReadyFn; 16 | 17 | 18 | // Import bw_ApplicationImpl and bw_ApplicationEngineImpl definitions 19 | #ifndef BW_BINDGEN 20 | 21 | #if defined(BW_WIN32) 22 | #include "application/win32.h" 23 | #elif defined(BW_GTK) 24 | #include "application/gtk.h" 25 | #elif defined(BW_CEF_WINDOW) 26 | #include "application/cef_window.h" 27 | #else 28 | #define BW_OTHER_IMPL 29 | #endif 30 | 31 | #if defined(BW_CEF) 32 | #include "application/cef.h" 33 | #elif defined(BW_EDGE2) 34 | #include "application/edge2.h" 35 | #else 36 | #define BW_OTHER_ENGINE_IMPL 37 | #endif 38 | 39 | #endif 40 | 41 | #if defined(BW_OTHER_IMPL) || defined(BW_BINDGEN) 42 | typedef struct {} bw_ApplicationImpl; 43 | #endif 44 | #if defined(BW_OTHER_ENGINE_IMPL) || defined(BW_BINDGEN) 45 | typedef struct {} bw_ApplicationEngineImpl; 46 | #endif 47 | 48 | 49 | #include "err.h" 50 | 51 | #include 52 | 53 | 54 | 55 | struct bw_Application { 56 | unsigned int windows_alive; 57 | BOOL is_running; 58 | BOOL is_done; 59 | bw_ApplicationImpl impl; 60 | bw_ApplicationEngineImpl engine_impl; /// Can be set by the implementation of a browser engine 61 | }; 62 | 63 | typedef struct bw_Application bw_Application; 64 | typedef struct bw_ApplicationEngineData bw_ApplicationEngineData; 65 | 66 | typedef struct { 67 | bw_ApplicationDispatchFn func; 68 | void* data; 69 | } bw_ApplicationDispatchData; 70 | 71 | typedef struct { 72 | bw_CStrSlice engine_seperate_executable_path; 73 | uint16_t remote_debugging_port; 74 | bw_CStrSlice resource_dir; 75 | } bw_ApplicationSettings; 76 | 77 | 78 | 79 | /// Safety check that makes sure the given application handle is used on the correct thread. 80 | /// Does nothing in release mode. 81 | void bw_Application_assertCorrectThread( const bw_Application* ); 82 | 83 | /// Exits the main loop, returning execution to the function that invoked the run call. 84 | /// The exit_code will be returned by bw_Application_run. 85 | void bw_Application_exit( bw_Application* app, int exit_code ); 86 | 87 | /// Same as bw_Application_exit, but guaranteed to be thread-safe 88 | /// The exit_code will be returned by bw_Application_run. 89 | void bw_Application_exitAsync( bw_Application* app, int exit_code ); 90 | 91 | /// Dispatches the given function to be executed on the thread this application instance has been created on, 92 | /// and passes the given data to it. 93 | /// This function is thread safe. 94 | /// 95 | /// # Returns 96 | /// An indication of whether or not the function was able to be dispatched. 97 | /// Dispatching a function fails when the application has already been terminated. 98 | BOOL bw_Application_dispatch( bw_Application* app, bw_ApplicationDispatchFn func, void* data ); 99 | 100 | /// Shuts down the applcation runtime & frees it's memory. 101 | void bw_Application_free( bw_Application* app ); 102 | 103 | /// Initializes browser window. 104 | /// Starts up browser engine process(es). 105 | /// Returns an application handle. 106 | bw_Err bw_Application_initialize( bw_Application** application, int argc, char** argv, const bw_ApplicationSettings* settings ); 107 | 108 | BOOL bw_Application_isRunning( const bw_Application* app ); 109 | 110 | void bw_Application_markAsDone(bw_Application* app); 111 | 112 | /// Runs the event loop. 113 | /// Calls the `on_ready` callback when `app` can be used. 114 | int bw_Application_run( bw_Application* app, bw_ApplicationReadyFn on_ready, void* user_data ); 115 | 116 | /// Executes the given closure after the specified delay. 117 | BOOL bw_Application_dispatchDelayed(bw_Application* app, bw_ApplicationDispatchFn func, void* user_data, uint64_t milliseconds); 118 | 119 | 120 | 121 | #ifdef __cplusplus 122 | } // extern "C" 123 | #endif 124 | 125 | #endif//BW_APPLICATION_H 126 | -------------------------------------------------------------------------------- /c/src/application/cef.cpp: -------------------------------------------------------------------------------- 1 | #include "../application.h" 2 | #include "../debug.h" 3 | #include "../cef/app_handler.hpp" 4 | #include "../cef/client_handler.hpp" 5 | 6 | #include "impl.h" 7 | 8 | #include 9 | #include 10 | #ifdef BW_MACOS 11 | #include 12 | #endif 13 | #include 14 | 15 | // X11 headers, when used by CEF 16 | #if defined(CEF_X11) 17 | #include 18 | #endif 19 | 20 | // Link with win32 libraries 21 | #if defined(BW_WIN32) 22 | #pragma comment(lib, "shell32.lib") 23 | #endif 24 | 25 | // Causes the current process to exit with the given exit code. 26 | void _bw_Application_exitProcess( int exit_code ); 27 | CefString to_string( bw_CStrSlice ); 28 | 29 | #ifdef CEF_X11 30 | int _bw_ApplicationCef_xErrorHandler( Display* display, XErrorEvent* event ); 31 | int _bw_ApplicationCef_xIoErrorHandler( Display* display ); 32 | #endif 33 | 34 | 35 | 36 | bw_Err bw_ApplicationEngineImpl_initialize( bw_ApplicationEngineImpl* impl, bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 37 | 38 | // If working with X, set error handlers that spit out errors instead of shutting down the application 39 | #if defined(CEF_X11) 40 | XSetErrorHandler( _bw_ApplicationCef_xErrorHandler ); 41 | XSetIOErrorHandler( _bw_ApplicationCef_xIoErrorHandler ); 42 | #endif 43 | 44 | // Load CEF libraries at runtime, as required by the MacOS sandbox 45 | #ifdef BW_MACOS 46 | CefScopedLibraryLoader library_loader; 47 | if (!library_loader.LoadInMain()) 48 | return bw_Err_new_with_msg(1, "unable to load CEF libraries"); 49 | #endif 50 | 51 | // For some reason the Windows implementation for CEF doesn't have the constructor for argc and argv. 52 | #ifdef BW_WINDOWS 53 | CefMainArgs main_args( GetModuleHandle(NULL) ); 54 | #else 55 | CefMainArgs main_args( argc, argv ); 56 | #endif 57 | 58 | CefSettings app_settings; 59 | if (settings->remote_debugging_port != 0) { 60 | app_settings.remote_debugging_port = settings->remote_debugging_port; 61 | } 62 | CefRefPtr cef_app_handle( new AppHandler( app ) ); 63 | 64 | if (settings->engine_seperate_executable_path.len == 0) { 65 | int exit_code = CefExecuteProcess( main_args, cef_app_handle.get(), 0 ); 66 | // If the current process returns a non-negative number, something went wrong... 67 | if ( exit_code >= 0 ) { 68 | exit(exit_code); 69 | } 70 | } 71 | else { 72 | char* path = bw_string_copyAsNewCstr(settings->engine_seperate_executable_path); 73 | CefString( &app_settings.browser_subprocess_path ) = path; 74 | bw_string_freeCstr(path); 75 | } 76 | 77 | // Only works on Windows and Linux according to docs. 78 | // Here it says it works on Windows only: https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-linux 79 | // TODO: Check if the GTK implementation works when it is set to false, and with CefMessageDoWork() called repeatedly from somewhere else. 80 | // TODO: Check if it works on BSD by any chance. 81 | // TODO: For unsupported systems (like macOS), CefDoMessageLoopWork needs to be called repeatedly. 82 | // This is usually less effecient than using the multithreaded message loop though. 83 | // TODO: If GTK will be used on macOS in the future, the 'if' macro below needs to be corrected. 84 | #if defined(BW_WIN32) 85 | app_settings.multi_threaded_message_loop = true; 86 | #endif 87 | if ( settings->resource_dir.data != 0 ) { 88 | char* path = bw_string_copyAsNewCstr( settings->resource_dir ); 89 | CefString( &app_settings.resources_dir_path ) = path; 90 | bw_string_freeCstr(path); 91 | } 92 | 93 | CefInitialize( main_args, app_settings, cef_app_handle.get(), 0 ); 94 | 95 | CefRefPtr* client = new CefRefPtr(new ClientHandler( app )); 96 | 97 | impl->exit_code = 0; 98 | impl->cef_client = (void*)client; 99 | 100 | BW_ERR_RETURN_SUCCESS; 101 | } 102 | 103 | void bw_ApplicationEngineImpl_free( bw_ApplicationEngineImpl* app ) { 104 | CefShutdown(); 105 | delete (CefRefPtr*)app->cef_client; 106 | } 107 | 108 | 109 | 110 | #ifdef CEF_X11 111 | int _bw_ApplicationCef_xErrorHandler( Display* display, XErrorEvent* event ) { 112 | 113 | fprintf( stderr, "X Error: type %d, serial %lu, error code %d, request code %d, mino r code %d\n", event->type, event->serial, event->error_code, event->request_code, event->minor_code ); 114 | return 0; 115 | } 116 | 117 | int _bw_ApplicationCef_xIoErrorHandler( Display* display ) { 118 | return 0; 119 | } 120 | #endif -------------------------------------------------------------------------------- /c/src/application/cef.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_CEF_H 2 | #define BW_APPLICATION_CEF_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | 10 | typedef struct { 11 | void* cef_client; 12 | int exit_code; 13 | } bw_ApplicationEngineImpl; 14 | 15 | 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif//BW_APPLICATION_CEF_H 22 | -------------------------------------------------------------------------------- /c/src/application/cef_window.cpp: -------------------------------------------------------------------------------- 1 | #include "../application.h" 2 | #include "../common.h" 3 | #include "impl.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | void bw_ApplicationImpl_dispatchHandler( bw_Application* app, bw_ApplicationDispatchData* data ); 14 | 15 | 16 | 17 | void bw_Application_assertCorrectThread( const bw_Application* app ) { 18 | BW_ASSERT( CefCurrentlyOn( TID_UI ), "Not called from the GUI thread!" ); 19 | } 20 | 21 | void bw_Application_exit( bw_Application* app, int exit_code ) { 22 | bw_Application_assertCorrectThread( app ); 23 | 24 | app->impl.exit_code = exit_code; 25 | 26 | CefQuitMessageLoop(); 27 | } 28 | 29 | void bw_Application_exitAsync( bw_Application* app, int exit_code ) { 30 | CefPostTask( TID_UI, base::BindOnce( &bw_Application_exit, app, exit_code )); 31 | } 32 | 33 | BOOL bw_ApplicationImpl_dispatchDelayed(bw_Application* app, bw_ApplicationDispatchData* data, uint64_t milliseconds) { 34 | BW_ASSERT(milliseconds < 0x8000000000000000, "CEF doesn't support delays of 0x8000000000000000 or longer"); // The milliseconds in CefPostDelayedTask is signed. 35 | 36 | CefPostDelayedTask(TID_UI, base::BindOnce(&bw_ApplicationImpl_dispatchHandler, app, data), milliseconds); 37 | return TRUE; 38 | } 39 | 40 | void bw_ApplicationImpl_free( bw_ApplicationImpl* app ) { 41 | 42 | } 43 | 44 | void bw_ApplicationImpl_dispatchHandler( bw_Application* app, bw_ApplicationDispatchData* data ) { 45 | data->func( app, data->data ); 46 | } 47 | 48 | 49 | 50 | BOOL bw_ApplicationImpl_dispatch( bw_Application* app, bw_ApplicationDispatchData* data ) { 51 | 52 | CefPostTask( TID_UI, base::BindOnce( &bw_ApplicationImpl_dispatchHandler, app, data ) ); 53 | return TRUE; 54 | } 55 | 56 | int bw_ApplicationImpl_run( bw_Application* app, bw_ApplicationImpl_ReadyHandlerData* ready_handler_data ) { 57 | bw_Application_assertCorrectThread( app ); 58 | 59 | app->impl.exit_code = 0; 60 | 61 | CefPostTask(TID_UI, base::BindOnce(ready_handler_data->func, app, ready_handler_data->data )); 62 | CefRunMessageLoop(); 63 | 64 | return app->impl.exit_code; 65 | } 66 | 67 | // Doesn't need to be implemented because it is already done so in bw_ApplicationEngineImpl_initialize 68 | bw_ApplicationImpl bw_ApplicationImpl_initialize( bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 69 | UNUSED( app ); 70 | UNUSED( argc ); 71 | UNUSED( argv ); 72 | UNUSED( settings ); 73 | 74 | bw_ApplicationImpl impl = { 0 }; 75 | return impl; 76 | } -------------------------------------------------------------------------------- /c/src/application/cef_window.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_CEF_WINDOW_H 2 | #define BW_APPLICATION_CEF_WINDOW_H 3 | 4 | #if !defined(BW_CEF) 5 | #error "BW_CEF needs to be defined in order to use BW_CEF_WINDOW!" 6 | #endif 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #include "../bool.h" 13 | 14 | 15 | 16 | typedef struct { 17 | int exit_code; 18 | } bw_ApplicationImpl; 19 | 20 | 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif//BW_APPLICATION_CEF_WINDOW_H 27 | -------------------------------------------------------------------------------- /c/src/application/common.c: -------------------------------------------------------------------------------- 1 | #include "../application.h" 2 | #include "../common.h" 3 | 4 | #include "impl.h" 5 | 6 | #include 7 | 8 | 9 | BOOL bw_Application_isRunning( const bw_Application* app ) { 10 | return app->is_running; 11 | } 12 | 13 | void bw_Application_markAsDone(bw_Application* app) { 14 | app->is_done = TRUE; 15 | if (app->windows_alive == 0) 16 | bw_Application_exit(app, 0); 17 | } 18 | 19 | void bw_Application_runOnReady(bw_Application* app, void* user_data) { 20 | bw_ApplicationImpl_ReadyHandlerData* ready_handler_data = (bw_ApplicationImpl_ReadyHandlerData*)user_data; 21 | 22 | ready_handler_data->func(app, ready_handler_data->data); 23 | } 24 | 25 | int bw_Application_run( bw_Application* app, bw_ApplicationReadyFn on_ready, void* user_data ) { 26 | bw_Application_assertCorrectThread( app ); 27 | app->is_running = TRUE; 28 | app->is_done = FALSE; 29 | 30 | bw_ApplicationImpl_ReadyHandlerData ready_handler_data = { 31 | app, 32 | on_ready, 33 | user_data 34 | }; 35 | 36 | bw_ApplicationImpl_ReadyHandlerData handler_data_wrapper = { 37 | app, 38 | bw_Application_runOnReady, 39 | (void*)&ready_handler_data 40 | }; 41 | 42 | int exit_code = bw_ApplicationImpl_run( app, &handler_data_wrapper ); 43 | app->is_running = FALSE; 44 | return exit_code; 45 | } 46 | 47 | void bw_Application_free( bw_Application* app ) { 48 | 49 | bw_ApplicationEngineImpl_free( &app->engine_impl ); 50 | bw_ApplicationImpl_free( &app->impl ); 51 | free(app); 52 | } 53 | 54 | bw_Err bw_Application_initialize( bw_Application** app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 55 | 56 | *app = (bw_Application*)malloc( sizeof( bw_Application ) ); 57 | (*app)->windows_alive = 0; 58 | (*app)->is_running = FALSE; 59 | (*app)->is_done = FALSE; 60 | 61 | bw_Err error = bw_ApplicationEngineImpl_initialize( &(*app)->engine_impl, (*app), argc, argv, settings ); 62 | if (BW_ERR_IS_FAIL(error)) return error; 63 | (*app)->impl = bw_ApplicationImpl_initialize( (*app), argc, argv, settings ); 64 | 65 | BW_ERR_RETURN_SUCCESS; 66 | } 67 | 68 | BOOL bw_Application_dispatch( bw_Application* app, bw_ApplicationDispatchFn func, void* data ) { 69 | 70 | bw_ApplicationDispatchData* dispatch_data = (bw_ApplicationDispatchData*)malloc( sizeof(bw_ApplicationDispatchData) ); 71 | dispatch_data->func = func; 72 | dispatch_data->data = data; 73 | 74 | return bw_ApplicationImpl_dispatch( app, dispatch_data ); 75 | } 76 | 77 | BOOL bw_Application_dispatchDelayed( bw_Application* app, bw_ApplicationDispatchFn func, void* data, uint64_t milliseconds ) { 78 | 79 | bw_ApplicationDispatchData* dispatch_data = (bw_ApplicationDispatchData*)malloc( sizeof(bw_ApplicationDispatchData) ); 80 | dispatch_data->func = func; 81 | dispatch_data->data = data; 82 | 83 | return bw_ApplicationImpl_dispatchDelayed( app, dispatch_data, milliseconds ); 84 | } 85 | -------------------------------------------------------------------------------- /c/src/application/edge2.c: -------------------------------------------------------------------------------- 1 | #include "edge2.h" 2 | #include "impl.h" 3 | #include "../common.h" 4 | 5 | #include 6 | 7 | 8 | void bw_ApplicationEngineImpl_free(bw_ApplicationEngineImpl* app) { 9 | UNUSED(app); 10 | } 11 | 12 | bw_Err bw_ApplicationEngineImpl_initialize( bw_ApplicationEngineImpl* impl, bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 13 | CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 14 | BW_ERR_RETURN_SUCCESS; 15 | } -------------------------------------------------------------------------------- /c/src/application/edge2.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_EDGE2_H 2 | #define BW_APPLICATION_EDGE2_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | typedef struct { 10 | void* _; 11 | } bw_ApplicationEngineImpl; 12 | 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | 18 | #endif//BW_APPLICATION_EDGE2_H 19 | -------------------------------------------------------------------------------- /c/src/application/gtk.c: -------------------------------------------------------------------------------- 1 | #include "../application.h" 2 | 3 | #include "impl.h" 4 | #include "../common.h" 5 | 6 | #include 7 | 8 | 9 | 10 | typedef struct { 11 | bw_Application* app; 12 | int exit_code; 13 | } bw_ApplicationGtkAsyncExitData; 14 | 15 | typedef struct { 16 | bw_Application* app; 17 | bw_ApplicationDispatchData* inner; 18 | } bw_ApplicationImplDispatchData; 19 | 20 | 21 | 22 | gboolean _bw_ApplicationImpl_dispatchHandler( gpointer _dispatch_data ); 23 | gboolean _bw_ApplicationImpl_exitHandler( gpointer data ); 24 | 25 | 26 | 27 | void bw_Application_assertCorrectThread( const bw_Application* app ) { 28 | #ifndef NDEBUG 29 | BW_ASSERT( app->impl.thread_id == pthread_self(), "Browser Window C function called from non-GUI thread!" ) 30 | #else 31 | UNUSED(app); 32 | #endif 33 | } 34 | 35 | void bw_Application_exit( bw_Application* app, int exit_code ) { 36 | app->impl.exit_code = exit_code; 37 | 38 | // Set `is_running` flag to false 39 | pthread_mutex_lock( &app->impl.is_running_mtx ); 40 | app->impl.is_running = false; 41 | pthread_mutex_unlock( &app->impl.is_running_mtx ); 42 | 43 | // Then quit the loop 44 | g_application_quit( G_APPLICATION( app->impl.handle ) ); 45 | } 46 | 47 | void bw_Application_exitAsync( bw_Application* app, int exit_code ) { 48 | bw_ApplicationGtkAsyncExitData data; 49 | data.app = app; 50 | data.exit_code = exit_code; 51 | 52 | gdk_threads_add_idle( _bw_ApplicationImpl_exitHandler, (gpointer)&data ); 53 | } 54 | 55 | void bw_ApplicationGtk_onActivate( GtkApplication* gtk_handle, gpointer data ) { 56 | UNUSED( gtk_handle ); 57 | 58 | bw_ApplicationImpl_ReadyHandlerData* ready_handler_data = (bw_ApplicationImpl_ReadyHandlerData*)data; 59 | bw_ApplicationImpl* app = &ready_handler_data->app->impl; 60 | 61 | // Mark the application as 'running' 62 | pthread_mutex_lock( &app->is_running_mtx ); 63 | app->is_running = true; 64 | pthread_mutex_unlock( &app->is_running_mtx ); 65 | 66 | (ready_handler_data->func)( ready_handler_data->app, ready_handler_data->data ); 67 | } 68 | 69 | int bw_ApplicationImpl_run( bw_Application* app, bw_ApplicationImpl_ReadyHandlerData* ready_handler_data ) { 70 | 71 | g_signal_connect( app->impl.handle, "activate", G_CALLBACK( bw_ApplicationGtk_onActivate ), (void*)ready_handler_data ); 72 | 73 | g_application_run( G_APPLICATION(app->impl.handle), app->impl.argc, app->impl.argv ); 74 | 75 | return app->impl.exit_code; 76 | } 77 | 78 | BOOL bw_ApplicationImpl_dispatch( bw_Application* app, bw_ApplicationDispatchData* _data ) { 79 | BOOL is_running = true; 80 | 81 | pthread_mutex_lock( &app->impl.is_running_mtx ); 82 | 83 | bw_ApplicationImplDispatchData* data = (bw_ApplicationImplDispatchData*)malloc( sizeof( bw_ApplicationImplDispatchData ) ); 84 | data->app = app; 85 | data->inner = _data; 86 | 87 | if ( app->impl.is_running ) 88 | gdk_threads_add_idle( _bw_ApplicationImpl_dispatchHandler, (gpointer)data ); 89 | else 90 | is_running = false; 91 | 92 | pthread_mutex_unlock( &app->impl.is_running_mtx ); 93 | 94 | return is_running; 95 | } 96 | 97 | void bw_ApplicationImpl_dispatchHandler( bw_Application* app, bw_ApplicationDispatchData* data ) { 98 | data->func( app, data->data ); 99 | } 100 | 101 | bw_ApplicationImpl bw_ApplicationImpl_initialize( bw_Application* _app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 102 | UNUSED( _app ); 103 | UNUSED( settings ); 104 | 105 | bw_ApplicationImpl app; 106 | 107 | app.handle = gtk_application_new("bamilab.BrowserWindow", G_APPLICATION_FLAGS_NONE); 108 | app.argc = argc; 109 | app.argv = argv; 110 | app.is_running = false; 111 | app.thread_id = pthread_self(); 112 | 113 | // Initialize mutex 114 | int result = pthread_mutex_init( &app.is_running_mtx, NULL ); 115 | BW_POSIX_ASSERT_SUCCESS( result ); 116 | 117 | return app; 118 | } 119 | 120 | // There is no 'free' function for GtkApplication* 121 | void bw_ApplicationImpl_finish( bw_ApplicationImpl* app ) { 122 | 123 | pthread_mutex_destroy( &app->is_running_mtx ); 124 | g_object_unref( app->handle ); 125 | } 126 | 127 | 128 | 129 | gboolean _bw_ApplicationImpl_dispatchHandler( gpointer _dispatch_data ) { 130 | bw_ApplicationImplDispatchData* dispatch_data = (bw_ApplicationImplDispatchData*)(_dispatch_data); 131 | 132 | dispatch_data->inner->func( dispatch_data->app, dispatch_data->inner->data ); 133 | 134 | free( dispatch_data->inner ); 135 | free( dispatch_data ); 136 | 137 | return FALSE; 138 | } 139 | 140 | gboolean _bw_ApplicationImpl_exitHandler( gpointer _data ) { 141 | bw_ApplicationGtkAsyncExitData* data = (bw_ApplicationGtkAsyncExitData*)_data; 142 | 143 | bw_Application_exit( data->app, data->exit_code ); 144 | 145 | return FALSE; 146 | } 147 | -------------------------------------------------------------------------------- /c/src/application/gtk.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_GTK_H 2 | #define BW_APPLICATION_GTK_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | 10 | typedef struct { 11 | GtkApplication* handle; 12 | int argc; 13 | char** argv; 14 | int exit_code; 15 | bool is_running; 16 | pthread_mutex_t is_running_mtx; 17 | pthread_t thread_id; 18 | } bw_ApplicationImpl; 19 | 20 | 21 | 22 | #endif//BW_APPLICATION_GTK_H -------------------------------------------------------------------------------- /c/src/application/impl.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_COMMON_H 2 | #define BW_APPLICATION_COMMON_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "../application.h" 9 | 10 | #include 11 | 12 | typedef struct { 13 | bw_Application* app; 14 | bw_ApplicationReadyFn func; 15 | void* data; 16 | } bw_ApplicationImpl_ReadyHandlerData; 17 | 18 | 19 | 20 | BOOL bw_ApplicationImpl_dispatch( bw_Application* app, bw_ApplicationDispatchData* data ); 21 | BOOL bw_ApplicationImpl_dispatchDelayed( bw_Application* app, bw_ApplicationDispatchData* data, uint64_t milliseconds ); 22 | void bw_ApplicationImpl_free( bw_ApplicationImpl* ); 23 | int bw_ApplicationImpl_run( bw_Application* app, bw_ApplicationImpl_ReadyHandlerData* ready_handler_data ); 24 | bw_ApplicationImpl bw_ApplicationImpl_initialize( bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ); 25 | 26 | void bw_ApplicationEngineImpl_free( bw_ApplicationEngineImpl* ); 27 | bw_Err bw_ApplicationEngineImpl_initialize( bw_ApplicationEngineImpl* impl, bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ); 28 | 29 | 30 | 31 | #ifdef __cplusplus 32 | } // extern "C" 33 | #endif 34 | 35 | #endif//BW_APPLICATION_COMMON_H 36 | -------------------------------------------------------------------------------- /c/src/application/other.c: -------------------------------------------------------------------------------- 1 | 2 | #include "impl.h" 3 | 4 | 5 | void bw_ApplicationEngineImpl_free( bw_ApplicationEngineImpl* ) {} 6 | 7 | bw_Err bw_ApplicationEngineImpl_initialize( bw_ApplicationEngineImpl* impl, bw_Application* app, int argc, char** argv, const bw_ApplicationSettings* settings ) { 8 | BW_ERR_RETURN_SUCCESS; 9 | } -------------------------------------------------------------------------------- /c/src/application/win32.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_APPLICATION_WIN32_H 2 | #define BW_APPLICATION_WIN32_H 3 | 4 | #if defined(BW_CEF) 5 | #include "cef.h" 6 | #endif 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #include 13 | 14 | 15 | 16 | typedef struct { 17 | DWORD thread_id; 18 | HINSTANCE handle; 19 | WNDCLASSEXW wc; 20 | SRWLOCK is_running_mtx; 21 | } bw_ApplicationImpl; 22 | 23 | typedef struct { 24 | void* dispatch_data; 25 | UINT delay; 26 | struct bw_Application* app; 27 | } bw_ApplicationDispatchDelayedData; 28 | 29 | 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif//BW_APPLICATION_WIN32_H 36 | -------------------------------------------------------------------------------- /c/src/assert.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_ASSERT_H 2 | #define BW_ASSERT_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #ifdef __cplusplus 12 | } //extern "C" 13 | #endif 14 | 15 | 16 | 17 | // Asserts if CONDITION evaluates to false. 18 | // MESSAGE will be printed in standard error output. 19 | // The same arguments provided to fprintf can be provided in this macro, like this: 20 | // BW_ASSERT( false, "Unable to find number %i", my_number ) 21 | #define BW_ASSERT( CONDITION, ... ) \ 22 | if ( !(CONDITION) ) { \ 23 | fprintf( stderr, "[ASSERTION %s:%i] ", __FILE__, __LINE__ ); \ 24 | fprintf( stderr, __VA_ARGS__ ); \ 25 | fprintf( stderr, "\n" ); \ 26 | assert( CONDITION ); \ 27 | } 28 | 29 | #define BW_PANIC( ... ) \ 30 | BW_ASSERT( 0, __VA_ARGS__ ) 31 | 32 | 33 | 34 | #endif//BW_ASSERT_H 35 | -------------------------------------------------------------------------------- /c/src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(improper_ctypes)] 3 | #![allow(non_upper_case_globals)] 4 | #![allow(non_camel_case_types)] 5 | #![allow(non_snake_case)] 6 | 7 | include!(concat!(env!("OUT_DIR"), "/c_bindings.rs")); 8 | -------------------------------------------------------------------------------- /c/src/bool.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_BOOL_H 2 | #define BW_BOOL_H 3 | 4 | 5 | 6 | typedef int BOOL; 7 | #define TRUE 1 8 | #define FALSE 0 9 | 10 | 11 | 12 | #endif//BW_BOOL_H -------------------------------------------------------------------------------- /c/src/browser_window.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_BROWSER_WINDOW_H 2 | #define BW_BROWSER_WINDOW_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #ifdef BW_CEF 9 | #include "browser_window/cef.h" 10 | #elif defined(BW_EDGE2) 11 | #include "browser_window/edge2.h" 12 | #else 13 | typedef struct {} bw_BrowserWindowImpl; 14 | #endif 15 | 16 | #include "application.h" 17 | #include "event.h" 18 | #include "err.h" 19 | #include "string.h" 20 | #include "window.h" 21 | 22 | #include 23 | 24 | 25 | 26 | typedef struct bw_BrowserWindow bw_BrowserWindow; 27 | 28 | 29 | typedef void (*bw_BrowserWindowCreationCallbackFn)( bw_BrowserWindow* window, void* data ); 30 | typedef void (*bw_BrowserWindowHandlerFn)( bw_BrowserWindow* window, bw_CStrSlice cmd, bw_CStrSlice* args, size_t arg_count ); 31 | typedef void (*bw_BrowserWindowJsCallbackFn)( bw_BrowserWindow* window, void* user_data, const char* result, const bw_Err* err ); 32 | 33 | 34 | typedef struct { 35 | bw_Event on_address_changed; 36 | bw_Event on_console_message; 37 | bw_Event on_favicon_changed; 38 | bw_Event on_fullscreen_mode_changed; 39 | bw_Event on_loading_progress_changed; 40 | bw_Event on_message; 41 | bw_Event on_navigation_start; 42 | bw_Event on_navigation_end; 43 | bw_Event on_page_title_changed; 44 | bw_Event on_status_message; 45 | bw_Event on_tooltip; 46 | } bw_BrowserWindowEvents; 47 | 48 | /// `cmd` is always a string. The arguments are JS values in the form of a string. 49 | typedef struct { 50 | bw_CStrSlice cmd; 51 | size_t arg_count; 52 | bw_CStrSlice* args; 53 | } bw_BrowserWindowMessageArgs; 54 | 55 | typedef struct bw_BrowserWindowOptions { 56 | BOOL dev_tools; 57 | bw_CStrSlice resource_path; 58 | } bw_BrowserWindowOptions; 59 | 60 | typedef struct bw_BrowserWindowSource { 61 | bw_CStrSlice data; 62 | BOOL is_html; 63 | } bw_BrowserWindowSource; 64 | 65 | 66 | 67 | struct bw_BrowserWindow { 68 | bw_Window* window; 69 | bw_BrowserWindowImpl impl; 70 | bw_BrowserWindowEvents events; 71 | }; 72 | 73 | 74 | /// Executes the given JavaScript and calls the given callback (on the GUI thread) to provide the result. 75 | void bw_BrowserWindow_evalJs( bw_BrowserWindow* bw, bw_CStrSlice js, bw_BrowserWindowJsCallbackFn callback, void* cb_data ); 76 | void bw_BrowserWindow_evalJsThreaded( bw_BrowserWindow* bw, bw_CStrSlice js, bw_BrowserWindowJsCallbackFn callback, void* cb_data ); 77 | 78 | void bw_BrowserWindow_free(bw_BrowserWindow* bw); 79 | 80 | bw_Application* bw_BrowserWindow_getApp( bw_BrowserWindow* bw ); 81 | void* bw_BrowserWindow_getUserData( bw_BrowserWindow* bw ); 82 | BOOL bw_BrowserWindow_getUrl(bw_BrowserWindow* bw, bw_StrSlice* url); 83 | bw_Window* bw_BrowserWindow_getWindow( bw_BrowserWindow* bw ); 84 | 85 | bw_Err bw_BrowserWindow_navigate( bw_BrowserWindow* bw, bw_CStrSlice url ); 86 | 87 | /// Allocates a browser window and creates the window for it. 88 | /// Call `bw_BrowserWindow_create` on it to add the actual browser framework to this window. 89 | bw_BrowserWindow* bw_BrowserWindow_new( 90 | bw_Application* app, 91 | const bw_Window* parent, 92 | bw_CStrSlice title, 93 | int width, int height, 94 | const bw_WindowOptions* window_options 95 | ); 96 | 97 | /// Adds the browser framework to the browser window. 98 | /// Should be called right after `bw_BrowserWindow_new`. 99 | void bw_BrowserWindow_create( 100 | bw_BrowserWindow* bw, 101 | int width, int height, 102 | bw_BrowserWindowSource source, 103 | const bw_BrowserWindowOptions* browser_window_options, 104 | bw_BrowserWindowCreationCallbackFn callback, // A function that gets invoked when the browser window has been created. 105 | void* callback_data // Data that will be passed to the creation callback 106 | ); 107 | 108 | 109 | #ifdef __cplusplus 110 | } // extern "C" 111 | #endif 112 | 113 | #endif//BW_BROWSER_WINDOW_H 114 | -------------------------------------------------------------------------------- /c/src/browser_window/cef.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_BROWSER_WINDOW_CEF_H 2 | #define BW_BROWSER_WINDOW_CEF_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | 10 | typedef struct { 11 | void* cef_ptr; 12 | char* resource_path; 13 | } bw_BrowserWindowImpl; 14 | 15 | 16 | 17 | #ifdef __cplusplus 18 | } // extern "C" 19 | #endif 20 | 21 | #endif//BW_BROWSER_WINDOW_CEF_H 22 | -------------------------------------------------------------------------------- /c/src/browser_window/common.c: -------------------------------------------------------------------------------- 1 | #include "../browser_window.h" 2 | #include "../common.h" 3 | 4 | #include "impl.h" 5 | 6 | 7 | void bw_BrowserWindow_free( bw_BrowserWindow* bw ) { 8 | bw_BrowserWindowImpl_clean(&bw->impl); 9 | //free(bw); 10 | } 11 | 12 | bw_Application* bw_BrowserWindow_getApp( bw_BrowserWindow* bw ) { 13 | return bw->window->app; 14 | } 15 | 16 | bw_Window* bw_BrowserWindow_getWindow( bw_BrowserWindow* bw ) { 17 | return bw->window; 18 | } 19 | 20 | bw_BrowserWindow* bw_BrowserWindow_new( 21 | bw_Application* app, 22 | const bw_Window* parent, 23 | bw_CStrSlice title, 24 | int width, int height, 25 | const bw_WindowOptions* window_options 26 | ) { 27 | bw_Application_assertCorrectThread( app ); 28 | 29 | bw_BrowserWindow* browser = (bw_BrowserWindow*)malloc( sizeof( bw_BrowserWindow ) ); 30 | browser->window = bw_Window_new( app, parent, title, width, height, window_options ); 31 | browser->window->browser = browser; 32 | memset(&browser->events, 0, sizeof(bw_BrowserWindowEvents)); 33 | return browser; 34 | } 35 | 36 | void bw_BrowserWindow_create( 37 | bw_BrowserWindow* bw, 38 | int width, int height, 39 | bw_BrowserWindowSource source, 40 | const bw_BrowserWindowOptions* browser_window_options, 41 | bw_BrowserWindowCreationCallbackFn callback, // A function that gets invoked when the browser window has been created. 42 | void* callback_data // Data that will be passed to the creation callback 43 | ) { 44 | bw_BrowserWindowImpl_new( 45 | bw, 46 | source, 47 | width, 48 | height, 49 | browser_window_options, 50 | callback, 51 | callback_data 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /c/src/browser_window/edge2.c: -------------------------------------------------------------------------------- 1 | #include "../browser_window.h" 2 | #include "impl.h" 3 | 4 | 5 | void bw_BrowserWindowImpl_clean(bw_BrowserWindowImpl* bw) { 6 | UNUSED(bw); 7 | } 8 | 9 | void bw_BrowserWindowImpl_new( 10 | bw_BrowserWindow* browser, 11 | bw_BrowserWindowSource source, 12 | int width, int height, 13 | const bw_BrowserWindowOptions* browser_window_options, 14 | bw_BrowserWindowCreationCallbackFn callback, 15 | void* callback_data 16 | ) { 17 | browser->impl.controller = NULL; 18 | browser->impl.webview = NULL; 19 | } 20 | -------------------------------------------------------------------------------- /c/src/browser_window/edge2.cpp.old: -------------------------------------------------------------------------------- 1 | /// WebView2 is corresponds to the Microsoft Egde browser engine that actually makes use of the chromium browser engine. 2 | #include "../browser_window.h" 3 | 4 | //#include 5 | //#include 6 | //#include 7 | //#include 8 | //#include 9 | //#include 10 | //#include 11 | //#include 12 | //#include 13 | 14 | #include "../win32.h" 15 | #include "../window.h" 16 | 17 | 18 | #define ASSERT_ON_FAIL( HR_STATEMENT ) \ 19 | { \ 20 | HRESULT r = (HR_STATEMENT); \ 21 | if ( r != 0 ) { \ 22 | BW_WIN32_PANIC_HRESULT( r ); \ 23 | } \ 24 | } 25 | 26 | 27 | 28 | #pragma comment(lib, "WebView2Loader.dll.lib") 29 | 30 | 31 | 32 | using namespace Microsoft::WRL; 33 | 34 | 35 | 36 | void _bw_BrowserWindow_doCleanup( bw_BrowserWindow* bw ) { 37 | 38 | } 39 | 40 | void bw_BrowserWindow_evalJs( bw_BrowserWindow* bw, bw_CStrSlice _js, bw_BrowserWindowJsCallbackFn callback, void* cb_data ) { 41 | WCHAR* js = bw_win32_copyAsNewWstr( _js ); 42 | 43 | reinterpret_cast(bw->impl.webview)->ExecuteScript( js, Callback( 44 | [bw, cb_data, callback]( HRESULT error_code, LPCWSTR result ) -> HRESULT { 45 | 46 | if ( error_code != 0 ) { 47 | bw_Err err = bw_win32_unhandledHresult( error_code ); 48 | callback( bw, cb_data, 0, &err ); 49 | } 50 | else { 51 | char* cstr = bw_win32_copyWstrAsNewCstr( result ); 52 | callback( bw, cb_data, cstr, 0 ); 53 | free( cstr ); 54 | } 55 | 56 | return S_OK; 57 | }).Get() 58 | ); 59 | } 60 | 61 | bw_Err bw_BrowserWindow_navigate( bw_BrowserWindow* bw, bw_CStrSlice _url ) { 62 | WCHAR* url = bw_win32_copyAsNewWstr( _url ); 63 | 64 | HRESULT res = reinterpret_cast(bw->impl.webview)->Navigate( url ); 65 | if ( res != 0 ) 66 | return bw_win32_unhandledHresult( res ); 67 | 68 | SysFreeString( url ); 69 | 70 | BW_ERR_RETURN_SUCCESS; 71 | } 72 | 73 | /// Creates a new browser window without any content 74 | void bw_BrowserWindowImpl_new( 75 | bw_BrowserWindow* browser_window, 76 | bw_BrowserWindowSource source, 77 | int width, int height, 78 | const bw_BrowserWindowOptions* browser_window_options, 79 | bw_BrowserWindowCreationCallbackFn callback, 80 | void* callback_data 81 | ) { 82 | // The options are passed to callbacks that might run after the passed options live. 83 | // So we copy them just to be sure... 84 | bw_BrowserWindowOptions options = *browser_window_options; 85 | 86 | //_bw_BrowserWindow_init_window_callbacks( browser_window ); 87 | 88 | // The pointer of source.data may not be valid anymore when a callback is fired. 89 | WCHAR* source_data = bw_win32_copyAsNewWstr( source.data ); 90 | 91 | // TODO: Instead of using the default location that edge is installed in, look up the install dir from the registry, and then default back to the default install dir 92 | HRESULT result = CreateCoreWebView2EnvironmentWithOptions( nullptr, nullptr, nullptr, 93 | 94 | Callback( 95 | [browser_window, options, source, source_data](HRESULT result, ICoreWebView2Environment* env) -> HRESULT { 96 | 97 | // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd 98 | HRESULT r = env->CreateCoreWebView2Controller( browser_window->window->impl.handle, Callback( 99 | [browser_window, options, source, source_data](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT { 100 | if (controller != nullptr) { 101 | 102 | browser_window->impl.webview_controller = controller; 103 | 104 | ASSERT_ON_FAIL( controller->get_CoreWebView2( reinterpret_cast(&browser_window->impl.webview) ) ); 105 | 106 | auto webview = reinterpret_cast( browser_window->impl.webview ); 107 | 108 | // Add a few settings for the webview 109 | // The demo step is redundant since the values are the default settings 110 | ICoreWebView2Settings* settings; 111 | ASSERT_ON_FAIL( webview->get_Settings(&settings) ); 112 | 113 | ASSERT_ON_FAIL( settings->put_IsScriptEnabled(true) ); 114 | ASSERT_ON_FAIL( settings->put_AreDefaultScriptDialogsEnabled(true) ); 115 | ASSERT_ON_FAIL( settings->put_IsWebMessageEnabled(true) ); 116 | ASSERT_ON_FAIL( settings->put_AreDevToolsEnabled(options.dev_tools) ); 117 | 118 | // Resize WebView to fit the bounds of the parent window 119 | RECT bounds; 120 | GetClientRect(browser_window->window->impl.handle, &bounds); 121 | ASSERT_ON_FAIL( controller->put_Bounds(bounds) ); 122 | 123 | // Navigate to the source provided 124 | // If it is an URL: 125 | 126 | if ( !source.is_html ) { 127 | ASSERT_ON_FAIL( webview->Navigate( source_data ) ); 128 | } 129 | // If it is plain HTML: 130 | else { 131 | ASSERT_ON_FAIL( webview->NavigateToString( source_data ) ); 132 | } 133 | free( source_data ); 134 | 135 | 136 | // Fire on_laoded callback 137 | //if ( browser_window->callbacks.on_loaded != 0 ) 138 | // browser_window->callbacks.on_loaded( browser_window ); 139 | 140 | if ( !UpdateWindow( browser_window->window->impl.handle ) ) { 141 | BW_WIN32_PANIC_LAST_ERROR; 142 | } 143 | fprintf(stderr,"Done!\n"); 144 | 145 | return S_OK; 146 | } 147 | } 148 | ).Get() ); 149 | 150 | if ( r != 0 ) { 151 | BW_WIN32_PANIC_HRESULT( r ); 152 | } 153 | 154 | return S_OK; 155 | } 156 | ).Get() 157 | ); 158 | 159 | if ( result != 0 ) { 160 | 161 | if ( result == __HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) { 162 | fprintf( stderr, "Microsoft Edge WebView2 installation not found!\n" ); 163 | assert(0); 164 | } 165 | else { 166 | BW_WIN32_PANIC_HRESULT( result ); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /c/src/browser_window/edge2.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_BROWSER_WINDOW_EDGE2_H 2 | #define BW_BROWSER_WINDOW_EDGE2_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | typedef struct { 10 | void* webview; /* Type: ICoreWebView2 */ 11 | void* controller; /* Type: ICoreWebView2Controller */ 12 | } bw_BrowserWindowImpl; 13 | 14 | 15 | #ifdef __cplusplus 16 | } // extern "C" 17 | #endif 18 | 19 | #endif//BW_BROWSER_WINDOW_EDGE2_H -------------------------------------------------------------------------------- /c/src/browser_window/impl.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_BROWSER_WINDOW_IMPL_H 2 | #define BW_BROWSER_WINDOW_IMPL_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | 10 | void bw_BrowserWindowImpl_clean(bw_BrowserWindowImpl* bw); 11 | 12 | // Should be implemented by the underlying browser engine to create a new browser and invoke the callback. 13 | void bw_BrowserWindowImpl_new( 14 | bw_BrowserWindow* browser, 15 | bw_BrowserWindowSource source, 16 | int width, int height, 17 | const bw_BrowserWindowOptions* browser_window_options, 18 | bw_BrowserWindowCreationCallbackFn callback, 19 | void* callback_data 20 | ); 21 | 22 | void bw_BrowserWindowImpl_onResize( const bw_Window* window, unsigned int width, unsigned int height ); 23 | 24 | 25 | 26 | #ifdef __cplusplus 27 | } // extern "C" 28 | #endif 29 | 30 | #endif//BW_BROWSER_WINDOW_IMPL_H 31 | -------------------------------------------------------------------------------- /c/src/cef/app_handler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_APP_HANDLER_H 2 | #define BW_CEF_APP_HANDLER_H 3 | 4 | #include "../assert.h" 5 | #include "../application.h" 6 | #include "../browser_window.h" 7 | 8 | #include "external_invocation_handler.hpp" 9 | #include "v8_to_string.hpp" 10 | #include "../cef/bw_handle_map.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | 19 | class AppHandler : public CefApp, public CefRenderProcessHandler { 20 | 21 | bw_Application* app; 22 | 23 | public: 24 | AppHandler( bw_Application* app ) : app(app) {} 25 | 26 | virtual void OnBrowserCreated( CefRefPtr browser, CefRefPtr extra_info ) override { 27 | /*if (browser->IsPopup()) { return; } 28 | printf("OnBrowserCreated %p\n", browser->GetMainFrame()); 29 | auto main_frame = browser->GetMainFrame(); 30 | if (main_frame != NULL) { 31 | // Lets send the handle and callback data back to the browser process, where we can actually use them 32 | auto msg = CefProcessMessage::Create( "on-browser-created" ); 33 | auto args = msg->GetArgumentList(); 34 | 35 | // Load in our other data 36 | args->SetBinary( 0, extra_info->GetBinary( "handle" ) ); 37 | args->SetBinary( 1, extra_info->GetBinary( "callback" ) ); 38 | args->SetBinary( 2, extra_info->GetBinary( "callback-data" ) ); 39 | args->SetBool( 3, extra_info->GetBool( "dev-tools" ) ); 40 | printf("OnBrowserCreated SendProcessMessage\n"); 41 | main_frame->SendProcessMessage( PID_BROWSER, msg ); 42 | }*/ 43 | } 44 | 45 | virtual void OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override { 46 | if (browser->IsPopup()) { return; } 47 | 48 | // Unused parameters 49 | (void)(frame); 50 | 51 | CefRefPtr object = context->GetGlobal(); 52 | 53 | CefRefPtr handler = new bw::ExternalInvocationHandler( browser ); 54 | CefRefPtr func = CefV8Value::CreateFunction("invoke_extern", handler); 55 | 56 | bool result = object->SetValue( "invoke_extern", func, V8_PROPERTY_ATTRIBUTE_NONE ); 57 | BW_ASSERT( result, "Unable to set invoke_extern function." ); 58 | } 59 | 60 | virtual CefRefPtr GetRenderProcessHandler() override { 61 | return this; 62 | } 63 | 64 | virtual bool OnProcessMessageReceived( 65 | CefRefPtr browser, 66 | CefRefPtr frame, 67 | CefProcessId source_process, 68 | CefRefPtr message 69 | ) override { 70 | // Unused parameters 71 | (void)(browser); 72 | (void)(frame); 73 | (void)(source_process); 74 | 75 | // The message to execute some javascript, and return its output 76 | if ( message->GetName() == "eval-js" ) { 77 | auto msg_args = message->GetArgumentList(); 78 | 79 | // Javascript to execute 80 | CefString js = msg_args->GetString( 0 ); 81 | 82 | // Browser window handle 83 | CefRefPtr bw_bin = msg_args->GetBinary( 1 ); 84 | CefRefPtr cb_bin = msg_args->GetBinary( 2 ); 85 | CefRefPtr user_data_bin = msg_args->GetBinary( 3 ); 86 | 87 | 88 | this->eval_js( browser, frame, js, bw_bin, cb_bin, user_data_bin ); 89 | 90 | return true; 91 | } 92 | else 93 | fprintf(stderr, "Unknown process message received: %s\n", message->GetName().ToString().c_str() ); 94 | 95 | return false; 96 | } 97 | 98 | // Evaluate JavaScript, and send back a message to the main process with the result 99 | void eval_js( 100 | CefRefPtr browser, 101 | CefRefPtr frame, 102 | const CefString& js, 103 | CefRefPtr bw_handle_binary, 104 | CefRefPtr callback_binary, 105 | CefRefPtr user_data_binary 106 | ) { 107 | // Unused parameters 108 | (void)(browser); 109 | 110 | CefString script_url( "eval" ); 111 | CefRefPtr ret_val; 112 | CefRefPtr exception; 113 | 114 | bool result = frame->GetV8Context()->Eval( js, script_url, 0, ret_val, exception ); 115 | 116 | // IPC message to be send to notify browser process of eval result 117 | CefRefPtr msg = CefProcessMessage::Create("eval-js-result"); 118 | CefRefPtr msg_args = msg->GetArgumentList(); 119 | 120 | if ( !result ) { 121 | 122 | // The first parameter specifies whether or not an error has resulted 123 | msg_args->SetBool( 0, false ); 124 | // The second parameter specifies the error message 125 | msg_args->SetString( 1, exception->GetMessage() ); 126 | } 127 | else { 128 | 129 | CefString result_string = V8ToString::convert( ret_val ); 130 | 131 | // The first parameter specifies whether or not an error has resulted 132 | msg_args->SetBool( 0, true ); 133 | // The second parameter specifies the result formatted as a string 134 | msg_args->SetString( 1, result_string ); 135 | } 136 | 137 | // Send along the binaries of the callback data 138 | msg_args->SetBinary( 2, bw_handle_binary ); 139 | msg_args->SetBinary( 3, callback_binary ); 140 | msg_args->SetBinary( 4, user_data_binary ); 141 | 142 | // Send the message back to the browser process 143 | frame->SendProcessMessage( PID_BROWSER, msg ); 144 | } 145 | 146 | protected: 147 | IMPLEMENT_REFCOUNTING(AppHandler); 148 | }; 149 | 150 | 151 | 152 | #endif//BW_CEF_APP_HANDLER_H 153 | -------------------------------------------------------------------------------- /c/src/cef/bw_handle_map.cpp: -------------------------------------------------------------------------------- 1 | #include "bw_handle_map.hpp" 2 | 3 | 4 | 5 | bw::HandleMap bw::bw_handle_map; 6 | -------------------------------------------------------------------------------- /c/src/cef/bw_handle_map.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_BW_HANDLE_MAP 2 | #define BW_CEF_BW_HANDLE_MAP 3 | 4 | #include "../browser_window.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | 13 | namespace bw { 14 | 15 | struct OnCreateCallback { 16 | bw_BrowserWindowCreationCallbackFn callback; 17 | void* data; 18 | }; 19 | 20 | struct BrowserInfo { 21 | bw_BrowserWindow* handle; 22 | std::optional callback; 23 | }; 24 | 25 | // A thread safe class that links CEF browser handles to our browser window handdles. 26 | // This makes it possible to get a bw_BrowserWindow* from a CefRefPtr, from any thread. 27 | class HandleMap { 28 | // The CefBrowser's GetIdentifier output is used as the key to identify CEF browser handles 29 | std::map map; 30 | std::mutex mutex; 31 | 32 | public: 33 | // The only constructor is the default constructor 34 | HandleMap() {} 35 | 36 | // Remove a link 37 | void drop( CefRefPtr cef_handle ) { 38 | this->mutex.lock(); 39 | this->map.erase(cef_handle->GetIdentifier()); 40 | this->mutex.unlock(); 41 | } 42 | 43 | // Stores a link 44 | void store(CefRefPtr cef_handle, bw_BrowserWindow* our_handle, bw_BrowserWindowCreationCallbackFn callback, void* callback_data) { 45 | BrowserInfo& bw_info = this->map[cef_handle->GetIdentifier()]; 46 | bw_info.handle = our_handle; 47 | OnCreateCallback occ = { 48 | callback, 49 | callback_data 50 | }; 51 | bw_info.callback = std::optional(occ); 52 | this->mutex.unlock(); 53 | } 54 | 55 | // Fetches a bw_BrowserWindow handle from a cef handle. 56 | // Returns an optional bw_BrowserWindow pointer. 57 | std::optional fetch( CefRefPtr cef_handle ) { 58 | this->mutex.lock(); 59 | auto it = this->map.find( cef_handle->GetIdentifier() ); 60 | 61 | // If not found return nothing 62 | if ( it == this->map.end() ) 63 | return std::optional(); 64 | 65 | // If found: 66 | std::optional result(&(*it).second); 67 | this->mutex.unlock(); 68 | 69 | return result; 70 | } 71 | }; 72 | 73 | // A global instance 74 | extern HandleMap bw_handle_map; 75 | } 76 | 77 | 78 | 79 | #endif//BW_CEF_BW_HANDLE_MAP 80 | -------------------------------------------------------------------------------- /c/src/cef/client_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "client_handler.hpp" 2 | 3 | 4 | void ClientHandler::externalInvocationHandlerFunc( bw_Application* app, void* _data ) { 5 | auto data = (ExternalInvocationHandlerData*)_data; 6 | 7 | // Slice of the command string 8 | bw_CStrSlice cmd_str_slice = { 9 | data->cmd.length(), 10 | data->cmd.c_str() 11 | }; 12 | 13 | // Slices of the arguments 14 | std::vector params_slices; params_slices.reserve( data->params.capacity() ); 15 | for ( size_t i = 0; i < data->params.size(); i++ ) { 16 | std::string& param = data->params[i]; 17 | 18 | // Convert the stored param into a bw_CStrSlice 19 | bw_CStrSlice param_str_slice = { 20 | param.length(), 21 | param.c_str() 22 | }; 23 | params_slices.push_back( param_str_slice ); 24 | } 25 | 26 | // Fire! 27 | bw_BrowserWindowMessageArgs args = { 28 | cmd_str_slice, 29 | params_slices.size(), 30 | ¶ms_slices[0] 31 | }; 32 | bw_Event_fire(&data->bw->events.on_message, (void*)&args); 33 | } -------------------------------------------------------------------------------- /c/src/cef/exception.cpp: -------------------------------------------------------------------------------- 1 | #include "exception.hpp" 2 | 3 | 4 | 5 | bw_Err bw_cef_v8exc_to_bwerr( const CefRefPtr& exc ) { 6 | 7 | bw_Err err; 8 | err.code = 1; // CefV8Exception doesn't provide an error code 9 | err.alloc_message = bw_Err_msg_string; 10 | 11 | // Allocate a string with the exception message 12 | // TODO: Conversion can be done more efficiently by doing the conversion from wide string to normal string in the newly allocated buffer immediately 13 | std::string error_msg = exc->GetMessage().ToString(); 14 | void* copy = malloc( error_msg.length() + 1 ); 15 | memcpy( copy, error_msg.c_str(), error_msg.length() + 1 ); 16 | 17 | err.data = copy; 18 | return err; 19 | } 20 | -------------------------------------------------------------------------------- /c/src/cef/exception.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_EXCEPTION_HPP 2 | #define BW_CEF_EXCEPTION_HPP 3 | 4 | #include 5 | 6 | #include "../err.h" 7 | 8 | 9 | 10 | // Creates a new bw_Err instance that translates the CefV8Exception 11 | bw_Err bw_cef_v8exc_to_bwerr( const CefRefPtr& exc ); 12 | 13 | 14 | 15 | #endif//BW_CEF_EXCEPTION_HPP 16 | -------------------------------------------------------------------------------- /c/src/cef/external_invocation_handler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_EXTERNAL_INVOCATION_HANDLER 2 | #define BW_CEF_EXTERNAL_INVOCATION_HANDLER 3 | 4 | #include 5 | #include 6 | 7 | #include "bw_handle_map.hpp" 8 | #include "v8_to_string.hpp" 9 | #include "../assert.h" 10 | 11 | 12 | 13 | namespace bw { 14 | 15 | class ExternalInvocationHandler : public CefV8Handler { 16 | CefRefPtr cef_browser; 17 | 18 | public: 19 | ExternalInvocationHandler( CefRefPtr browser ) : cef_browser(browser) {} 20 | 21 | virtual bool Execute( 22 | const CefString& name, 23 | CefRefPtr object, 24 | const CefV8ValueList& arguments, 25 | CefRefPtr& retval, 26 | CefString& exception 27 | ) override { 28 | (void)(object); 29 | (void)(retval); 30 | (void)(exception); 31 | 32 | if ( name == "invoke_extern" ) { 33 | 34 | CefRefPtr msg = CefProcessMessage::Create("invoke-handler"); 35 | CefRefPtr msg_args = msg->GetArgumentList(); 36 | 37 | // Convert all function arguments to strings 38 | size_t index = 0; 39 | for ( auto it = arguments.begin(); it != arguments.end(); it++, index++ ) { 40 | 41 | if (index == 0) { 42 | msg_args->SetString(0, (*it)->GetStringValue()); 43 | } else { 44 | CefString string = V8ToString::convert(*it); 45 | msg_args->SetString( index, string ); 46 | } 47 | } 48 | 49 | this->cef_browser->GetMainFrame()->SendProcessMessage( PID_BROWSER, msg ); 50 | } 51 | 52 | return false; 53 | } 54 | 55 | protected: 56 | IMPLEMENT_REFCOUNTING(ExternalInvocationHandler); 57 | }; 58 | } 59 | 60 | 61 | 62 | #endif//BW_CEF_EXTERNAL_INVOCATION_HANDLER 63 | -------------------------------------------------------------------------------- /c/src/cef/seperate_executable.cpp: -------------------------------------------------------------------------------- 1 | #include "app_handler.hpp" 2 | 3 | #include 4 | #include 5 | 6 | 7 | 8 | int main(int argc, char* argv[]) { 9 | #ifdef BW_MACOS 10 | // Initialize the macOS sandbox for this helper process. 11 | CefScopedSandboxContext sandbox_context; 12 | if (!sandbox_context.Initialize(argc, argv)) 13 | return 1; 14 | 15 | // Load the CEF framework library at runtime instead of linking directly 16 | // as required by the macOS sandbox implementation. 17 | CefScopedLibraryLoader library_loader; 18 | if (!library_loader.LoadInHelper()) 19 | return 1; 20 | #endif 21 | 22 | // Structure for passing command-line arguments. 23 | // The definition of this structure is platform-specific. 24 | #ifndef BW_WINDOWS 25 | CefMainArgs main_args(argc, argv); 26 | #else 27 | CefMainArgs main_args(GetModuleHandle(NULL)); 28 | #endif 29 | 30 | // Optional implementation of the CefApp interface. 31 | CefRefPtr app( new AppHandler( 0 ) ); 32 | 33 | // Execute the sub-process logic. This will block until the sub-process should exit. 34 | return CefExecuteProcess(main_args, app, 0); 35 | } -------------------------------------------------------------------------------- /c/src/cef/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | 3 | #include 4 | 5 | 6 | 7 | CefString bw_cef_copyFromStrSlice( bw_CStrSlice slice ) { 8 | 9 | std::string temp( slice.data, slice.len ); 10 | CefString string( temp ); 11 | 12 | return string; 13 | } 14 | 15 | size_t bw_cef_copyToCstr( const CefString& cef_string, char** cstr ) { 16 | std::string temp = cef_string.ToString(); 17 | 18 | *cstr = (char*)malloc(temp.length()); 19 | memcpy(*cstr, temp.c_str(), temp.length()); 20 | 21 | return temp.length(); 22 | } 23 | 24 | bw_CStrSlice bw_cef_copyToCStrSlice(const CefString& string) { 25 | bw_CStrSlice slice; 26 | 27 | std::string temp = string.ToString(); 28 | 29 | size_t len = temp.length(); 30 | char* data = (char*)malloc(temp.length()); 31 | memcpy(data, temp.c_str(), len); 32 | slice.data = data; 33 | slice.len = temp.length(); 34 | return slice; 35 | } 36 | 37 | bw_StrSlice bw_cef_copyToStrSlice(const CefString& string) { 38 | bw_StrSlice slice; 39 | 40 | std::string temp = string.ToString(); 41 | 42 | size_t len = temp.length(); 43 | char* data = (char*)malloc(len); 44 | memcpy(data, temp.c_str(), len); 45 | slice.data = data; 46 | slice.len = len; 47 | return slice; 48 | } -------------------------------------------------------------------------------- /c/src/cef/util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_H 2 | #define BW_CEF_H 3 | 4 | #include "../string.h" 5 | 6 | #include 7 | 8 | 9 | CefString bw_cef_copyFromStrSlice( bw_CStrSlice slice ); 10 | size_t bw_cef_copyToCstr( const CefString& cef_string, char** cstr ); 11 | bw_CStrSlice bw_cef_copyToCStrSlice(const CefString& string); 12 | bw_StrSlice bw_cef_copyToStrSlice(const CefString& string); 13 | 14 | 15 | #endif//BW_CEF_H -------------------------------------------------------------------------------- /c/src/cef/v8_to_string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BW_CEF_V8_TO_STRING_HPP 2 | #define BW_CEF_V8_TO_STRING_HPP 3 | 4 | #include 5 | #include 6 | 7 | 8 | class V8ToString { 9 | public: 10 | 11 | // Convert a javascript value into a string, for very basic compatibility purposes with the Rust application 12 | // Note: This function is not yet complete. Not all types are converted appropriately. 13 | static CefString convert( CefRefPtr val ) { 14 | 15 | // If undefined 16 | if ( val->IsUndefined() ) 17 | return "undefined"; 18 | 19 | // If null 20 | if ( val->IsNull() ) 21 | return "null"; 22 | 23 | // If string 24 | if ( val->IsString() ) { 25 | std::string string = "\""; 26 | // TODO: Escape the string value: 27 | string += val->GetStringValue(); 28 | string += "\""; 29 | return string; 30 | } 31 | 32 | // If boolean 33 | if ( val->IsBool() ) 34 | return boolToString( val->GetBoolValue() ); 35 | 36 | // If integer 37 | if ( val->IsInt() ) 38 | return intoString( val->GetIntValue() ); 39 | 40 | // If unsigned integer 41 | if ( val->IsUInt() ) 42 | return intoString( val->GetUIntValue() ); 43 | 44 | // If unsigned integer 45 | if ( val->IsDouble() ) 46 | return intoString( val->GetDoubleValue() ); 47 | 48 | // If array 49 | if ( val->IsArray() ) { 50 | std::string string = "["; 51 | for (int i = 0; i < val->GetArrayLength(); i++) { 52 | if (i != 0) { 53 | string += ","; 54 | } 55 | string += convert(val->GetValue(i)); 56 | } 57 | string += "]"; 58 | return string; 59 | } 60 | 61 | // If object 62 | if ( val->IsObject() ) { 63 | std::vector keys; 64 | val->GetKeys(keys); 65 | std::string string = "{"; 66 | for (size_t i = 0; i < keys.size(); i++) { 67 | std::string key = keys[i]; 68 | if (i != 0) { 69 | string += ","; 70 | } 71 | string += key + ":" + convert(val->GetValue(i)).ToString(); 72 | } 73 | string += "}"; 74 | return string; 75 | } 76 | 77 | if ( val->IsDate() ) 78 | return "date"; 79 | 80 | // If exception (unsupported) 81 | if ( val->IsFunction() ) 82 | return "function"; 83 | 84 | // If type is not accounted for, return this string: 85 | return "unknown type"; 86 | } 87 | 88 | protected: 89 | 90 | static CefString boolToString( bool boolean ) { 91 | if ( boolean ) 92 | return "true"; 93 | // else 94 | return "false"; 95 | } 96 | 97 | template 98 | static CefString intoString( const V& value ) { 99 | std::string str = std::to_string( value ); 100 | return CefString( str ); 101 | } 102 | }; 103 | 104 | 105 | 106 | #endif//BW_CEF_V8_TO_STRING_HPP -------------------------------------------------------------------------------- /c/src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_COMMON_H 2 | #define BW_COMMON_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | 10 | #include "assert.h" 11 | #include "debug.h" 12 | 13 | #ifdef BW_GTK 14 | #include "posix.h" 15 | #endif 16 | 17 | #include 18 | 19 | 20 | 21 | #define UNUSED( X ) \ 22 | (void)( X ); 23 | 24 | 25 | typedef struct { 26 | uint16_t width; 27 | uint16_t height; 28 | } bw_Dims2D; 29 | 30 | typedef struct { 31 | uint16_t x; 32 | uint16_t y; 33 | } bw_Pos2D; 34 | 35 | 36 | 37 | #ifdef __cplusplus 38 | } // extern "C" 39 | #endif 40 | 41 | #endif//BW_COMMON_H 42 | -------------------------------------------------------------------------------- /c/src/cookie.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_COOKIE_H 2 | #define BW_COOKIE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "bool.h" 9 | #include "err.h" 10 | #include "string.h" 11 | 12 | #include 13 | 14 | #ifdef BW_CEF 15 | #include "cookie/cef.h" 16 | #else 17 | struct bw_CookieImpl { void* _; }; 18 | struct bw_CookieIteratorImpl { void* _; }; 19 | struct bw_CookieJarImpl { void* _; }; 20 | #endif 21 | 22 | 23 | 24 | typedef struct { 25 | struct bw_CookieImpl impl; 26 | } bw_Cookie; 27 | 28 | typedef struct { 29 | struct bw_CookieJarImpl impl; 30 | } bw_CookieJar; 31 | 32 | typedef struct { 33 | struct bw_CookieIteratorImpl impl; 34 | } bw_CookieIterator; 35 | 36 | typedef void (*bw_CookieJarStorageCallbackFn)( bw_CookieJar* cj, void* data, bw_Err error ); 37 | typedef void (*bw_CookieIteratorNextCallbackFn)(bw_CookieIterator* ci, void* data, bw_Cookie* cookie); 38 | typedef void (*bw_CookieJarDeleteCallbackFn)(bw_CookieJar* cj, void* data, unsigned int deleted); 39 | 40 | 41 | 42 | void bw_Cookie_free(bw_Cookie* cookie); 43 | bw_Cookie* bw_Cookie_new(bw_CStrSlice name, bw_CStrSlice value); 44 | uint64_t bw_Cookie_getCreationTime(const bw_Cookie* cookie); 45 | void bw_Cookie_setCreationTime(bw_Cookie* cookie, uint64_t time); 46 | BOOL bw_Cookie_getDomain(const bw_Cookie* cookie, bw_StrSlice* domain); 47 | void bw_Cookie_setDomain(bw_Cookie* cookie, bw_CStrSlice domain); 48 | uint64_t bw_Cookie_getExpires(const bw_Cookie* cookie); 49 | void bw_Cookie_setExpires(bw_Cookie* cookie, uint64_t time); 50 | BOOL bw_Cookie_getName(const bw_Cookie* cookie, bw_StrSlice* name); 51 | void bw_Cookie_setName(bw_Cookie* cookie, bw_CStrSlice name); 52 | BOOL bw_Cookie_getPath(const bw_Cookie* cookie, bw_StrSlice* path); 53 | void bw_Cookie_setPath(bw_Cookie* cookie, bw_CStrSlice path); 54 | BOOL bw_Cookie_getValue(const bw_Cookie* cookie, bw_StrSlice* value); 55 | void bw_Cookie_setValue(bw_Cookie* cookie, bw_CStrSlice value); 56 | BOOL bw_Cookie_isHttpOnly(const bw_Cookie* cookie); 57 | void bw_Cookie_makeHttpOnly(bw_Cookie* cookie); 58 | BOOL bw_Cookie_isSecure(const bw_Cookie* cookie); 59 | void bw_Cookie_makeSecure(bw_Cookie* cookie); 60 | 61 | void bw_CookieJar_delete(bw_CookieJar* jar, bw_CStrSlice url, bw_CStrSlice name, bw_CookieJarDeleteCallbackFn cb, void* cb_data); 62 | void bw_CookieJar_free(bw_CookieJar* jar); 63 | void bw_CookieJar_iterator(bw_CookieJar* jar, bw_CookieIterator** iterator, BOOL include_http_only, bw_CStrSlice url); 64 | void bw_CookieJar_iteratorAll(bw_CookieJar* jar, bw_CookieIterator** iterator); 65 | bw_CookieJar* bw_CookieJar_newGlobal(); 66 | bw_Err bw_CookieJar_store(bw_CookieJar* jar, bw_CStrSlice url, const bw_Cookie* cookie, bw_CookieJarStorageCallbackFn cb, void* cb_data); 67 | 68 | void bw_CookieIterator_free(bw_CookieIterator* iterator); 69 | BOOL bw_CookieIterator_next(bw_CookieIterator* iterator, bw_CookieIteratorNextCallbackFn on_next, void* cb_data); 70 | 71 | 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif//BW_COOKIE_H -------------------------------------------------------------------------------- /c/src/cookie/cef.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_COOKIE_CEF_H 2 | #define BW_COOKIE_CEF_H 3 | 4 | #include 5 | 6 | 7 | 8 | struct bw_CookieImpl { 9 | void* handle_ptr; 10 | }; 11 | 12 | struct bw_CookieJarImpl { 13 | void* handle_ptr; 14 | }; 15 | 16 | struct bw_CookieIteratorImpl { 17 | size_t index; 18 | void* visitor_ptr; 19 | }; 20 | 21 | 22 | 23 | #endif//BW_COOKIE_CEF_H -------------------------------------------------------------------------------- /c/src/cookie/unsupported.c: -------------------------------------------------------------------------------- 1 | #include "../cookie.h" 2 | 3 | #include "../common.h" 4 | 5 | 6 | void bw_Cookie_free(bw_Cookie* cookie) {} 7 | 8 | bw_Cookie* bw_Cookie_new(bw_CStrSlice name, bw_CStrSlice value) { return NULL; } 9 | 10 | uint64_t bw_Cookie_getCreationTime(const bw_Cookie* cookie) { return 0; } 11 | 12 | void bw_Cookie_setCreationTime(bw_Cookie* cookie, uint64_t time) {} 13 | 14 | BOOL bw_Cookie_getDomain(const bw_Cookie* cookie, bw_StrSlice* domain) { return FALSE; } 15 | 16 | void bw_Cookie_setDomain(bw_Cookie* cookie, bw_CStrSlice domain) {} 17 | 18 | uint64_t bw_Cookie_getExpires(const bw_Cookie* cookie) { return 0; } 19 | 20 | void bw_Cookie_setExpires(bw_Cookie* cookie, uint64_t time) {} 21 | 22 | void bw_Cookie_setName(bw_Cookie* cookie, bw_CStrSlice name) {} 23 | 24 | BOOL bw_Cookie_getPath(const bw_Cookie* cookie, bw_StrSlice* path) { return FALSE; } 25 | 26 | void bw_Cookie_setPath(bw_Cookie* cookie, bw_CStrSlice path) {} 27 | 28 | void bw_Cookie_setValue(bw_Cookie* cookie, bw_CStrSlice value) {} 29 | 30 | BOOL bw_Cookie_isHttpOnly(const bw_Cookie* cookie) { return FALSE; } 31 | 32 | void bw_Cookie_makeHttpOnly(bw_Cookie* cookie) {} 33 | 34 | BOOL bw_Cookie_isSecure(const bw_Cookie* cookie) { return FALSE; } 35 | 36 | void bw_Cookie_makeSecure(bw_Cookie* cookie) {} 37 | 38 | BOOL bw_Cookie_getName(const bw_Cookie* cookie, bw_StrSlice* name) { return FALSE; } 39 | 40 | BOOL bw_Cookie_getValue(const bw_Cookie* cookie, bw_StrSlice* value) { return FALSE; } 41 | 42 | void bw_CookieJar_delete(bw_CookieJar* jar, bw_CStrSlice _url, bw_CStrSlice _name, bw_CookieJarDeleteCallbackFn cb, void* cb_data) {} 43 | 44 | void bw_CookieJar_free(bw_CookieJar* jar) {} 45 | 46 | void bw_CookieJar_iterator(bw_CookieJar* jar, bw_CookieIterator** iterator, BOOL include_http_only, bw_CStrSlice _url) {} 47 | 48 | void bw_CookieJar_iteratorAll(bw_CookieJar* jar, bw_CookieIterator** iterator) {} 49 | 50 | bw_CookieJar* bw_CookieJar_newGlobal() { return NULL; } 51 | 52 | bw_Err bw_CookieJar_store(bw_CookieJar* jar, bw_CStrSlice url, const bw_Cookie* cookie, bw_CookieJarStorageCallbackFn cb, void* cb_data) { 53 | BW_ERR_RETURN_SUCCESS; 54 | } 55 | 56 | void bw_CookieIterator_free(bw_CookieIterator* iterator) {} 57 | 58 | BOOL bw_CookieIterator_next(bw_CookieIterator* iterator, bw_CookieIteratorNextCallbackFn on_next, void* cb_data) { return FALSE; } 59 | -------------------------------------------------------------------------------- /c/src/debug.h: -------------------------------------------------------------------------------- 1 | /* This file contains all functionality to make tool-less debugging a little bit easier. 2 | * Most macros are simply shorter ways to print stuff to the console, that only get compiled in debug builds. 3 | */ 4 | #ifndef BW_DEBUG_H 5 | #define BW_DEBUG_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | 12 | 13 | #include 14 | 15 | 16 | 17 | // Print MESSAGE to the standard error output. 18 | // Additional information can be provided into the message, just like printf. 19 | #ifndef NDEBUG 20 | #define BW_DEBUG( ... ) \ 21 | { \ 22 | fprintf( stderr, "[DEBUG %s:%d] ", __FILE__, __LINE__ ); \ 23 | fprintf( stderr, __VA_ARGS__ ); \ 24 | fprintf( stderr, "\n" ); \ 25 | } 26 | #else 27 | #define BW_DEBUG( ... ) 28 | #endif 29 | 30 | 31 | 32 | #ifdef __cplusplus 33 | } //extern "C" 34 | #endif 35 | 36 | 37 | 38 | #endif//BW_DEBUG_H 39 | -------------------------------------------------------------------------------- /c/src/err.c: -------------------------------------------------------------------------------- 1 | #include "err.h" 2 | 3 | #include 4 | #include 5 | 6 | 7 | BW_ERR_MSG_DEF( bw_Err_msg_success, "success" ) 8 | 9 | char* bw_Err_message(const bw_Err* error) { 10 | return error->alloc_message(error->code, error->data); 11 | } 12 | 13 | char* bw_Err_msg_string( bw_ErrCode code, const void* message ) { 14 | (void)(code); 15 | 16 | return (char*)message; 17 | } 18 | 19 | 20 | 21 | void bw_Err_free( bw_Err* err ) { 22 | free( (void*)err->data ); 23 | } 24 | 25 | bw_Err bw_Err_new_with_msg( bw_ErrCode code, const char* msg ) { 26 | 27 | size_t size = strlen( msg ) + 1; 28 | 29 | void* buffer = malloc( size ); 30 | memcpy( buffer, msg, size ); 31 | 32 | bw_Err e = { 33 | code, 34 | buffer, 35 | bw_Err_msg_string 36 | }; 37 | return e; 38 | } 39 | -------------------------------------------------------------------------------- /c/src/err.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_ERR_H 2 | #define BW_ERR_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | 12 | 13 | typedef unsigned int bw_ErrCode; 14 | 15 | typedef struct bw_Err { 16 | bw_ErrCode code; 17 | const void* data; 18 | char* (*alloc_message)( bw_ErrCode code, const void* data ); /// Returns a newly allocated pointer to a null terminated utf8 string that describes the error 19 | } bw_Err; 20 | 21 | #define BW_ERR_CODE_SUCCESS 0 22 | 23 | #define BW_ERR_IS_FAIL(ERROR) \ 24 | (ERROR).code != BW_ERR_CODE_SUCCESS 25 | #define BW_ERR_IS_OK(ERROR) \ 26 | (ERROR).code == BW_ERR_CODE_SUCCESS 27 | 28 | #define BW_ERR_RETURN( CODE, DATA_PTR, MSG_FUNC ) \ 29 | { bw_Err r; r.code = CODE; r.data = (const void*)DATA_PTR; r.alloc_message = MSG_FUNC; return r; } 30 | 31 | #define BW_ERR_DECLARE(VAR_NAME, CODE, DATA_PTR, MSG_FUNC) \ 32 | bw_Err VAR_NAME = { CODE, DATA_PTR, MSG_FUNC }; 33 | 34 | #define BW_ERR_RETURN_SUCCESS \ 35 | BW_ERR_RETURN( BW_ERR_CODE_SUCCESS, 0, bw_Err_msg_success ) 36 | 37 | #define BW_ERR_DECLARE_SUCCESS(VAR_NAME) \ 38 | BW_ERR_DECLARE(VAR_NAME, BW_ERR_CODE_SUCCESS, 0, bw_Err_msg_success) 39 | 40 | #define BW_ERR_MSG_DEF( FUNC, MSG ) \ 41 | char* FUNC( bw_ErrCode code, const void* data ) { \ 42 | (void)(code); \ 43 | (void)(data); \ 44 | char* msg = (char*)malloc( strlen( MSG ) + 1 ); \ 45 | strcpy( msg, MSG ); \ 46 | return msg; \ 47 | } 48 | 49 | 50 | 51 | char* bw_Err_msg_success( bw_ErrCode, const void* ); 52 | char* bw_Err_msg_string( bw_ErrCode, const void* ); 53 | 54 | // Should always be called on a bw_Err. 55 | // Frees internal data from the heap. 56 | // TODO: Rename to bw_Err_destroy 57 | // Now it sounds like its going to free the memory of bw_Err as well... 58 | void bw_Err_free( bw_Err* err ); 59 | // Creates a new initialized bw_Err, with the given code and message. 60 | // The alloc_message pointer will return the same message as given here. 61 | bw_Err bw_Err_new_with_msg( bw_ErrCode code, const char* msg ); 62 | 63 | char* bw_Err_message(const bw_Err* error); 64 | 65 | 66 | 67 | #ifdef __cplusplus 68 | } // extern "C" 69 | #endif 70 | 71 | #endif//BW_ERR_H 72 | -------------------------------------------------------------------------------- /c/src/event.c: -------------------------------------------------------------------------------- 1 | #include "event.h" 2 | 3 | #include 4 | 5 | 6 | BOOL bw_Event_fire(bw_Event* event, void* arg) { 7 | if (event->callback != NULL) { 8 | return event->callback(event->data, arg); 9 | } 10 | return FALSE; 11 | } -------------------------------------------------------------------------------- /c/src/event.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_EVENT_H 2 | #define BW_EVENT_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "bool.h" 9 | 10 | 11 | typedef BOOL (*bw_EventCallbackFn)(void* arg, void* event_data); 12 | 13 | typedef struct { 14 | bw_EventCallbackFn callback; 15 | void* data; 16 | } bw_Event; 17 | 18 | 19 | BOOL bw_Event_fire(bw_Event* event, void* data); 20 | 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif//BW_EVENT_H -------------------------------------------------------------------------------- /c/src/gl_surface.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_GL_SURFACE_H 2 | #define BW_GL_SURFACE_H 3 | 4 | #include "window.h" 5 | 6 | 7 | 8 | typedef struct { 9 | /// Implementation related data 10 | bw_GlSurfaceImpl impl; 11 | } bw_GlSurface; 12 | 13 | 14 | 15 | /// Removes and cleans up the GL surface, freeing it from memory as well. 16 | void bw_GlSurface_destroy( bw_GlSurface* surface ); 17 | 18 | /// Creates a new GL surface inside the given window. 19 | bw_GlSurface* bw_GlSurface_new( bw_Window* window ); 20 | 21 | 22 | 23 | #endif//BW_GL_SURFACE_H -------------------------------------------------------------------------------- /c/src/gl_surface/common.c: -------------------------------------------------------------------------------- 1 | #include "../gl_surface.h" 2 | #include "impl.h" 3 | 4 | 5 | 6 | void bw_GlSurface_destroy( bw_GlSurface* surface ) { 7 | bw_GlSurfaceImpl_destroy( &surface->impl ); 8 | } 9 | 10 | bw_GlSurface* bw_GlSurface_new( bw_Window* window ) { 11 | bw_GlSurface* surface = (bw_GlSurface*)malloc( sizeof( bw_GlSurface ) ); 12 | 13 | surface->impl = bw_GlSurfaceImpl_new( window ); 14 | 15 | return surface; 16 | } -------------------------------------------------------------------------------- /c/src/gl_surface/glx.c: -------------------------------------------------------------------------------- 1 | #include "../gl_surface.h" 2 | 3 | 4 | 5 | bw_GlSurfaceImpl bw_GlSurfaceImpl_new( bw_Window* window ) { 6 | 7 | glxCreateWindow( display, config, bw_Window_getXWindowHandle( window ) ) 8 | } -------------------------------------------------------------------------------- /c/src/gl_surface/impl.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_GL_SURFACE_IMPL_H 2 | #define BW_GL_SURFACE_IMPL_H 3 | 4 | #include "../gl_surface.h" 5 | #include "../window.h" 6 | 7 | 8 | 9 | void bw_GlSurfaceImpl_destroy( bw_Window* window ); 10 | 11 | bw_GlSurfaceImpl bw_GlSurfaceImpl_new( bw_Window* window ); 12 | 13 | 14 | 15 | #endif//BW_GL_SURFACE_IMPL_H -------------------------------------------------------------------------------- /c/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bindings; 2 | 3 | use std::{error::Error, ffi::CStr, fmt, ptr, slice, str}; 4 | 5 | pub use crate::bindings::*; 6 | 7 | /************************************************************** 8 | * Implementations for C structs that are also useful in Rust * 9 | *************************************************** */ 10 | 11 | impl cbw_CStrSlice { 12 | pub fn empty() -> Self { 13 | Self { 14 | len: 0, 15 | data: ptr::null(), 16 | } 17 | } 18 | } 19 | 20 | impl From<&str> for cbw_CStrSlice { 21 | fn from(string: &str) -> Self { 22 | Self { 23 | data: string.as_bytes().as_ptr() as _, 24 | len: string.len() as _, 25 | } 26 | } 27 | } 28 | 29 | impl<'a> Into<&'a str> for cbw_CStrSlice { 30 | fn into(self) -> &'a str { 31 | let raw: &[u8] = unsafe { slice::from_raw_parts(self.data as _, self.len as _) }; 32 | 33 | #[cfg(debug_assertions)] 34 | return str::from_utf8(raw).expect("Invalid UTF-8"); 35 | #[cfg(not(debug_assertions))] 36 | return unsafe { str::from_utf8_unchecked(raw) }; 37 | } 38 | } 39 | 40 | impl Into for cbw_CStrSlice { 41 | fn into(self) -> String { 42 | let str: &str = self.into(); 43 | 44 | str.to_owned() 45 | } 46 | } 47 | 48 | impl cbw_StrSlice { 49 | pub fn empty() -> Self { 50 | Self { 51 | len: 0, 52 | data: ptr::null_mut(), 53 | } 54 | } 55 | } 56 | 57 | impl<'a> Into<&'a str> for cbw_StrSlice { 58 | fn into(self) -> &'a str { 59 | let raw: &[u8] = unsafe { slice::from_raw_parts(self.data as _, self.len as _) }; 60 | 61 | #[cfg(debug_assertions)] 62 | return str::from_utf8(raw).expect("Invalid UTF-8"); 63 | #[cfg(not(debug_assertions))] 64 | return unsafe { str::from_utf8_unchecked(raw) }; 65 | } 66 | } 67 | 68 | impl Into for cbw_StrSlice { 69 | fn into(self) -> String { 70 | let str: &str = self.into(); 71 | 72 | str.to_owned() 73 | } 74 | } 75 | 76 | impl Error for cbw_Err { 77 | fn source(&self) -> Option<&(dyn Error + 'static)> { None } 78 | } 79 | 80 | impl fmt::Display for cbw_Err { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 | unsafe { 83 | let msg_ptr = (self.alloc_message.unwrap())(self.code, self.data); 84 | let cstr = CStr::from_ptr(msg_ptr); 85 | write!(f, "{}", cstr.to_string_lossy().as_ref()) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /c/src/posix.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_POSIX_H 2 | #define BW_POSIX_H 3 | 4 | #include "assert.h" 5 | 6 | #include 7 | 8 | 9 | 10 | #define BW_POSIX_ASSERT_SUCCESS( ERRNO ) \ 11 | BW_ASSERT( ERRNO == 0, "[posix error %i] %s", ERRNO, strerror( ERRNO ) ) 12 | 13 | 14 | 15 | #endif//BW_POSIX_H -------------------------------------------------------------------------------- /c/src/string.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | #include 4 | #include 5 | 6 | 7 | 8 | char* bw_string_copyAsNewCstr( bw_CStrSlice str ) { 9 | char* new_str = (char*)malloc( str.len + 1 ); 10 | memcpy( new_str, str.data, str.len ); 11 | new_str[ str.len ] = '\0'; 12 | return new_str; 13 | } 14 | 15 | void bw_string_free(bw_StrSlice slice) { 16 | free(slice.data); 17 | } 18 | 19 | void bw_string_freeC(bw_CStrSlice slice) { 20 | free((void*)slice.data); 21 | } 22 | 23 | void bw_string_freeCstr( char* str ) { 24 | free( str ); 25 | } 26 | -------------------------------------------------------------------------------- /c/src/string.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_STRING_H 2 | #define BW_STRING_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #ifndef BW_BINDGEN 9 | #include 10 | #else 11 | #ifdef BW_WIN32 12 | typedef unsigned long long size_t; 13 | #else 14 | #include 15 | #endif 16 | #endif 17 | 18 | 19 | /// A 'string slice' 20 | /// Points to a mutable, non-zero-terminated, UTF-8 encoded string. 21 | /// Using rust's string's memory layout. 22 | typedef struct bw_StrSlice { 23 | size_t len; 24 | char* data; 25 | } bw_StrSlice; 26 | 27 | /// A 'const string slice' 28 | /// Points to a immutable, non-zero-terminated, UTF-8 encoded string. 29 | /// Using rust's string's memory layout. 30 | typedef struct bw_CStrSlice { 31 | size_t len; 32 | const char* data; 33 | } bw_CStrSlice; 34 | 35 | 36 | 37 | /// Copies the string from the given `bw_CStrSlice` to a C compatible, nul-terminated string. 38 | char* bw_string_copyAsNewCstr( bw_CStrSlice str ); 39 | 40 | /// Frees the string allocated with any of the functions of this module. 41 | void bw_string_freeCstr( char* str ); 42 | void bw_string_free(bw_StrSlice str); 43 | void bw_string_freeC(bw_CStrSlice str); 44 | 45 | 46 | 47 | #ifdef __cplusplus 48 | } // extern "C" 49 | #endif 50 | 51 | #endif//BW_STRING_H 52 | -------------------------------------------------------------------------------- /c/src/win32.c: -------------------------------------------------------------------------------- 1 | #include "win32.h" 2 | #include "common.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #pragma comment(lib, "User32.lib") 12 | #pragma comment(lib, "OleAut32.lib") 13 | 14 | #ifndef WC_ERR_INVALID_CHARS 15 | #define WC_ERR_INVALID_CHARS 0x80 16 | #endif 17 | 18 | 19 | 20 | char* bw_win32_unhandledHresultMessage( bw_ErrCode code, const void* data ); 21 | char* bw_win32_unknownHresultMessage( bw_ErrCode code, const void* data ); 22 | 23 | 24 | 25 | void bw_win32_print_error( DWORD code ) { 26 | 27 | wchar_t msg[512]; 28 | FormatMessageW( 29 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 30 | NULL, 31 | code, 32 | 0, 33 | msg, 34 | 512, 35 | NULL 36 | ); 37 | 38 | fwprintf(stderr, L"win32 error [%i]: %s\n", code, msg); 39 | 40 | // TODO: Print stack trace 41 | } 42 | 43 | void bw_win32_print_hresult_error( HRESULT hresult ) { 44 | 45 | WCHAR* message = 0; 46 | 47 | if ( FormatMessageW( 48 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 49 | NULL, 50 | hresult, 51 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 52 | (LPWSTR)&message, 53 | 0, NULL ) 54 | ) { 55 | fwprintf( stderr, L"win32 hresult assertion [%x]: %s\n", hresult, message ); 56 | 57 | free( message ); 58 | } 59 | else { 60 | fprintf( stderr, "win32 hresult assertion with unknown message: %x\n", hresult ); 61 | } 62 | } 63 | 64 | WCHAR* bw_win32_copyAsNewWstr( bw_CStrSlice slice ) { 65 | 66 | DWORD size = MultiByteToWideChar( CP_UTF8, 0, slice.data, (int)slice.len, 0, 0 ); 67 | 68 | WCHAR* str = (WCHAR*)calloc( size + 1, sizeof(WCHAR) ); 69 | if (str == 0) { 70 | return 0; 71 | } 72 | 73 | MultiByteToWideChar( CP_UTF8, 0, slice.data, (int)slice.len, str, size ); 74 | str[size] = L'\0'; 75 | 76 | return str; 77 | } 78 | 79 | char* bw_win32_copyAsNewCstr( bw_CStrSlice str ) { 80 | char* new_str = (char*)malloc( str.len + 1 ); 81 | 82 | memcpy( new_str, str.data, str.len ); 83 | new_str[ str.len ] = '\0'; 84 | 85 | return new_str; 86 | } 87 | 88 | char* bw_win32_copyWstrAsNewCstr( const WCHAR* str ) { 89 | 90 | size_t len = wcslen( str ); 91 | DWORD size_needed = WideCharToMultiByte( CP_UTF8, WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS, str, (int)len, 0, 0, 0, 0 ); 92 | 93 | char* cstr = (char*)calloc( size_needed + 1, sizeof( char ) ); 94 | WideCharToMultiByte( CP_UTF8, WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS, str, (int)len, cstr, size_needed, 0, 0 ); 95 | cstr[ size_needed ] = '\0'; 96 | 97 | return cstr; 98 | } 99 | 100 | size_t bw_win32_copyAsNewUtf8Str( const WCHAR* string, char** output ) { 101 | 102 | size_t len = WideCharToMultiByte( CP_UTF8, 0, string, -1, 0, 0, 0, 0 ); 103 | if ( len == 0 ) BW_WIN32_PANIC_LAST_ERROR; 104 | 105 | *output = (char*)malloc( len ); 106 | 107 | if ( !WideCharToMultiByte( 108 | CP_UTF8, 109 | WC_ERR_INVALID_CHARS, 110 | (LPCWCH)string, 111 | -1, 112 | *output, 113 | (int)len, 114 | 0, 115 | 0 116 | ) ) { 117 | BW_WIN32_PANIC_LAST_ERROR; 118 | } 119 | 120 | return len; 121 | } 122 | 123 | bw_Err bw_win32_unhandledHresult( HRESULT hResult ) { 124 | 125 | WCHAR* message; 126 | 127 | if ( FACILITY_WINDOWS == HRESULT_FACILITY( hResult ) ) 128 | hResult = HRESULT_CODE( hResult ); 129 | 130 | if( FormatMessageW( 131 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 132 | NULL, 133 | hResult, 134 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 135 | (LPWSTR)&message, 136 | 0, NULL ) != 0 137 | ) { 138 | 139 | bw_Err error; 140 | error.code = (bw_ErrCode)hResult; 141 | error.data = message; 142 | error.alloc_message = bw_win32_unhandledHresultMessage; 143 | return error; 144 | } 145 | else { 146 | bw_Err error; 147 | error.code = (bw_ErrCode)hResult; 148 | error.data = 0; // No data is used here 149 | error.alloc_message = bw_win32_unknownHresultMessage; 150 | return error; 151 | } 152 | } 153 | 154 | 155 | char* bw_win32_unhandledHresultMessage( bw_ErrCode code, const void* data ) { 156 | 157 | char* hresult_msg = bw_win32_copyWstrAsNewCstr( (WCHAR*)data ); 158 | 159 | char* message = (char*)calloc( strlen("Unhandled win32 hresult error [0x00000000]: ") + strlen( hresult_msg ) + 9, sizeof( char ) ); 160 | sprintf( message, "Unhandled win32 hresult error [0x%x]: %s", code, hresult_msg ); 161 | 162 | free( hresult_msg ); 163 | return message; 164 | } 165 | 166 | char* bw_win32_unknownHresultMessage( bw_ErrCode code, const void* _ ) { 167 | UNUSED(_); 168 | 169 | char* message = (char*)calloc( strlen("Unknown win32 hresult error: 0x00000000") + 9, sizeof( char ) ); 170 | 171 | sprintf( message, "Unknown win32 hresult error: 0x%x", code ); 172 | 173 | return message; 174 | } 175 | -------------------------------------------------------------------------------- /c/src/win32.h: -------------------------------------------------------------------------------- 1 | #ifndef BROWSER_WINDOW_WIN32_H 2 | #define BROWSER_WINDOW_WIN32_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | 10 | #include "err.h" 11 | #include "string.h" 12 | 13 | 14 | // Some definitions as defined in windef.h: 15 | // https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types 16 | // Because including windef.h with MinGW can cause some issues 17 | typedef unsigned char BYTE; 18 | typedef unsigned long DWORD; 19 | typedef long HRESULT; 20 | typedef wchar_t WCHAR; 21 | 22 | 23 | #define BW_WIN32_PANIC_LAST_ERROR \ 24 | { bw_win32_print_error( GetLastError() ); assert(0); } 25 | #define BW_WIN32_PANIC_HRESULT( HRESULT ) \ 26 | { bw_win32_print_hresult_error( HRESULT ); assert(0); } 27 | #define BW_WIN32_ASSERT_SUCCESS \ 28 | { DWORD err = GetLastError(); if ( err != 0 ) { bw_win32_print_error( err ); assert(0); } } 29 | 30 | 31 | 32 | char* bw_win32_copyWstrAsNewCstr( const WCHAR* str ); 33 | char* bw_win32_copyAsNewCstr( bw_CStrSlice str ); 34 | 35 | size_t bw_win32_copyAsNewUtf8Str( const WCHAR* string, char** output ); 36 | 37 | /// Copies the given string into a newly allocated BSTR (widestring). 38 | /// Make sure to free it with SysFreeString. 39 | WCHAR* bw_win32_copyAsNewWstr( bw_CStrSlice str ); 40 | 41 | void bw_win32_print_error( DWORD code ); 42 | 43 | void bw_win32_print_hresult_error( HRESULT hresult ); 44 | 45 | void bw_win32_copyWcharIntoSlice( const WCHAR* input, bw_StrSlice output ); 46 | 47 | bw_Err bw_win32_unhandledHresult( HRESULT hresult ); 48 | 49 | 50 | 51 | #ifdef __cplusplus 52 | } // extern "C" 53 | #endif 54 | 55 | #endif//BROWSER_WINDOW_WIN32_H 56 | -------------------------------------------------------------------------------- /c/src/window.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_WINDOW_H 2 | #define BW_WINDOW_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "application.h" 9 | #include "common.h" 10 | #include "string.h" 11 | 12 | #include 13 | #include 14 | 15 | 16 | 17 | typedef struct bw_Window bw_Window; 18 | 19 | typedef struct bw_WindowOptions { 20 | bool borders; 21 | bool minimizable; 22 | bool resizable; 23 | } bw_WindowOptions; 24 | 25 | typedef void (*bw_WindowDispatchFn)( bw_Window* window, void* data ); 26 | typedef struct bw_WindowDispatchData bw_WindowDispatchData; 27 | 28 | 29 | #if defined(BW_WIN32) 30 | #include "window/win32.h" 31 | #elif defined(BW_GTK) 32 | #include "window/gtk.h" 33 | #elif defined(BW_CEF_WINDOW) 34 | #include "window/cef.h" 35 | #else 36 | typedef struct {} bw_WindowImpl; 37 | #endif 38 | 39 | 40 | typedef struct bw_BrowserWindow bw_BrowserWindow; 41 | 42 | struct bw_Window { 43 | bw_Application* app; // The application handle that this window belongs to. 44 | const bw_Window* parent; // An optional window that acts as the parent to this window. If the parent gets destroyed, children will get destroyed too. 45 | bool closed; // Whether or not the window has been closed already 46 | bool dropped; // Whether or not the window may be destroyed when it is actually closed 47 | void* user_data; // TODO: Put the user_data ptr on bw_BrowserWindow 48 | bw_BrowserWindow* browser; 49 | bw_WindowImpl impl; // Data for the implementation of the window 50 | }; 51 | 52 | 53 | /// Hides the window and frees the user data 54 | void bw_Window_close(bw_Window* window); 55 | 56 | /// Invalidates the window handle. 57 | void bw_Window_free(bw_Window* window); 58 | 59 | /// Frees the user data that was attached to this window. 60 | void bw_Window_freeUserData(bw_Window* window); 61 | 62 | /// Gets the width and height of the usable area inside the window. 63 | bw_Dims2D bw_Window_getContentDimensions( bw_Window* window ); 64 | 65 | /// Gets the opacity of the window as a value from 0 to 255. 66 | uint8_t bw_Window_getOpacity( bw_Window* window ); 67 | 68 | /// Gets the X and Y coordinates of the window position relative to the desktop screen. 69 | bw_Pos2D bw_Window_getPosition( bw_Window* window ); 70 | 71 | /// Copies as many bytes into `title` that fit in there. 72 | /// Returns the number of characters the title actually has. 73 | size_t bw_Window_getTitle( bw_Window* window, char** title ); 74 | 75 | /// Gets the width and height of the window including the title bar and borders. 76 | bw_Dims2D bw_Window_getWindowDimensions( bw_Window* window ); 77 | 78 | // Makes the window invisible and inaccessible from the user. 79 | void bw_Window_hide( bw_Window* window ); 80 | 81 | /// Returns whether or not the window is not hidden. 82 | /// `bw_Window_show` and `bw_Window_hide` change the visibility. 83 | bool bw_Window_isVisible( const bw_Window* window ); 84 | 85 | /// Creates a new (empty) window 86 | /// The returned pointer is a handler for the window. 87 | /// bw_Window_drop needs to be called on it after it is done being used, 88 | /// otherwise the window is never actually destroyed and memory leakes happen. 89 | bw_Window* bw_Window_new( 90 | bw_Application* app, 91 | const bw_Window* parent, 92 | bw_CStrSlice _title, 93 | int width, int height, 94 | const bw_WindowOptions* options 95 | ); 96 | 97 | 98 | void bw_Window_setContentDimensions( bw_Window* window, bw_Dims2D dimensions ); 99 | 100 | void bw_Window_setOpacity( bw_Window* window, uint8_t opacity ); 101 | 102 | void bw_Window_setPosition( bw_Window* window, bw_Pos2D position ); 103 | 104 | void bw_Window_setUserData(bw_Window* bw, void* user_data); 105 | 106 | /// Applies the given title; 107 | void bw_Window_setTitle( bw_Window* window, bw_CStrSlice title ); 108 | 109 | void bw_Window_setWindowDimensions( bw_Window* window, bw_Dims2D dimensions ); 110 | 111 | /// Shows the window if it was previously hidden 112 | /// Is generally called after window creation. 113 | void bw_Window_show( bw_Window* window ); 114 | 115 | void bw_Window_triggerClose( bw_Window* window ); 116 | 117 | 118 | 119 | void _bw_Window_onResize( const bw_Window* window, unsigned int width, unsigned int height ); 120 | 121 | 122 | 123 | #ifdef __cplusplus 124 | } // extern "C" 125 | #endif 126 | 127 | #endif//BW_WINDOW_H 128 | -------------------------------------------------------------------------------- /c/src/window/cef.cpp: -------------------------------------------------------------------------------- 1 | #include "../window.h" 2 | 3 | #include "../cef/util.hpp" 4 | #include "../common.h" 5 | 6 | #include 7 | #include 8 | 9 | 10 | 11 | class MyWindowDelegate : public CefWindowDelegate { 12 | bw_Window* window; 13 | bw_WindowOptions options; 14 | 15 | public: 16 | MyWindowDelegate( bw_Window* window, const bw_WindowOptions& options ) : window(window), options(options) {} 17 | 18 | bool CanClose( CefRefPtr window ) override { 19 | UNUSED( window ); 20 | return true; 21 | } 22 | 23 | bool CanMaximize( CefRefPtr window ) override { 24 | UNUSED( window ); 25 | return true; 26 | } 27 | 28 | bool CanMinimize( CefRefPtr window ) override { 29 | UNUSED( window ); 30 | return this->options.minimizable; 31 | } 32 | 33 | bool CanResize( CefRefPtr window ) override { 34 | UNUSED( window ); 35 | return this->options.resizable; 36 | } 37 | 38 | CefRefPtr GetParentWindow( CefRefPtr window, bool* is_menu, bool* can_activate_menu ) override { 39 | UNUSED( window ); 40 | UNUSED( is_menu ); 41 | UNUSED( can_activate_menu ); 42 | return nullptr; 43 | } 44 | 45 | bool IsFrameless( CefRefPtr window ) override { 46 | UNUSED( window ); 47 | return !this->options.borders; 48 | } 49 | 50 | bool OnAccelerator( CefRefPtr window, int command_id ) override { 51 | UNUSED( window ); 52 | UNUSED( command_id ); 53 | return false; 54 | } 55 | 56 | bool OnKeyEvent( CefRefPtr window, const CefKeyEvent& event ) override { 57 | UNUSED( window ); 58 | return false; 59 | } 60 | 61 | void OnWindowCreated( CefRefPtr window ) override { 62 | UNUSED( window ); 63 | } 64 | 65 | void OnWindowDestroyed( CefRefPtr window ) override { 66 | UNUSED( window ); 67 | 68 | bw_Window_freeUserData(this->window); 69 | } 70 | 71 | protected: 72 | IMPLEMENT_REFCOUNTING(MyWindowDelegate); 73 | }; 74 | 75 | 76 | 77 | // Opacity is not supported with CEF's window API. 78 | uint8_t bw_Window_getOpacity( bw_Window* window ) { 79 | return 255; 80 | } 81 | 82 | void bw_Window_setOpacity( bw_Window* window, uint8_t opacity ) { 83 | UNUSED( window ); 84 | UNUSED( opacity ); 85 | } 86 | 87 | extern "C" bw_WindowImpl bw_WindowImpl_new( 88 | bw_Window* _window, 89 | bw_CStrSlice _title, 90 | int width, int height, 91 | const bw_WindowOptions* options 92 | ) { 93 | UNUSED( _window ); 94 | 95 | CefRefPtr cef_window_options( new MyWindowDelegate( _window, *options ) ); 96 | CefRefPtr window = CefWindow::CreateTopLevelWindow( cef_window_options ); 97 | 98 | window->SetTitle( bw_cef_copyFromStrSlice( _title ) ); 99 | 100 | CefSize size( width, height ); 101 | window->SetSize( size ); 102 | 103 | bw_WindowImpl impl; 104 | impl.handle_ptr = new CefRefPtr( window ); 105 | return impl; 106 | } 107 | 108 | extern "C" void bw_WindowImpl_close( bw_WindowImpl* window ) { 109 | auto window_ptr = (CefRefPtr*)window->handle_ptr; 110 | (*window_ptr)->Close(); 111 | delete window_ptr; 112 | } 113 | 114 | extern "C" void bw_WindowImpl_hide( bw_WindowImpl* window ) { 115 | (*(CefRefPtr*)window->handle_ptr)->Hide(); 116 | } 117 | 118 | extern "C" void bw_WindowImpl_show( bw_WindowImpl* window ) { 119 | (*(CefRefPtr*)window->handle_ptr)->Show(); 120 | } 121 | 122 | 123 | 124 | bw_Dims2D bw_Window_getContentDimensions( bw_Window* window ) { 125 | CefRect rect = (*(CefRefPtr*)window->impl.handle_ptr)->GetClientAreaBoundsInScreen(); 126 | 127 | bw_Dims2D dims; 128 | dims.width = rect.width; 129 | dims.height = rect.height; 130 | 131 | return dims; 132 | } 133 | 134 | bw_Pos2D bw_Window_getPosition( bw_Window* window ) { 135 | CefPoint p = (*(CefRefPtr*)window->impl.handle_ptr)->GetPosition(); 136 | 137 | bw_Pos2D pos; 138 | pos.x = p.x; 139 | pos.y = p.y; 140 | 141 | return pos; 142 | } 143 | 144 | size_t bw_Window_getTitle( bw_Window* window, char** title ) { 145 | CefString cef_title = (*(CefRefPtr*)window->impl.handle_ptr)->GetTitle(); 146 | 147 | return bw_cef_copyToCstr( cef_title, title ); 148 | } 149 | 150 | bw_Dims2D bw_Window_getWindowDimensions( bw_Window* window ) { 151 | // FIXME: This is the same as getContentDimensions... 152 | CefRect rect = (*(CefRefPtr*)window->impl.handle_ptr)->GetBounds(); 153 | 154 | bw_Dims2D dims; 155 | dims.width = rect.width; 156 | dims.height = rect.height; 157 | 158 | return dims; 159 | } 160 | 161 | void bw_Window_setContentDimensions( bw_Window* window, bw_Dims2D dims ) { 162 | // FIXME: This doesn't work quite yet... 163 | CefRefPtr browser_view = (*(CefRefPtr*)window->impl.handle_ptr)->GetChildViewAt(0); 164 | 165 | CefRect rect(0, 0, dims.width, dims.height); 166 | browser_view->SetBounds(rect); 167 | (*(CefRefPtr*)window->impl.handle_ptr)->Layout(); 168 | } 169 | 170 | void bw_Window_setPosition( bw_Window* window, bw_Pos2D position ) { 171 | CefPoint point(position.x, position.y); 172 | 173 | (*(CefRefPtr*)window->impl.handle_ptr)->SetPosition(point); 174 | } 175 | 176 | void bw_Window_setTitle( bw_Window* window, bw_CStrSlice _title ) { 177 | CefString title = bw_cef_copyFromStrSlice( _title ); 178 | 179 | (*(CefRefPtr*)window->impl.handle_ptr)->SetTitle(title); 180 | } 181 | 182 | void bw_Window_setWindowDimensions( bw_Window* window, bw_Dims2D dims ) { 183 | CefRect bounds( 0, 0, dims.width, dims.height ); 184 | 185 | (*(CefRefPtr*)window->impl.handle_ptr)->SetBounds( bounds ); 186 | } -------------------------------------------------------------------------------- /c/src/window/cef.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_WINDOW_CEF_H 2 | #define BW_WINDOW_CEF_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | 10 | typedef struct { 11 | void* handle_ptr; 12 | } bw_WindowImpl; 13 | 14 | 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif//BW_WINDOW_CEF_H 21 | -------------------------------------------------------------------------------- /c/src/window/common.c: -------------------------------------------------------------------------------- 1 | #include "../common.h" 2 | #include "../window.h" 3 | 4 | #include "impl.h" 5 | 6 | #include 7 | #include 8 | 9 | 10 | 11 | void bw_Window_close(bw_Window* window) { 12 | if (window->user_data != NULL) { 13 | bw_Window_freeUserData(window); 14 | } 15 | bw_WindowImpl_close(&window->impl); 16 | } 17 | 18 | void bw_Window_free( bw_Window* window ) { 19 | 20 | // Free the memory 21 | bw_Application* app = window->app; 22 | free( window ); 23 | // Decrease the window counter and exit if we are done and all windows are closed. 24 | app->windows_alive -= 1; 25 | if ( app->windows_alive == 0 ) 26 | if (app->is_done) { 27 | bw_Application_exit( app, 0 ); } 28 | } 29 | 30 | 31 | 32 | const bw_Application* bw_Window_getApp( bw_Window* window ) { 33 | return window->app; 34 | } 35 | 36 | void bw_Window_hide( bw_Window* window ) { 37 | window->closed = true; 38 | 39 | bw_WindowImpl_hide( &window->impl ); 40 | } 41 | 42 | bool bw_Window_isVisible( const bw_Window* window ) { 43 | return !window->closed; 44 | } 45 | 46 | bw_Window* bw_Window_new( 47 | bw_Application* app, 48 | const bw_Window* parent, 49 | bw_CStrSlice title, 50 | int width, int height, 51 | const bw_WindowOptions* options 52 | ) { 53 | bw_Application_assertCorrectThread( app ); 54 | 55 | bw_Window* window = (bw_Window*)malloc( sizeof(bw_Window) ); 56 | 57 | window->app = app; 58 | window->parent = parent; 59 | window->closed = true; // Windows start out hidden to the user 60 | window->dropped = false; 61 | window->user_data = NULL; 62 | window->browser = NULL; 63 | 64 | window->impl = bw_WindowImpl_new( window, title, width, height, options ); 65 | 66 | app->windows_alive += 1; 67 | 68 | return window; 69 | } 70 | 71 | void bw_Window_show( bw_Window* window ) { 72 | window->closed = false; 73 | 74 | bw_WindowImpl_show( &window->impl ); 75 | } 76 | -------------------------------------------------------------------------------- /c/src/window/gtk.c: -------------------------------------------------------------------------------- 1 | #include "../window.h" 2 | 3 | #include "../common.h" 4 | 5 | 6 | 7 | gboolean _bw_WindowGtk_closeHandler( GtkWidget* handle, gpointer data ); 8 | gboolean _bw_WindowGtk_stateHandler( GtkWidget *widget, GdkEventWindowState *event, gpointer user_data ); 9 | void _bw_WindowGtk_onSizeAllocate( GtkWidget* handle, GdkRectangle* alloc, gpointer data ); 10 | 11 | 12 | 13 | uint8_t bw_Window_getOpacity( bw_Window* window ) { 14 | double fraction = gtk_widget_get_opacity( window->impl.handle ); 15 | 16 | return (uint8_t)(fraction * 255); 17 | } 18 | 19 | void bw_Window_setOpacity( bw_Window* window, uint8_t opacity ) { 20 | gtk_widget_set_opacity( window->impl.handle, (double)opacity / 255.0 ); 21 | } 22 | 23 | bw_WindowImpl bw_WindowImpl_new( 24 | const bw_Window* window, 25 | bw_CStrSlice _title, 26 | int width, int height, 27 | const bw_WindowOptions* options 28 | ) { 29 | //GtkWidget* gtk_handle = gtk_application_window_new( window->app->impl.handle ); 30 | GtkWidget* gtk_handle = gtk_window_new( GTK_WINDOW_TOPLEVEL ); 31 | 32 | gtk_widget_hide_on_delete( gtk_handle ); 33 | 34 | if ( window->parent != 0 ) { 35 | gtk_widget_set_parent_window( gtk_handle, GDK_WINDOW( window->parent->impl.handle ) ); 36 | gtk_window_set_destroy_with_parent( GTK_WINDOW(gtk_handle), FALSE ); 37 | } 38 | 39 | // Title 40 | // TODO: Convert c string to utf8 string 41 | gchar* title = bw_string_copyAsNewCstr( _title ); 42 | gtk_window_set_title( GTK_WINDOW(gtk_handle), title ); 43 | free( title ); 44 | 45 | if ( width != -1 || height != -1 ) { 46 | if ( width < 1 ) 47 | width = 800; 48 | if ( height < 1 ) 49 | height = 600; 50 | 51 | // Width and height 52 | gtk_window_resize( GTK_WINDOW(gtk_handle), width, height ); 53 | } 54 | 55 | gtk_window_set_resizable( GTK_WINDOW(gtk_handle), options->resizable ); 56 | 57 | // If both not minimizable and not resizable, make it a dialog 58 | //gtk_window_set_type_hint( GTK_WINDOW(gtk_handle), GDK_WINDOW_TYPE_HINT_DIALOG ); 59 | 60 | g_signal_connect( gtk_handle, "window-state-event", G_CALLBACK( _bw_WindowGtk_stateHandler ), (gpointer)window ); 61 | //g_signal_connect( gtk_handle, "destroy-event", G_CALLBACK( _bw_WindowGtk_closeHandler ), (gpointer)window ); 62 | g_signal_connect( gtk_handle, "size-allocate", G_CALLBACK( _bw_WindowGtk_onSizeAllocate ), (gpointer)window ); 63 | 64 | // FIXME: Is this really necessary or not?: 65 | gtk_application_add_window( window->app->impl.handle, GTK_WINDOW(gtk_handle) ); 66 | 67 | gtk_widget_realize( gtk_handle ); 68 | 69 | bw_WindowImpl impl; 70 | impl.handle = gtk_handle; 71 | return impl; 72 | } 73 | 74 | void bw_WindowImpl_destroy( bw_WindowImpl* window ) { 75 | gdk_window_destroy( gtk_widget_get_window( window->handle ) ); 76 | } 77 | 78 | void bw_WindowImpl_hide( bw_WindowImpl* window ) { 79 | gdk_window_hide( gtk_widget_get_window( window->handle ) ); 80 | } 81 | 82 | void bw_WindowImpl_show( bw_WindowImpl* window ) { 83 | gdk_window_show( gtk_widget_get_window( window->handle ) ); 84 | } 85 | 86 | 87 | 88 | void _bw_WindowGtk_onSizeAllocate( GtkWidget* handle, GdkRectangle* alloc, gpointer user_data ) { 89 | bw_Window* window = (bw_Window*)user_data; 90 | 91 | if ( window->callbacks.on_resize != 0 ) 92 | window->callbacks.on_resize( window, alloc->width, alloc->height ); 93 | } 94 | 95 | gboolean _bw_WindowGtk_stateHandler( GtkWidget *widget, GdkEventWindowState *event, gpointer user_data ) { 96 | bw_Window* window = (bw_Window*)user_data; 97 | 98 | // If iconified 99 | if ( event->new_window_state & GDK_WINDOW_STATE_ICONIFIED ) { 100 | 101 | // If window is not minimizable, deiconify 102 | if ( !window->impl.minimizable ) { 103 | return FALSE; 104 | gtk_window_deiconify( GTK_WINDOW( widget ) ); 105 | } 106 | } 107 | 108 | return TRUE; 109 | } 110 | 111 | gboolean _bw_WindowGtk_closeHandler( GtkWidget* handle, gpointer data ) { 112 | UNUSED(handle); 113 | 114 | bw_Window* window = (bw_Window*)data; 115 | 116 | bw_WindowImpl_hide( &window->impl ); 117 | return FALSE; 118 | } 119 | -------------------------------------------------------------------------------- /c/src/window/gtk.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_WINDOW_GTK_H 2 | #define BW_WINDOW_GTK_H 3 | 4 | #include 5 | 6 | 7 | 8 | typedef struct { 9 | GtkWidget* handle; 10 | gboolean minimizable; 11 | } bw_WindowImpl; 12 | 13 | 14 | 15 | 16 | #endif//BW_WINDOW_GTK_H 17 | -------------------------------------------------------------------------------- /c/src/window/impl.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_WINDOW_IMPL_H 2 | #define BW_WINDOW_IMPL_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | bw_WindowImpl bw_WindowImpl_new( 10 | bw_Window* window, 11 | bw_CStrSlice _title, 12 | int width, int height, 13 | const bw_WindowOptions* options 14 | ); 15 | 16 | void bw_WindowImpl_close( bw_WindowImpl* window ); 17 | void bw_WindowImpl_hide( bw_WindowImpl* window ); 18 | void bw_WindowImpl_show( bw_WindowImpl* window ); 19 | 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif//BW_WINDOW_IMPL_H 26 | -------------------------------------------------------------------------------- /c/src/window/win32.h: -------------------------------------------------------------------------------- 1 | #ifndef BW_WINDOW_WIN32_H 2 | #define BW_WINDOW_WIN32_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "../win32.h" 9 | #include "../bool.h" 10 | 11 | 12 | typedef struct bw_Window bw_Window; 13 | typedef void (*bw_WindowDispatchFn)( bw_Window* window, void* data ); 14 | 15 | 16 | struct bw_WindowDispatchData { 17 | bw_WindowDispatchFn func; 18 | bw_Window* window; 19 | void* data; 20 | 21 | }; 22 | 23 | typedef struct { 24 | void* handle; 25 | BOOL closed; 26 | DWORD style; 27 | BYTE opacity; 28 | } bw_WindowImpl; 29 | 30 | 31 | void bw_WindowWin32_onResize(bw_Window*, int left, int right, int top, int bottom); 32 | 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | 38 | #endif//BW_WINDOW_WIN32_H 39 | -------------------------------------------------------------------------------- /c/update_bindgen_backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This invokes the build script in a way that regenerates the bindgen file and places it in ./bindgen_backup.rs 4 | # This file will be then be present in docs.rs' build environment. 5 | 6 | env UPDATE_BINDGEN_BACKUP=1 cargo build -------------------------------------------------------------------------------- /docs/GETTING-STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | _BrowserWindow_ needs to be build with one of the following browser frameworks: [CEF](https://bitbucket.org/chromiumembedded/cef/wiki/Home), [WebkitGTK](https://www.webkit.org/) or [Edge WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/?form=MA13LH). They require either the `cef`, `webkitgtk` or `edge2` feature to be set. 4 | 5 | ## Picking the right browser framework 6 | 7 | Here are the pros and cons of each browser framework. Choose wisely: 8 | 9 | ### CEF 10 | 11 | *Pros:* 12 | * Is available on all major platforms: Windows, MacOS, Linux (although MacOS support in _BrowserWindow_ needs some work). 13 | If you want the exact same behavior of your app on all these platforms, CEF is recommended. 14 | * Supports the cookie API. 15 | * Supports the most event types. 16 | 17 | *Cons:* 18 | * Can be a pain to set up correctly; requires a lot of files to be present for the executable & compilation (especially on Windows) needs some extra care to get it done correctly. Although, there are scripts in this repository to do it for you. 19 | * No option to link statically & generally not available through package managers. 20 | 21 | ### WebkitGTK 22 | 23 | *Pros:* 24 | * Generally easily installed on Unix-like systems; a lot of linux distros have a package for it. 25 | 26 | *Cons:* 27 | * Compiling WebkitGTK or GTK for Windows is not supported. 28 | * Homebrew package for WebkitGTK seems to be unmaintained. 29 | * Static linking is also not really supported for GTK. 30 | 31 | ### Edge WebView2 32 | 33 | *Pro:* 34 | * Preinstalled on Windows 11 35 | * Can be statically linked to when using the `*-pc-windows-msvc` toolchain. 36 | * Can be used to cross-compile for Windows. 37 | 38 | *Cons:* 39 | * Not cross-platform at all. 40 | * The framework is not open source, which might be problematic for those concerned about privacy. 41 | 42 | ## Set up Bindgen 43 | 44 | _BrowserWindow_ uses Bindgen, which needs some things to be set up on your system. 45 | This is explained [here](https://rust-lang.github.io/rust-bindgen/requirements.html) pretty well. 46 | 47 | ## Set up WebkitGTK 48 | 49 | If you're going to use WebkitGTK, a lot of systems have a convenient package for this. If not, just 50 | make sure that `pkg-config` is set up to find all the headers & binaries. 51 | 52 | ### Debian APT 53 | 54 | `apt install libwebkit2gtk-4.1-dev` 55 | 56 | ## Set up Edge WebView2 57 | 58 | If you're going to use the Microsoft Edge WebView2 framework, you need to make sure that the runtime 59 | is installed on your system. 60 | 61 | ### Some notes on cross-compilation 62 | 63 | Cross compilation to the `*-pc-windows-gnu` target works. Just make sure that MinGW is set up to 64 | find the headers of the win32 API. 65 | 66 | Moreover, you need to ship the executable together with the WebView2Loader.dll file. 67 | It can be obtained on non-Windows systems, by installing nuget, and downloading it with: 68 | 69 | `nuget install Microsoft.Web.WebView2` 70 | 71 | It will be installed in the current working directory, and then the .dll file can be located at 72 | `Microsoft.Web.WebView2.*/build/native/`. 73 | 74 | ## Set up CEF 75 | 76 | Keep in mind when you're going to use CEF, that _BrowserWindow_ is written to work for a specific version of CEF, and CEF does release new major versions fairly often. Therefore, it is recommended to 77 | obtain the version that _BrowserWindow_ supports, which currently is v122. Use other versions at 78 | your own risk. 79 | 80 | CEF isn't generally available in package managers, so it needs to be set up manually. Luckily, there are binaries avaiable. You can also build it from source, but that is a whole other beast and it is 81 | not covered by this guide. 82 | 83 | If you want to set up CEF by building it from source, take a look at [this](https://bitbucket.org/chromiumembedded/cef/wiki/MasterBuildQuickStart.md). 84 | However, it will take a lot of effort, time, memory & disk space for the compilation process. 85 | 86 | Otherwise, if you're on linux, here it the TL;DR version on setting up CEF: 87 | 88 | #### Linux 89 | 90 | ``` 91 | git clone https://github.com/bamidev/browser-window 92 | cd browser-window 93 | ./get-cef.sh # Download & compile CEF 94 | export CEF_PATH= ... # Set environment variable 95 | ./setup-cef-files.sh # Put necessary files in target/debug 96 | cargo run --example terminal --features cef # Run example code to test if it works 97 | ``` 98 | 99 | #### Windows 100 | 101 | ``` 102 | git clone https://github.com/bamidev/browser-window 103 | cd browser-window # Download & compile CEF 104 | .\get-cef.ps1 # Set environment variable 105 | ``` 106 | Then, add the printed environment variable to your system environment variables for next time. 107 | ``` 108 | .\setup-cef-files.bat # Put necessary files in target/debug 109 | cargo run --example terminal --features cef # Run example code to test if it works 110 | ``` 111 | 112 | As long as `CEF_PATH` is set correctly on the system you're compiling on, compilation of `browser-window` 113 | should work even if it is a dependency of your crate. 114 | But you still need to copy files (just like `setup-cef-files.sh` does) to your crate's target/debug directory. 115 | 116 | ### Download & Extract 117 | 118 | If you're going to set it up manually, you need to get the binaries first. 119 | You can get the latest prebuilt binaries [here](https://cef-builds.spotifycdn.com/index.html). 120 | The 'minimal' package will be fine. 121 | Once downloaded, you will need to extract it somewhere. 122 | 123 | ### Compilation 124 | 125 | The library itself is already compiled for the binary distribution. However, there is a static 'wrapper' library that still needs to be built. 126 | To do this, first run _cmake_ by running this on the command line from within the extracted folder: 127 | ``` 128 | cmake . 129 | ``` 130 | Keep in mind that currently, right out of the box, CEF seems to miss a symbol when compiled. 131 | This can be solved by defining `DCHECK_ALWAYS_ON` before compiling, which can be straightforward in 132 | Visual Studio. 133 | Otherwise, you can also just add `add_compile_definitions(DCHECK_ALWAYS_ON=1)` to the beginning of 134 | the `CMakeLists.txt` file. 135 | Another solution might be to build the cmake project in debug mode: `cmake -DCMAKE_BUILD_TYPE=Debug .` 136 | 137 | #### Unix-like Systems 138 | 139 | After you have run `cmake`, you can just simply run `make`. This will build the wrapper lib for CEF. 140 | 141 | #### Windows 142 | 143 | A newly generated Visual Studio solution has been generated in the folder. 144 | You should build this solution's Release target with [Visual Studio](https://visualstudio.microsoft.com/vs/). 145 | However, before you do, you need to change one setting in the project's settings. 146 | 147 | Make sure to compile the project as a static lib (.lib). 148 | 149 | Now you can just build the solution. 150 | 151 | ### Environment Variables & Resource Files 152 | 153 | Once you have extracted and compiled everything, we need to let _Browser Window_ know where it can find the header and library files to link to. 154 | If you set environment variable `CEF_PATH` to the directory that you have extracted, Browser Window is able to find them. 155 | Otherwise, `cargo` will fail to build `browser-window`. 156 | 157 | You also need to copy resource and library files (`.so`/`.dll` and `.bin`), so that they are available to the executable. 158 | Running `setup-cef-files.sh` or `setup-cef-files.bat` copies them to the target 159 | folder so that you can just run your compiled binaries normally. 160 | It will also change some permissions that CEF requires you to do. 161 | `setup-cef-files.sh` will use `sudo`, so if you don't have `sudo`, inspect the file and execute the commands yourself. 162 | 163 | For the library files, you could also just add the `Release` folder to your `PATH` environment variable for the `.so`/`.dll` files. 164 | 165 | That's it! 166 | Running `cargo run` should just work now. 167 | 168 | If you encounter any issues, take a look at the [issue diagnosis page](https://github.com/bamilab/browser-window/blob/master/docs/ISSUE-DIAGNOSIS.md). 169 | If that doesn't help, you can submit an issue to the repository. -------------------------------------------------------------------------------- /docs/ISSUE-DIAGNOSIS.md: -------------------------------------------------------------------------------- 1 | # Issue Diagnosis 2 | 3 | If you are encountering issues while using and/or compiling _Browser Window_, this page is here to help you fix them. 4 | If your issues isn't explained in here, please file the issue [here](https://github.com/bamilab/browser-window/issues). 5 | 6 | ## Compilation Errors 7 | 8 | ### Cross-platform 9 | 10 | #### 11 | 12 | ```rustc 13 | undefined reference to `base::cef_subtle::RefCountedThreadSafeBase::~RefCountedThreadSafeBase()' 14 | ``` 15 | 16 | This tends to happen when you compile `libcef_dll_wrapper` in release mode. 17 | For some reason, libcef_dll_wrapper doesn't compile these symbols in release 18 | mode. Therefore, currently only build with debug symbols: 19 | 20 | ``` 21 | make clean 22 | cmake -DCMAKE_BUILD_TYPE=Debug . 23 | make 24 | ``` 25 | 26 | ### Unix-like Systems 27 | 28 | #### Missing "stdlib.h" or "stddef.h" 29 | 30 | ```rustc 31 | ./src/err.h:8:10: fatal error: 'stdlib.h' file not found 32 | ./src/err.h:8:10: fatal error: 'stdlib.h' file not found, err: true 33 | thread 'main' panicked at 'Unable to generate FFI bindings! ... 34 | ``` 35 | 36 | `bindgen` might fail when `clang` isn't properly installed. 37 | To fix this for Debian-like systems, try `apt install libclang-dev`. 38 | 39 | ### Windows 40 | 41 | #### Missing "Windows.h" 42 | 43 | ```rustc 44 | src/application/win32.h:18:10: fatal error: 'Windows.h' file not found 45 | ``` 46 | 47 | This error occurs when the MSVC compiler can't find the Windows SDK headers. 48 | This could mean that you didn't install the Windows SDK. 49 | If you did, it could also mean that MSVC environment variables aren't set properly. 50 | This tends to happen when using `bindgen` (see feature `bindgen`). 51 | 52 | To fix this, call `cargo` from within the 'Cross Tools Command Prompt for VS 20**'. 53 | 54 | If you want to work with another type of console, like that of Msys2 for example, you can do to following. 55 | Run `vcvarsall.bat` (usually located in `C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build`) from the command line with your architecture and SDK version provided as arguments. Run it without arguments for more information. 56 | When this is done, your console should be able to build `browser-window-c`. 57 | But this needs to be done for every console that you want to build _Browser Window_ with, so this is not actually a long-term solution. 58 | 59 | #### error LNK2038 60 | 61 | ```rustc 62 | libcpmt.lib(winapisupp.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease' in libbrowser_window_ffi-d71871b45632c28d.rlib(cef.o) 63 | LINK : warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs; use /NODEFAULTLIB:library 64 | ``` 65 | 66 | This happends when you didn't follow the getting started guide properly. 67 | You need to built CEF with the `/MD` flag. 68 | 69 | ### Other C++ Errors 70 | 71 | Sometimes you may run into (weird) C++ compilation errors when building _Browser Window_. 72 | If you are using a published version of _Browser Window_, this should generally not happen. 73 | In case it does happen, it is likely to be a incompatibility with the _CEF_ API. 74 | _CEF_ release new major version very regurarely. 75 | 76 | Try using a version of _CEF_ that is stated as being the _latest version known to work_, as stated in the [getting started guide](./GETTING-STARTED.md). 77 | 78 | If that doesn't work, please [submit an issue](https://github.com/bamilab/browser-window/issues). 79 | 80 | ## Runtime Errors 81 | 82 | ### My program executes, but it hangs at the start 83 | 84 | For some unknown reason, when you're using CEF on linux, this issue may sometimes happen. 85 | It may hang at where you're using `Application::initialize`. 86 | The source of this bug remains a mystery: https://www.magpcss.org/ceforum/viewtopic.php?f=6&t=19719 87 | 88 | ### Unable to initialize application 89 | 90 | ``` 91 | thread 'main' panicked at 'Unable to initialize application: c(bw) error: [1] unable to execute CEF process (are all required files located near the executable?)', ... 92 | ``` 93 | 94 | This may be because the library and resource files were not copied to your executable directory. 95 | If this is not done, your program will not get past `Application::initialize`. 96 | Execute `setup-cef-files.sh` or `setup-cef-files.bat` if you're trying to run an example. 97 | Otherwise, check the content of these scripts to get an idea of what files need to be copied. -------------------------------------------------------------------------------- /examples/authentication.rs: -------------------------------------------------------------------------------- 1 | //! This is an example that demonstrates how you can get the session cookie data 2 | //! of a site after logging in. 3 | 4 | use std::time::Duration; 5 | 6 | use browser_window::{application::*, browser::*, prelude::*}; 7 | 8 | fn main() { 9 | let application = 10 | Application::initialize(&ApplicationSettings::default()).expect("unable to initialize"); 11 | let runtime = application.start(); 12 | 13 | runtime.run_async(|app| async move { 14 | let mut bwb = 15 | BrowserWindowBuilder::new(Source::Url("https://github.com/login".to_string())); 16 | //bwb.dev_tools(false) 17 | bwb.size(800, 600); 18 | bwb.title("Log in to Github"); 19 | let bw = bwb.build(&app).await; 20 | bw.show(); 21 | 22 | let cookie_jar = app.cookie_jar().expect("cookies not supported"); 23 | 24 | // Wait until we moved away from the login page 25 | while bw.url() == "" 26 | || bw.url() == "https://github.com/login" 27 | || bw.url() == "https://github.com/session" 28 | { 29 | app.sleep(Duration::from_millis(100)).await; 30 | } 31 | 32 | // Check if logged in 33 | let logged_in_cookie = cookie_jar.find_from_all("logged_in").await; 34 | if logged_in_cookie.is_none() || logged_in_cookie.unwrap().value() != "yes" { 35 | eprintln!("Not logged in."); 36 | } else { 37 | // Get session cookie 38 | let session_cookie = cookie_jar 39 | .find_from_all("_gh_sess") 40 | .await 41 | .expect("session cookie not found"); 42 | let session_id = session_cookie.value(); 43 | // You can use this `session_id` to do anything, like scraping user information. 44 | 45 | eprintln!("Logged in with session ID: {}", session_id); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /examples/northern-lights.txt: -------------------------------------------------------------------------------- 1 | ` : | | | |: || : ` : | |+|: | : : :| . ` . 2 | ` : | :| || |: : ` | | :| : | : |: | . : 3 | .' ': || |: | ' ` || | : | |: : | . ` . :. 4 | `' || | ' | * ` : | | :| |*| : : :| 5 | * * ` | : : | . ` ' :| | :| . : : * :.|| 6 | .` | | | : .:| ` | || | : |: | | || 7 | ' . + ` | : .: . '| | : :| : . |:| || 8 | . . ` *| || : ` | | :| | : |:| | 9 | . . . || |.: * | || : : :||| 10 | . . . * . . ` |||. + + '| ||| . ||` 11 | . * . +:`|! . |||| :.||` 12 | + . ..!|* . | :`||+ |||` 13 | . + : |||` .| :| | | |.| ||` . 14 | * + ' + :|| |` :.+. || || | |:`|| ` 15 | . .||` . ..|| | |: '` `| | |` + 16 | . +++ || !|!: ` :| | 17 | + . . | . `|||.: .|| . . ` 18 | ' `|. . `:||| + ||' ` 19 | __ + * `' `'|. `: 20 | "' `---"""----....____,..^---`^``----.,.___ `. `. . ____,.,- 21 | ___,--'""`---"' ^ ^ ^ ^ """'---,..___ __,..---""' 22 | --"' ^ ``--..,__ 23 | D. Rice 24 | -------------------------------------------------------------------------------- /examples/preview-art.sh: -------------------------------------------------------------------------------- 1 | cat examples/northern-lights.txt 2 | -------------------------------------------------------------------------------- /examples/resources/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Carrois Type Design, Ralph du Carrois (post@carrois.com www.carrois.com), with Reserved Font Name 'Share' 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /examples/resources/main.js: -------------------------------------------------------------------------------- 1 | var cmd_line = "" 2 | var is_executing = false; 3 | var working_dir = "C:\\" 4 | 5 | 6 | 7 | function addCmdChar( char_code ) { 8 | let char = String.fromCharCode( char_code ) 9 | cmd_line += char 10 | 11 | if ( !is_executing ) 12 | print( char ) 13 | } 14 | 15 | function executeCmdLine() { 16 | 17 | // If not executing something, use the command line to execute something 18 | if ( !is_executing ) { 19 | is_executing = true 20 | invoke_extern("exec", cmd_line) 21 | } 22 | // If already executing something, use the line as input 23 | else 24 | invoke_extern("input", cmd_line) 25 | 26 | // Empty the buffer 27 | cmd_line = "" 28 | } 29 | 30 | function initialize( wd ) { 31 | working_dir = wd; 32 | 33 | printPrefix() 34 | } 35 | 36 | function onExecutionEnded() { 37 | printPrefix() 38 | print( cmd_line ) 39 | is_executing = false 40 | } 41 | 42 | function escapeText(text) { 43 | return text 44 | .replaceAll(' ', ' ') 45 | .replaceAll("\r\n", '
') 46 | .replaceAll("\n", '
') 47 | } 48 | 49 | function onOutputReceived( output ) { 50 | let span = document.createElement("span") 51 | span.innerHTML = escapeText(output) 52 | 53 | document.body.firstElementChild.appendChild( span ) 54 | } 55 | 56 | function onErrorOutputReceived( output ) { 57 | 58 | let span = document.createElement("span") 59 | span.setAttribute("class", "stderr") 60 | span.innerHTML = escapeText(output) 61 | 62 | document.body.firstElementChild.appendChild( span ) 63 | } 64 | 65 | function print( text ) { 66 | $('body div').append( text ) 67 | } 68 | 69 | function printPrefix() { 70 | print(working_dir + '$ ') 71 | } 72 | 73 | 74 | 75 | window.onkeypress = e => { 76 | 77 | if ( e.charCode == 0x0D ) { 78 | print("\r\n") 79 | executeCmdLine() 80 | } 81 | else 82 | addCmdChar( e.charCode ) 83 | } -------------------------------------------------------------------------------- /examples/resources/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000; 3 | color: #0F0; 4 | font: 1.2em monospace; 5 | margin-top: 0; 6 | word-break: break-all; 7 | word-wrap: break-word; 8 | white-space: pre-line; 9 | } 10 | 11 | .stderr { 12 | color: #F00; 13 | } -------------------------------------------------------------------------------- /examples/resources/terminal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Example 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/terminal.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | io::prelude::*, 4 | process::{exit, Command, Stdio}, 5 | }; 6 | 7 | use browser_window::{application::*, browser::*, prelude::*}; 8 | use serde_json; 9 | 10 | async fn execute_command(bw: BrowserWindow, line: &str) { 11 | let working_dir = bw 12 | .eval_js("working_dir") 13 | .await 14 | .expect("Unable to obtain working dir from JavaScript!") 15 | .to_string_unenclosed() 16 | .to_string(); 17 | 18 | let cmd = if env::consts::OS == "windows" { 19 | Command::new("cmd") 20 | .arg("/C") 21 | .arg( line ) 22 | .current_dir( working_dir ) 23 | .stdout( Stdio::piped() ) 24 | .stderr( Stdio::piped() ) 25 | //.kill_on_drop(true) 26 | .spawn() 27 | .expect("Command failed to run!") 28 | } else { 29 | Command::new("sh") 30 | .arg("-c") 31 | .arg( line ) 32 | //.current_dir( working_dir ) 33 | .stdout( Stdio::piped() ) 34 | .stderr( Stdio::piped() ) 35 | //.kill_on_drop(true) 36 | .spawn() 37 | .expect("Command failed to run!") 38 | }; 39 | 40 | // Read the output 41 | let mut stdout = cmd.stdout.unwrap(); 42 | let mut stderr = cmd.stderr.unwrap(); 43 | let mut buffer: [u8; 1024] = [0xFF; 1024]; 44 | loop { 45 | let stdout_empty = read_stream(&bw, &mut stdout, &mut buffer, "onOutputReceived"); 46 | let stderr_empty = read_stream(&bw, &mut stderr, &mut buffer, "onErrorOutputReceived"); 47 | 48 | if !stdout_empty && !stderr_empty { 49 | break; 50 | } 51 | } 52 | 53 | // Notify the terminal that it can type commands again 54 | bw.exec_js("onExecutionEnded()"); 55 | } 56 | 57 | fn read_stream( 58 | bw: &BrowserWindowHandle, reader: &mut R, buffer: &mut [u8], js_func: &str, 59 | ) -> bool 60 | where 61 | R: Read, 62 | { 63 | match reader.read(buffer) { 64 | Err(e) => eprintln!("Command error: {}", e), 65 | Ok(read) => { 66 | if read == 0 { 67 | return false; 68 | } 69 | 70 | // Convert to string 71 | let text = JsValue::String(String::from_utf8_lossy(&buffer[0..read]).to_string()); 72 | // Converting `JsValue` to a string automatically formats the value as a 73 | // JavaScript string literal 74 | let js_string = text.to_string(); 75 | 76 | bw.exec_js(&(js_func.to_owned() + "(" + js_string.as_str() + ")")); 77 | } 78 | } 79 | 80 | true 81 | } 82 | 83 | fn main() { 84 | let mut settings = ApplicationSettings::default(); 85 | settings.remote_debugging_port = Some(10000); 86 | let application = match Application::initialize(&settings) { 87 | Err(e) => panic!("Unable to initialize application: {}", e), 88 | Ok(app) => app, 89 | }; 90 | let runtime = application.start(); 91 | 92 | let exit_code = runtime.run_async(|app| async move { 93 | let working_dir = env::current_dir().unwrap(); 94 | let mut html_file = working_dir.clone(); 95 | html_file.push("examples/resources/terminal.html"); 96 | 97 | let mut bwb = BrowserWindowBuilder::new(Source::File(html_file)); 98 | bwb.dev_tools(true); 99 | bwb.size(800, 600); 100 | bwb.title("Terminal Example"); 101 | 102 | let bw = bwb.build_async(&app).await; 103 | 104 | bw.on_message().register_async(|bw, e| async move { 105 | match e.cmd.as_str() { 106 | "exec" => { 107 | // The whole command line is passed one string value. 108 | let cmd_line = e.args[0].to_string_unenclosed(); 109 | 110 | execute_command(bw, &cmd_line.to_string()).await; 111 | } 112 | other => { 113 | eprintln!("Received unsupported command: {}", other); 114 | } 115 | } 116 | }); 117 | bw.window().set_opacity(224); 118 | bw.window().show(); 119 | 120 | // Initialize the script with our working directory. 121 | // Make sure that it is initializes whether document has been loaded already or 122 | // not. 123 | let working_dir_js = serde_json::to_string(working_dir.to_str().unwrap()) 124 | .expect("Invalid working directory characters!"); 125 | match bw 126 | .eval_js(format!("initialize({})", &working_dir_js).as_str()) 127 | .await 128 | { 129 | Err(e) => eprintln!("Javascript Error: {:?}", e), 130 | Ok(_) => {} 131 | }; 132 | }); 133 | 134 | // Return exit code 135 | exit(exit_code); 136 | } 137 | -------------------------------------------------------------------------------- /get-cef.ps1: -------------------------------------------------------------------------------- 1 | # Download CEF archive 2 | $CEF_ARCHIVE = "cef_binary_122.1.12+g6e69d20+chromium-122.0.6261.112_windows64_minimal" 3 | 4 | 5 | $ErrorActionPreference = "Stop" 6 | 7 | 8 | mkdir -f cef 9 | if (!(Test-Path "cef\$CEF_ARCHIVE.tar.bz2")) { 10 | "Downloading CEF..." 11 | curl -o "cef\$CEF_ARCHIVE.tar.bz2.part" "https://cef-builds.spotifycdn.com/$CEF_ARCHIVE.tar.bz2" 12 | mv "cef\$CEF_ARCHIVE.tar.bz2.part" "cef\$CEF_ARCHIVE.tar.bz2" 13 | } 14 | 15 | if (!(Test-Path "cef\$CEF_ARCHIVE")) { 16 | "Unpacking CEF..." 17 | tar -xvf "cef\$CEF_ARCHIVE.tar.bz2" -C cef 18 | } 19 | 20 | "Compiling CEF..." 21 | try { 22 | cd "cef\$CEF_ARCHIVE" 23 | 24 | # Add compilation definitions to the top of the CMakeLists.txt file 25 | if (!(Test-Path "CMakeLists.txt.def")) { 26 | mv CMakeLists.txt CMakeLists.txt.old 27 | Set-Content -Path "CMakeLists.txt.def" -Value "add_compile_definitions(NDEBUG=1 DCHECK_ALWAYS_ON=1)" 28 | Get-Content CMakeLists.txt.def, CMakeLists.txt.old | Set-Content -Path "CMakeLists.txt" 29 | } 30 | 31 | cmake . 32 | cmake --build . --config Release 33 | cmake --build libcef_dll_wrapper --config Release --target libcef_dll_wrapper 34 | } 35 | finally { 36 | cd ..\.. 37 | } 38 | ` 39 | "CEF is ready. Add the following path to with name CEF_PATH to your environment variables:" 40 | "$PWD\cef\$CEF_ARCHIVE" 41 | 42 | $Env:CEF_PATH = "$PWD\cef\$CEF_ARCHIVE" 43 | -------------------------------------------------------------------------------- /get-cef.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Check processor architecture to download the correction version 4 | LINUX_ARCH=`uname -m` 5 | if [ "$LINUX_ARCH" = "x86_64" ]; then 6 | CEF_ARCH="64" 7 | elif [ "$LINUX_ARCH" = "arm" ]; then 8 | CEF_ARCH="arm" 9 | elif [ "$LINUX_ARCH" = "aarch64" ]; then 10 | CEF_ARCH="arm64" 11 | elif [ "$LINUX_ARCH" = "aarch64_be" ]; then 12 | CEF_ARCH="arm64" 13 | elif [ "$LINUX_ARCH" = "armv8b" ]; then 14 | CEF_ARCH="arm64" 15 | elif [ "$LINUX_ARCH" = "armv8l" ]; then 16 | CEF_ARCH="arm64" 17 | else 18 | echo "Your system has a processor architecture that is unsupported by CEF: \"$LINUX_ARCH\"" 19 | exit 1 20 | fi 21 | 22 | # Download CEF archive 23 | CEF_ARCHIVE="cef_binary_122.1.12+g6e69d20+chromium-122.0.6261.112_linux${CEF_ARCH}_minimal" 24 | if [ ! -f /tmp/cef.tar.bz2 ]; then 25 | curl -o /tmp/cef.tar.bz2.part "https://cef-builds.spotifycdn.com/$CEF_ARCHIVE.tar.bz2" 26 | mv /tmp/cef.tar.bz2.part /tmp/cef.tar.bz2 27 | fi 28 | mkdir -p cef 29 | tar -xvf /tmp/cef.tar.bz2 -C cef 30 | 31 | export CEF_PATH="$PWD/cef/$CEF_ARCHIVE" 32 | 33 | # Build CEF 34 | ( 35 | cd "$CEF_PATH" 36 | echo $CEF_PATH 37 | 38 | # Add compilation definitions to the top of the CMakeLists.txt file 39 | mv CMakeLists.txt CMakeLists.txt.old 40 | echo "add_compile_definitions(DCHECK_ALWAYS_ON=1)" > CMakeLists.txt 41 | cat CMakeLists.txt.old >> CMakeLists.txt 42 | 43 | # Build 44 | cmake . 45 | cmake --build . 46 | ) 47 | 48 | echo "CEF is ready, please put the following line somewhere to set the environment variable, e.g. in .profile:" 49 | echo "export CEF_PATH=\"$CEF_PATH\"" 50 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamidev/browser-window/3ea05e6ee4493dc34d2f0ae073c0b7f3d81740c8/preview.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | blank_lines_upper_bound = 2 2 | condense_wildcard_suffixes = true 3 | edition = "2021" 4 | enum_discrim_align_threshold = 60 5 | fn_params_layout = "Compressed" 6 | fn_single_line = true 7 | format_code_in_doc_comments = true 8 | format_macro_matchers = true 9 | format_strings = true 10 | group_imports = "StdExternalCrate" 11 | hard_tabs = true 12 | imports_granularity = "Crate" 13 | match_arm_blocks = false 14 | newline_style = "Unix" 15 | reorder_impl_items = true 16 | use_field_init_shorthand = true 17 | use_try_shorthand = true 18 | version = "Two" 19 | wrap_comments = true -------------------------------------------------------------------------------- /setup-cef-files.bat: -------------------------------------------------------------------------------- 1 | mkdir target\debug 2 | 3 | xcopy /Y %CEF_PATH%\Resources\* target\debug 4 | xcopy /Y %CEF_PATH%\Release\* target\debug -------------------------------------------------------------------------------- /setup-cef-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -p ./target/debug 3 | 4 | cp -r "$CEF_PATH/Resources/"* ./target/debug 5 | cp -r "$CEF_PATH/Release/"* ./target/debug 6 | 7 | #chown root:root ./target/debug/chrome-sandbox 8 | #chmod 4755 ./target/debug/chrome-sandbox 9 | -------------------------------------------------------------------------------- /src/application/settings.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | -------------------------------------------------------------------------------- /src/browser/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::DerefMut, path::PathBuf}; 2 | 3 | #[cfg(feature = "threadsafe")] 4 | use unsafe_send_sync::UnsafeSend; 5 | 6 | use crate::{ 7 | application::ApplicationHandle, 8 | browser::*, 9 | core::{browser_window::*, window::*}, 10 | rc::Rc, 11 | window::WindowBuilder, 12 | }; 13 | 14 | /// The type of content to display in a browser window 15 | #[derive(Clone)] 16 | pub enum Source { 17 | /// Displays the given HTML code in the browser. 18 | Html(String), 19 | /// Displays the local file for the given path. 20 | File(PathBuf), 21 | /// Displays the given URL in the browser. 22 | Url(String), 23 | } 24 | 25 | /// Used to create a [`BrowserWindow`] or [`BrowserWindowThreaded`] instance, 26 | /// depending on whether or not you have feature `threadsafe` enabled. 27 | /// 28 | /// ```ignore 29 | /// let mut bwb = BrowserWindowBuilder::new(Source::Url("https://www.duckduckgo.com".into())) 30 | /// bwb.dev_tools(true); 31 | /// bwb.title("DuckDuckGo"); 32 | /// let bw = bwb.build( app ); 33 | /// ``` 34 | pub struct BrowserWindowBuilder { 35 | dev_tools: bool, 36 | source: Source, 37 | window: WindowBuilder, 38 | } 39 | 40 | impl BrowserWindowBuilder { 41 | /// Sets whether or not an extra window with developer tools will be opened 42 | /// together with this browser. When in debug mode the default is `true`. 43 | /// When in release mode the default is `false`. 44 | pub fn dev_tools(&mut self, enabled: bool) -> &mut Self { 45 | self.dev_tools = enabled; 46 | self 47 | } 48 | 49 | /// Creates an instance of a browser window builder. 50 | /// 51 | /// # Arguments 52 | /// * `source` - The content that will be displayed in the browser window. 53 | pub fn new(source: Source) -> Self { 54 | Self { 55 | dev_tools: false, 56 | source, 57 | window: WindowBuilder::new(), 58 | } 59 | } 60 | 61 | #[deprecated(since = "0.12.1", note = "please use `build_async` instead")] 62 | pub async fn build(self, app: &ApplicationHandle) -> BrowserWindow { 63 | self.build_async(app).await 64 | } 65 | 66 | /// Creates the browser window. 67 | /// 68 | /// # Arguments 69 | /// * `app` - An application handle that this browser window can spawn into 70 | pub async fn build_async(self, app: &ApplicationHandle) -> BrowserWindow { 71 | let (tx, rx) = oneshot::channel::(); 72 | 73 | self._build(app, move |handle| { 74 | if let Err(_) = tx.send(handle) { 75 | panic!("Unable to send browser handle back") 76 | } 77 | }); 78 | 79 | Self::prepare_handle(rx.await.unwrap()) 80 | } 81 | 82 | /// Creates the browser window. 83 | /// 84 | /// Keep in mind that the description of this function is for when feature 85 | /// `threadsafe` is enabled. When it is not enabled, it looks like this: 86 | /// ```ignore 87 | /// pub async fn build( self, app: ApplicationHandle ) -> Result { /* ... */ } 88 | /// ``` 89 | /// 90 | /// # Arguments 91 | /// * `app` - An (thread-safe) application handle. 92 | #[cfg(feature = "threadsafe")] 93 | pub async fn build_threaded( 94 | self, app: &ApplicationHandleThreaded, 95 | ) -> Result { 96 | let (tx, rx) = oneshot::channel::>(); 97 | 98 | // We need to dispatch the spawning of the browser to the GUI thread 99 | app.delegate(|app_handle| { 100 | self._build(&*app_handle, |inner_handle| { 101 | if let Err(_) = tx.send(UnsafeSend::new(inner_handle)) { 102 | panic!("Unable to send browser handle back") 103 | } 104 | }); 105 | }) 106 | .await?; 107 | 108 | Ok(BrowserWindowThreaded(Self::prepare_handle( 109 | rx.await.unwrap().unwrap(), 110 | ))) 111 | } 112 | 113 | fn prepare_handle(handle: BrowserWindowHandle) -> BrowserWindow { 114 | // Put a reference counted handle in the user data of the window, so that there 115 | // exists 'ownership' for as long as the window actually lives. 116 | let owner = BrowserWindowOwner(handle); 117 | let rc_handle = Rc::new(owner); 118 | let user_data = Box::into_raw(Box::new(BrowserUserData { 119 | _handle: rc_handle.clone(), 120 | })); 121 | rc_handle.0.window().0.set_user_data(user_data as _); 122 | 123 | BrowserWindow(rc_handle) 124 | } 125 | 126 | pub fn build_sync(self, app: &ApplicationHandle, on_created: impl FnOnce(BrowserWindow)) { 127 | self._build(app, move |inner| { 128 | let handle = Self::prepare_handle(inner); 129 | on_created(handle); 130 | }) 131 | } 132 | 133 | fn _build(self, app: &ApplicationHandle, on_created: impl FnOnce(BrowserWindowHandle)) { 134 | match self { 135 | Self { 136 | source, 137 | dev_tools, 138 | window, 139 | } => { 140 | // Parent 141 | let parent_handle = match window.parent { 142 | None => WindowImpl::default(), 143 | Some(p) => p.i, 144 | }; 145 | 146 | // Title 147 | let title = match window.title.as_ref() { 148 | None => "Browser Window".into(), 149 | Some(t) => t.as_str().into(), 150 | }; 151 | 152 | let callback_data: *mut Box = 153 | Box::into_raw(Box::new(Box::new(on_created))); 154 | 155 | // Convert options to FFI structs 156 | let window_options = WindowOptions { 157 | borders: window.borders, 158 | minimizable: window.minimizable, 159 | resizable: window.resizable, 160 | }; 161 | let other_options = BrowserWindowOptions { 162 | dev_tools: if dev_tools { 1 } else { 0 }, 163 | resource_path: "".into(), 164 | }; 165 | 166 | BrowserWindowImpl::new( 167 | app.inner.clone(), 168 | parent_handle, 169 | source, 170 | title, 171 | window.width, 172 | window.height, 173 | &window_options, 174 | &other_options, 175 | browser_window_created_callback, 176 | callback_data as _, 177 | ); 178 | } 179 | } 180 | } 181 | } 182 | 183 | impl Deref for BrowserWindowBuilder { 184 | type Target = WindowBuilder; 185 | 186 | fn deref(&self) -> &Self::Target { &self.window } 187 | } 188 | 189 | impl DerefMut for BrowserWindowBuilder { 190 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.window } 191 | } 192 | 193 | fn browser_window_created_callback(inner_handle: BrowserWindowImpl, data: *mut ()) { 194 | let data_ptr = data as *mut Box; 195 | let func = unsafe { Box::from_raw(data_ptr) }; 196 | 197 | let rust_handle = BrowserWindowHandle::new(inner_handle); 198 | 199 | func(&rust_handle); 200 | } 201 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | pub trait HasHandle { 2 | fn handle(&self) -> &H; 3 | } 4 | 5 | 6 | impl HasHandle for H { 7 | fn handle(&self) -> &H { self } 8 | } 9 | -------------------------------------------------------------------------------- /src/cookie.rs: -------------------------------------------------------------------------------- 1 | //! Module for dealing with cookies. 2 | 3 | use std::{borrow::Cow, marker::PhantomData, ops::*, time::SystemTime}; 4 | 5 | use futures_channel::oneshot; 6 | 7 | use crate::core::cookie::*; 8 | 9 | pub struct Cookie(CookieImpl); 10 | 11 | pub struct CookieJar(CookieJarImpl); 12 | 13 | pub struct CookieIterator<'a> { 14 | inner: CookieIteratorImpl, 15 | _phantom: PhantomData<&'a u8>, 16 | } 17 | 18 | impl Cookie { 19 | pub fn new(name: &str, value: &str) -> Self { Self(CookieImpl::new(name, value)) } 20 | 21 | pub fn creation_time(&self) -> SystemTime { self.0.creation_time() } 22 | 23 | pub fn expires(&self) -> Option { self.0.expires() } 24 | 25 | pub fn domain(&self) -> Cow<'_, str> { self.0.domain() } 26 | 27 | pub fn is_http_only(&self) -> bool { self.0.is_http_only() } 28 | 29 | pub fn name(&self) -> Cow<'_, str> { self.0.name() } 30 | 31 | pub fn path(&self) -> Cow<'_, str> { self.0.path() } 32 | 33 | pub fn is_secure(&self) -> bool { self.0.is_secure() } 34 | 35 | pub fn value(&self) -> Cow<'_, str> { self.0.value() } 36 | 37 | pub fn make_http_only(&mut self) -> &mut Self { 38 | self.0.make_http_only(); 39 | self 40 | } 41 | 42 | pub fn make_secure(&mut self) -> &mut Self { 43 | self.0.make_secure(); 44 | self 45 | } 46 | 47 | pub fn set_creation_time(&mut self, time: &SystemTime) -> &mut Self { 48 | self.0.set_creation_time(time); 49 | self 50 | } 51 | 52 | pub fn set_expires(&mut self, time: &SystemTime) -> &mut Self { 53 | self.0.set_expires(time); 54 | self 55 | } 56 | 57 | pub fn set_domain(&mut self, domain: &str) -> &mut Self { 58 | self.0.set_domain(domain); 59 | self 60 | } 61 | 62 | pub fn set_name(&mut self, name: &str) -> &mut Self { 63 | self.0.set_name(name); 64 | self 65 | } 66 | 67 | pub fn set_path(&mut self, path: &str) -> &mut Self { 68 | self.0.set_path(path); 69 | self 70 | } 71 | 72 | pub fn set_value(&mut self, value: &str) -> &mut Self { 73 | self.0.set_value(value); 74 | self 75 | } 76 | } 77 | 78 | impl Drop for Cookie { 79 | fn drop(&mut self) { self.0.free(); } 80 | } 81 | 82 | impl<'a> CookieIterator<'a> { 83 | pub async fn next(&mut self) -> Option { 84 | let (tx, rx) = oneshot::channel::>(); 85 | 86 | let more = self._next(|result| { 87 | if let Err(_) = tx.send(result) { 88 | panic!("unable to send cookie iterator next result back"); 89 | } 90 | }); 91 | 92 | if !more { 93 | return None; 94 | } 95 | 96 | rx.await.unwrap() 97 | } 98 | 99 | fn _next(&mut self, on_next: H) -> bool 100 | where 101 | H: FnOnce(Option), 102 | { 103 | let data = Box::into_raw(Box::new(on_next)); 104 | 105 | let called_closure = self 106 | .inner 107 | .next(cookie_iterator_next_handler::, data as _); 108 | 109 | if !called_closure { 110 | unsafe { 111 | let _ = Box::from_raw(data); 112 | } 113 | } 114 | 115 | called_closure 116 | } 117 | } 118 | 119 | impl<'a> Drop for CookieIterator<'a> { 120 | fn drop(&mut self) { self.inner.free(); } 121 | } 122 | 123 | impl CookieJar { 124 | /// Deletes all cookies. 125 | /// If `url` is not an empty string, only the cookies of the given url will 126 | /// be deleted. 127 | pub async fn clear(&mut self, url: &str) -> usize { self.delete(url, "").await } 128 | 129 | /// Like `clear`, but with `url` set empty. 130 | pub async fn clear_all(&mut self) -> usize { self.clear("").await } 131 | 132 | fn _delete(&mut self, url: &str, name: &str, on_complete: H) 133 | where 134 | H: FnOnce(usize), 135 | { 136 | let data = Box::into_raw(Box::new(on_complete)); 137 | 138 | self.0 139 | .delete(url, name, cookie_delete_callback::, data as _); 140 | } 141 | 142 | /// Deletes all cookies with the given `name`. 143 | /// If `url` is not empty, only the cookie with the given `name` at that 144 | /// `url` will be deleted. If `name` is empty, all cookies at the given 145 | /// `url` will be deleted. If both `url` and `name` are empty, all cookies 146 | /// will be deleted. 147 | pub async fn delete(&mut self, url: &str, name: &str) -> usize { 148 | let (tx, rx) = oneshot::channel::(); 149 | 150 | self._delete(url, name, |result| { 151 | tx.send(result) 152 | .expect("unable to send back cookie delete count"); 153 | }); 154 | 155 | rx.await.unwrap() 156 | } 157 | 158 | /// Like `delete`, but with `url` set empty. 159 | pub async fn delete_all(&mut self, name: &str) -> usize { self.delete("", name).await } 160 | 161 | /// Finds the first cookie that has the given `name` in the given `url`. 162 | /// If `include_http_only` is set to `false`, a `HttpOnly` cookie will not 163 | /// be found. 164 | pub async fn find(&self, url: &str, name: &str, include_http_only: bool) -> Option { 165 | let mut iter = self.iter(url, include_http_only); 166 | 167 | while let Some(cookie) = iter.next().await { 168 | if cookie.name() == name { 169 | return Some(cookie); 170 | } 171 | } 172 | 173 | None 174 | } 175 | 176 | /// Finds the first cookie that has the given `name`. 177 | pub async fn find_from_all(&self, name: &str) -> Option { 178 | let mut iter = self.iter_all(); 179 | 180 | while let Some(cookie) = iter.next().await { 181 | if cookie.name() == name { 182 | return Some(cookie); 183 | } 184 | } 185 | 186 | None 187 | } 188 | 189 | pub(crate) fn global() -> Option { CookieJarImpl::global().map(|i| Self(i)) } 190 | 191 | /// Returns a `CookieIterator` that iterates over cookies asynchronously. 192 | /// The `CookieIterator` has an async `next` function that you can use. 193 | /// 194 | /// # Example 195 | /// ```ignore 196 | /// if let Some(cookie_jar) = app.cookie_jar() { 197 | /// let mut iterator = cookie_jar.iter("http://localhost/", true); 198 | /// 199 | /// while let Some(cookie) = iterator.next().await { 200 | /// // ... do something with `cookie` 201 | /// } 202 | /// } 203 | /// ``` 204 | pub fn iter<'a>(&'a self, url: &str, include_http_only: bool) -> CookieIterator<'a> { 205 | let inner = self.0.iterator(url, include_http_only); 206 | 207 | CookieIterator { 208 | inner, 209 | _phantom: PhantomData, 210 | } 211 | } 212 | 213 | /// Returns a `CookieIterator` that iterators over cookies asynchronously. 214 | /// Like `iter`, but iterates over all cookies from any url. 215 | pub fn iter_all<'a>(&'a self) -> CookieIterator<'a> { 216 | let inner = self.0.iterator_all(); 217 | 218 | CookieIterator { 219 | inner, 220 | _phantom: PhantomData, 221 | } 222 | } 223 | 224 | fn _store<'a, H>(&mut self, url: &str, cookie: &Cookie, on_complete: H) 225 | where 226 | H: FnOnce(Result<(), CookieStorageError>) + 'a, 227 | { 228 | let data = Box::into_raw(Box::new(on_complete)); 229 | 230 | self.0.store( 231 | url.into(), 232 | &cookie.0, 233 | Some(cookie_store_callback::<'a, H>), 234 | data as _, 235 | ); 236 | } 237 | 238 | /// Stores the given `cookie` for the given `url`. 239 | pub async fn store(&mut self, url: &str, cookie: &Cookie) -> Result<(), CookieStorageError> { 240 | let (tx, rx) = oneshot::channel::>(); 241 | 242 | self._store(url, cookie, |result| { 243 | tx.send(result) 244 | .expect("unable to retrieve cookie storage error"); 245 | }); 246 | 247 | rx.await.unwrap() 248 | } 249 | } 250 | 251 | impl Drop for CookieJar { 252 | fn drop(&mut self) { self.0.free(); } 253 | } 254 | 255 | unsafe fn cookie_delete_callback<'a, H>(_handle: CookieJarImpl, cb_data: *mut (), deleted: usize) 256 | where 257 | H: FnOnce(usize) + 'a, 258 | { 259 | let data_ptr = cb_data as *mut H; 260 | let data: Box = Box::from_raw(data_ptr); 261 | 262 | (*data)(deleted); 263 | } 264 | 265 | unsafe fn cookie_store_callback<'a, H>( 266 | _handle: CookieJarImpl, cb_data: *mut (), result: Result<(), CookieStorageError>, 267 | ) where 268 | H: FnOnce(Result<(), CookieStorageError>) + 'a, 269 | { 270 | let data_ptr = cb_data as *mut H; 271 | let data: Box = Box::from_raw(data_ptr); 272 | 273 | (*data)(result); 274 | } 275 | 276 | unsafe fn cookie_iterator_next_handler( 277 | _handle: CookieIteratorImpl, cb_data: *mut (), cookie: Option, 278 | ) where 279 | H: FnOnce(Option), 280 | { 281 | let data_ptr = cb_data as *mut H; 282 | let data: Box = Box::from_raw(data_ptr); 283 | 284 | (*data)(cookie.map(|c| Cookie(c))); 285 | } 286 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | pub mod application; 2 | pub mod browser_window; 3 | pub mod cookie; 4 | pub mod error; 5 | pub mod prelude; 6 | pub mod window; 7 | -------------------------------------------------------------------------------- /src/core/application.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "gtk"))] 2 | pub mod c; 3 | #[cfg(feature = "gtk")] 4 | pub mod gtk; 5 | 6 | use std::{ 7 | os::raw::{c_char, c_int}, 8 | time::Duration, 9 | }; 10 | 11 | #[cfg(not(feature = "gtk"))] 12 | pub use c::ApplicationImpl; 13 | #[cfg(feature = "gtk")] 14 | pub use gtk::ApplicationImpl; 15 | 16 | use crate::{application::ApplicationSettings, error::Result}; 17 | 18 | pub trait ApplicationExt: Clone { 19 | /// Asserts if not on the GUI thread 20 | fn assert_correct_thread(&self); 21 | /// Dispatches work to be executed on the GUI thread. 22 | fn dispatch(&self, work: fn(ApplicationImpl, *mut ()), data: *mut ()) -> bool; 23 | /// Dispatches work to be executed on the GUI thread, but delayed by the 24 | /// specified number of milliseconds. 25 | fn dispatch_delayed( 26 | &self, work: fn(ApplicationImpl, *mut ()), data: *mut (), delay: Duration, 27 | ) -> bool; 28 | /// Causes the main loop to exit and lets it return the given code. 29 | fn exit(&self, exit_code: i32); 30 | /// Same as `exit`, but is thread-safe. 31 | fn exit_threadsafe(self: &Self, exit_code: i32); 32 | /// Shuts down all application processes and performs necessary clean-up 33 | /// code. 34 | fn free(&self) {} 35 | fn initialize( 36 | argc: c_int, argv: *mut *mut c_char, settings: &ApplicationSettings, 37 | ) -> Result; 38 | /// When this is called, the runtime will exit as soon as there are no more 39 | /// windows left. 40 | fn mark_as_done(&self); 41 | /// Runs the main loop. 42 | /// This blocks until the application is exitting. 43 | fn run(&self, on_ready: fn(ApplicationImpl, *mut ()), data: *mut ()) -> i32; 44 | } 45 | -------------------------------------------------------------------------------- /src/core/application/c.rs: -------------------------------------------------------------------------------- 1 | //! This module implements the `Application` trait with the corresponding 2 | //! function definitions found in the C code base of `browser-window-c`. 3 | //! All functions are basically wrapping the FFI provided by crate 4 | //! `browser-window-c`. 5 | 6 | use std::{ 7 | os::raw::{c_char, c_int, c_void}, 8 | path::PathBuf, 9 | ptr, 10 | time::Duration, 11 | }; 12 | 13 | use super::{ApplicationExt, ApplicationSettings}; 14 | use crate::{error::*, prelude::*}; 15 | 16 | #[derive(Clone, Copy)] 17 | pub struct ApplicationImpl { 18 | pub(crate) inner: *mut cbw_Application, 19 | } 20 | 21 | impl ApplicationExt for ApplicationImpl { 22 | fn assert_correct_thread(&self) { unsafe { cbw_Application_assertCorrectThread(self.inner) } } 23 | 24 | fn dispatch(&self, work: fn(ApplicationImpl, *mut ()), _data: *mut ()) -> bool { 25 | let data = Box::new(DispatchData { 26 | func: work, 27 | data: _data, 28 | }); 29 | 30 | let data_ptr = Box::into_raw(data); 31 | 32 | unsafe { 33 | cbw_Application_dispatch(self.inner, Some(invocation_handler), data_ptr as _) != 0 34 | } 35 | } 36 | 37 | fn dispatch_delayed( 38 | &self, work: fn(ApplicationImpl, *mut ()), _data: *mut (), delay: Duration, 39 | ) -> bool { 40 | let data = Box::new(DispatchData { 41 | func: work, 42 | data: _data, 43 | }); 44 | 45 | let data_ptr = Box::into_raw(data); 46 | 47 | unsafe { 48 | cbw_Application_dispatchDelayed( 49 | self.inner, 50 | Some(invocation_handler), 51 | data_ptr as _, 52 | delay.as_millis() as _, 53 | ) != 0 54 | } 55 | } 56 | 57 | fn exit(&self, exit_code: i32) { unsafe { cbw_Application_exit(self.inner, exit_code as _) } } 58 | 59 | fn exit_threadsafe(self: &Self, exit_code: i32) { 60 | unsafe { cbw_Application_exitAsync(self.inner, exit_code) } 61 | } 62 | 63 | fn initialize( 64 | argc: c_int, argv: *mut *mut c_char, settings: &ApplicationSettings, 65 | ) -> Result { 66 | let exec_path: &str = match settings.engine_seperate_executable_path.as_ref() { 67 | None => "", 68 | Some(path) => path.to_str().unwrap(), 69 | }; 70 | 71 | let c_settings = cbw_ApplicationSettings { 72 | engine_seperate_executable_path: exec_path.into(), 73 | resource_dir: settings 74 | .resource_dir 75 | .as_ref() 76 | .unwrap_or(&PathBuf::new()) 77 | .as_os_str() 78 | .to_string_lossy() 79 | .as_ref() 80 | .into(), 81 | remote_debugging_port: settings.remote_debugging_port.unwrap_or(0), 82 | }; 83 | 84 | let mut c_handle: *mut cbw_Application = ptr::null_mut(); 85 | let c_err = unsafe { cbw_Application_initialize(&mut c_handle, argc, argv, &c_settings) }; 86 | if c_err.code != 0 { 87 | return Err(c_err.into()); 88 | } 89 | 90 | Ok(Self { inner: c_handle }) 91 | } 92 | 93 | fn mark_as_done(&self) { unsafe { cbw_Application_markAsDone(self.inner) }; } 94 | 95 | fn run(&self, on_ready: fn(ApplicationImpl, *mut ()), _data: *mut ()) -> i32 { 96 | let data = Box::new(DispatchData { 97 | func: on_ready, 98 | data: _data, 99 | }); 100 | 101 | let data_ptr = Box::into_raw(data); 102 | 103 | // The dispatch handler does exactly the same thing 104 | unsafe { cbw_Application_run(self.inner, Some(invocation_handler), data_ptr as _) } 105 | } 106 | } 107 | 108 | struct DispatchData { 109 | func: unsafe fn(ApplicationImpl, *mut ()), 110 | data: *mut (), 111 | } 112 | 113 | unsafe extern "C" fn invocation_handler(_handle: *mut cbw_Application, _data: *mut c_void) { 114 | let data_ptr = _data as *mut DispatchData; 115 | let data = Box::from_raw(data_ptr); 116 | let handle = ApplicationImpl { inner: _handle }; 117 | 118 | (data.func)(handle, data.data); 119 | } 120 | -------------------------------------------------------------------------------- /src/core/application/gtk.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_char, c_int}, 3 | sync::{ 4 | atomic::{AtomicI32, Ordering}, 5 | Arc, 6 | }, 7 | time::Duration, 8 | }; 9 | 10 | use gtk::prelude::{ApplicationExt, ApplicationExtManual}; 11 | 12 | use super::{super::error::*, ApplicationSettings}; 13 | 14 | #[derive(Clone)] 15 | pub struct ApplicationImpl { 16 | pub inner: gtk::Application, 17 | pub exit_code: Arc, 18 | } 19 | 20 | impl super::ApplicationExt for ApplicationImpl { 21 | fn assert_correct_thread(&self) { unimplemented!() } 22 | 23 | fn dispatch(&self, work: fn(ApplicationImpl, *mut ()), data: *mut ()) -> bool { 24 | let this = self.clone(); 25 | gtk::glib::source::idle_add_local_once(move || work(this, data)); 26 | true 27 | } 28 | 29 | fn dispatch_delayed( 30 | &self, work: fn(ApplicationImpl, *mut ()), data: *mut (), delay: Duration, 31 | ) -> bool { 32 | let this = self.clone(); 33 | gtk::glib::source::timeout_add_local_once(delay, move || work(this, data)); 34 | true 35 | } 36 | 37 | fn exit(&self, exit_code: i32) { 38 | self.exit_code.store(exit_code, Ordering::Relaxed); 39 | self.inner.quit(); 40 | } 41 | 42 | fn exit_threadsafe(&self, exit_code: i32) { self.exit(exit_code); } 43 | 44 | fn free(&self) {} 45 | 46 | fn initialize( 47 | _argc: c_int, _argv: *mut *mut c_char, _settings: &ApplicationSettings, 48 | ) -> Result { 49 | let inner = gtk::Application::builder().build(); 50 | Ok(Self { 51 | inner, 52 | exit_code: Arc::new(AtomicI32::new(0)), 53 | }) 54 | } 55 | 56 | fn mark_as_done(&self) {} 57 | 58 | fn run(&self, on_ready: fn(ApplicationImpl, *mut ()), data: *mut ()) -> i32 { 59 | let this = self.clone(); 60 | self.inner.connect_activate(move |_| { 61 | on_ready(this.clone(), data); 62 | }); 63 | self.inner.run(); 64 | self.exit_code.load(Ordering::Relaxed) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/browser_window.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(feature = "gtk", feature = "edge2")))] 2 | pub mod c; 3 | #[cfg(feature = "edge2")] 4 | mod edge2; 5 | #[cfg(feature = "gtk")] 6 | mod webkit; 7 | 8 | use std::borrow::Cow; 9 | 10 | use browser_window_c::*; 11 | #[cfg(not(any(feature = "gtk", feature = "edge2")))] 12 | pub use c::{BrowserWindowImpl, JsEvaluationError}; 13 | #[cfg(feature = "edge2")] 14 | pub use edge2::{BrowserWindowImpl, JsEvaluationError}; 15 | #[cfg(feature = "gtk")] 16 | pub use webkit::{BrowserWindowImpl, JsEvaluationError}; 17 | 18 | use super::{ 19 | super::event::*, 20 | application::ApplicationImpl, 21 | cookie::CookieJarImpl, 22 | window::{WindowImpl, WindowOptions}, 23 | }; 24 | use crate::{browser::*, prelude::JsValue, rc::*}; 25 | 26 | 27 | //pub type BrowserWindowEventHandler<'a, A> = EventHandler<'a, 28 | // BrowserWindowHandle, A>; 29 | 30 | pub type BrowserWindowOptions = cbw_BrowserWindowOptions; 31 | 32 | /// The data that is passed to the C FFI handler function 33 | pub(crate) struct BrowserUserData { 34 | pub(crate) _handle: Rc, 35 | } 36 | 37 | pub type CreationCallbackFn = fn(bw: BrowserWindowImpl, data: *mut ()); 38 | pub type EvalJsCallbackFn = 39 | fn(bw: BrowserWindowImpl, data: *mut (), result: Result); 40 | 41 | pub trait BrowserWindowEventExt { 42 | fn on_address_changed(&self, _handle: Weak) -> AddressChangedEvent { 43 | unimplemented!(); 44 | } 45 | fn on_console_message(&self, _handle: Weak) -> ConsoleMessageEvent { 46 | unimplemented!(); 47 | } 48 | fn on_favicon_changed(&self, _handle: Weak) -> FaviconChangedEvent { 49 | unimplemented!(); 50 | } 51 | fn on_fullscreen_mode_changed( 52 | &self, _handle: Weak, 53 | ) -> FullscreenModeChangedEvent { 54 | unimplemented!(); 55 | } 56 | fn on_loading_progress_changed( 57 | &self, _handle: Weak, 58 | ) -> LoadingProgressChangedEvent { 59 | unimplemented!(); 60 | } 61 | fn on_message(&self, _handle: Weak) -> MessageEvent; 62 | fn on_navigation_end(&self, _handle: Weak) -> NavigationEndEvent { 63 | unimplemented!(); 64 | } 65 | fn on_navigation_start(&self, _handle: Weak) -> NavigationStartEvent { 66 | unimplemented!(); 67 | } 68 | fn on_page_title_changed(&self, _handle: Weak) -> PageTitleChangedEvent { 69 | unimplemented!(); 70 | } 71 | fn on_status_message(&self, _handle: Weak) -> StatusMessageEvent { 72 | unimplemented!(); 73 | } 74 | fn on_tooltip(&self, _handle: Weak) -> TooltipEvent { 75 | unimplemented!(); 76 | } 77 | } 78 | 79 | pub trait BrowserWindowExt: BrowserWindowEventExt + Clone { 80 | fn cookie_jar(&self) -> Option; 81 | 82 | /// Executes the given JavaScript string. 83 | /// The result will be provided by invoking the callback function. 84 | fn eval_js(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()); 85 | 86 | /// Like `eval_js`, except it can be called from any thread. 87 | fn eval_js_threadsafe(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()); 88 | 89 | fn free(&self); 90 | 91 | /// Causes the browser to navigate to the given URI. 92 | fn navigate(&self, uri: &str); 93 | 94 | fn url<'a>(&'a self) -> Cow<'a, str>; 95 | 96 | /// Gives a handle to the underlying window. 97 | fn window(&self) -> WindowImpl; 98 | 99 | fn new( 100 | app: ApplicationImpl, parent: WindowImpl, source: Source, title: &str, width: Option, 101 | height: Option, options: &WindowOptions, 102 | browser_window_options: &BrowserWindowOptions, creation_callback: CreationCallbackFn, 103 | callback_data: *mut (), 104 | ); 105 | } 106 | 107 | 108 | impl BrowserWindowImpl { 109 | pub(crate) fn free_user_data(user_data: *mut ()) { 110 | let ptr = user_data as *mut BrowserUserData; 111 | unsafe { 112 | let _ = Box::from_raw(ptr); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/core/browser_window/webkit.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | cell::Cell, 4 | collections::HashMap, 5 | sync::atomic::{AtomicBool, Ordering}, 6 | }; 7 | 8 | use gtk::{gio::Cancellable, glib::CastNone, prelude::*}; 9 | use javascriptcore::ValueExt; 10 | use webkit2gtk::{LoadEvent, Settings, SettingsExt, UserContentManagerExt, WebViewExt}; 11 | 12 | use super::{super::window::WindowImpl, *}; 13 | use crate::{ 14 | def_browser_event, def_event, 15 | prelude::{ApplicationExt, WindowExt}, 16 | rc::Rc, 17 | }; 18 | 19 | 20 | #[derive(Clone)] 21 | pub struct BrowserWindowImpl { 22 | inner: webkit2gtk::WebView, 23 | } 24 | 25 | struct EvalJsCallbackData { 26 | handle: BrowserWindowImpl, 27 | code: String, 28 | callback: EvalJsCallbackFn, 29 | data: *mut (), 30 | } 31 | 32 | /// An error that may occur when evaluating or executing JavaScript code. 33 | pub type JsEvaluationError = webkit2gtk::Error; 34 | 35 | impl BrowserWindowExt for BrowserWindowImpl { 36 | fn cookie_jar(&self) -> Option { None } 37 | 38 | fn eval_js(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) { 39 | let this = self.clone(); 40 | self.inner 41 | .evaluate_javascript(js, None, None, Option::<&Cancellable>::None, move |r| { 42 | let result = match r { 43 | Ok(v) => Ok(transform_js_value(v)), 44 | // TODO: Test the error properly, not by testing message 45 | Err(e) => 46 | if e.message() == "Unsupported result type" { 47 | Ok(JsValue::Undefined) 48 | } else { 49 | Err(e) 50 | }, 51 | }; 52 | callback(this, callback_data, result); 53 | }); 54 | } 55 | 56 | fn eval_js_threadsafe(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) { 57 | let app = self.window().app(); 58 | let dispatch_data = Box::new(EvalJsCallbackData { 59 | handle: self.clone(), 60 | code: js.to_owned(), 61 | callback, 62 | data: callback_data, 63 | }); 64 | app.dispatch(dispatch_eval_js, Box::into_raw(dispatch_data) as _); 65 | } 66 | 67 | fn free(&self) {} 68 | 69 | fn navigate(&self, uri: &str) { self.inner.load_uri(uri); } 70 | 71 | fn new( 72 | app: ApplicationImpl, parent: WindowImpl, source: Source, title: &str, width: Option, 73 | height: Option, options: &WindowOptions, 74 | browser_window_options: &BrowserWindowOptions, creation_callback: CreationCallbackFn, 75 | callback_data: *mut (), 76 | ) { 77 | let window = WindowImpl::new(app, parent, title, width, height, options); 78 | let settings = Settings::builder().build(); 79 | if browser_window_options.dev_tools > 0 { 80 | settings.set_enable_developer_extras(true); 81 | } 82 | let inner = webkit2gtk::WebView::builder().settings(&settings).build(); 83 | let this = Self { 84 | inner: inner.clone(), 85 | }; 86 | 87 | // Add the webview to the window 88 | window.0.add(&inner); 89 | 90 | // Load the source 91 | match source { 92 | Source::Url(url) => { 93 | inner.load_uri(&url); 94 | } 95 | Source::File(file) => { 96 | let uri = "file://".to_string() 97 | + file 98 | .clone() 99 | .into_os_string() 100 | .into_string() 101 | .unwrap() 102 | .as_str(); 103 | inner.load_uri(&uri); 104 | } 105 | Source::Html(html) => { 106 | inner.load_html(&html, None); 107 | } 108 | } 109 | 110 | // FIXME: We need to call creation_callback, but pass an error to it, if the web 111 | // view can not be loaded correctly. Now we risk never notifying the 112 | // future that is waiting on us. 113 | let created = AtomicBool::new(false); 114 | inner.connect_load_changed(move |i, e| { 115 | if e == LoadEvent::Finished { 116 | // Create the global JS function `invoke_extern` 117 | i.evaluate_javascript( 118 | r#" 119 | function invoke_extern(...args) { 120 | window.webkit.messageHandlers.bw.postMessage([].slice.call(args)) 121 | } 122 | "#, 123 | None, 124 | None, 125 | Option::<&Cancellable>::None, 126 | |r| { 127 | r.expect("invalid invoke_extern code"); 128 | }, 129 | ); 130 | 131 | if !created.swap(true, Ordering::Relaxed) { 132 | creation_callback(this.clone(), callback_data); 133 | } 134 | } 135 | }); 136 | } 137 | 138 | fn url(&self) -> Cow<'_, str> { 139 | self.inner 140 | .uri() 141 | .map(|g| g.to_string()) 142 | .unwrap_or_default() 143 | .into() 144 | } 145 | 146 | fn window(&self) -> WindowImpl { WindowImpl(self.inner.toplevel().and_dynamic_cast().unwrap()) } 147 | } 148 | 149 | impl BrowserWindowEventExt for BrowserWindowImpl { 150 | fn on_message(&self, handle: Weak) -> MessageEvent { 151 | MessageEvent::new(handle) 152 | } 153 | } 154 | 155 | def_browser_event!(MessageEvent(&mut self, handler) { 156 | // Register a message handler 157 | let user_context_manager = self.owner.upgrade().unwrap().inner.inner.user_content_manager().unwrap(); 158 | user_context_manager.register_script_message_handler("bw"); 159 | let owner = self.owner.clone(); 160 | let h = Rc::new(Cell::new(handler)); 161 | user_context_manager.connect_script_message_received(Some("bw"), move |_, r| { 162 | if let Some(this) = owner.upgrade() { 163 | let value = r 164 | .js_value() 165 | .map(|v| transform_js_value(v)) 166 | .unwrap_or(JsValue::Undefined); 167 | let (command, args) = match &value { 168 | JsValue::Array(a) => (a[0].to_string_unenclosed(), a[1..].to_vec()), 169 | _ => panic!("unexpected value type received from invoke_extern"), 170 | }; 171 | 172 | let e = MessageEventArgs { 173 | cmd: command.to_string(), 174 | args 175 | }; 176 | match unsafe { &mut *h.as_ptr() } { 177 | EventHandler::Sync(callback) => { 178 | (callback)(&*this, e); 179 | } 180 | EventHandler::Async(callback) => { 181 | let app = this.0.app(); 182 | let future = (callback)(BrowserWindow(this.clone()), e); 183 | app.spawn(future); 184 | } 185 | } 186 | } 187 | }); 188 | }); 189 | 190 | 191 | fn transform_js_value(v: javascriptcore::Value) -> JsValue { 192 | if v.is_array() { 193 | let props = v.object_enumerate_properties(); 194 | let mut vec = Vec::with_capacity(props.len()); 195 | for i in 0..props.len() as u32 { 196 | let iv = v.object_get_property_at_index(i).unwrap(); 197 | vec.push(transform_js_value(iv)); 198 | } 199 | JsValue::Array(vec) 200 | } else if v.is_boolean() { 201 | JsValue::Boolean(v.to_boolean()) 202 | } else if v.is_null() { 203 | JsValue::Null 204 | } else if v.is_number() { 205 | JsValue::Number(v.to_double().into()) 206 | } else if v.is_object() { 207 | let props = v.object_enumerate_properties(); 208 | let mut map = HashMap::with_capacity(props.len()); 209 | for prop in props { 210 | let pv = v.object_get_property(&prop).unwrap(); 211 | map.insert(prop.to_string(), transform_js_value(pv)); 212 | } 213 | JsValue::Object(map) 214 | } else if v.is_string() { 215 | JsValue::String(v.to_str().into()) 216 | } else if v.is_undefined() { 217 | JsValue::Undefined 218 | } else { 219 | JsValue::Other(v.to_str().to_string()) 220 | } 221 | } 222 | 223 | fn dispatch_eval_js(_app: ApplicationImpl, dispatch_data: *mut ()) { 224 | let data_ptr = dispatch_data as *mut EvalJsCallbackData; 225 | let data = unsafe { Box::from_raw(data_ptr) }; 226 | 227 | let inner = data.handle.clone().inner; 228 | let callback = data.callback.clone(); 229 | let handle = data.handle.clone(); 230 | let callback_data = data.data.clone(); 231 | inner.evaluate_javascript( 232 | &data.code, 233 | None, 234 | None, 235 | Option::<&Cancellable>::None, 236 | move |r| { 237 | let result = match r { 238 | Ok(v) => Ok(transform_js_value(v)), 239 | // TODO: Test the error properly, not by testing message 240 | Err(e) => 241 | if e.message() == "Unsupported result type" { 242 | Ok(JsValue::Undefined) 243 | } else { 244 | Err(e) 245 | }, 246 | }; 247 | (callback)(handle, callback_data, result); 248 | }, 249 | ); 250 | } 251 | -------------------------------------------------------------------------------- /src/core/cookie.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "webkitgtk"))] 2 | mod c; 3 | #[cfg(feature = "webkitgtk")] 4 | mod unsupported; 5 | 6 | use std::{borrow::Cow, error::Error, fmt, time::SystemTime}; 7 | 8 | #[cfg(not(feature = "webkitgtk"))] 9 | pub use c::*; 10 | #[cfg(feature = "webkitgtk")] 11 | pub use unsupported::*; 12 | 13 | pub type CookieStorageCallbackFn = 14 | unsafe fn(cj: CookieJarImpl, data: *mut (), Result<(), CookieStorageError>); 15 | pub type CookieDeleteCallbackFn = unsafe fn(cj: CookieJarImpl, data: *mut (), deleted: usize); 16 | pub type CookieIteratorNextCallbackFn = 17 | unsafe fn(cj: CookieIteratorImpl, data: *mut (), Option); 18 | 19 | pub trait CookieExt { 20 | fn new(_name: &str, _value: &str) -> CookieImpl { 21 | unimplemented!(); 22 | } 23 | 24 | fn creation_time(&self) -> SystemTime { 25 | unimplemented!(); 26 | } 27 | fn expires(&self) -> Option { 28 | unimplemented!(); 29 | } 30 | fn domain<'a>(&'a self) -> Cow<'a, str> { 31 | unimplemented!(); 32 | } 33 | fn free(&mut self) {} 34 | fn is_http_only(&self) -> bool { 35 | unimplemented!(); 36 | } 37 | fn name<'a>(&'a self) -> Cow<'a, str> { 38 | unimplemented!(); 39 | } 40 | fn path<'a>(&'a self) -> Cow<'a, str> { 41 | unimplemented!(); 42 | } 43 | fn is_secure(&self) -> bool { 44 | unimplemented!(); 45 | } 46 | fn value<'a>(&'a self) -> Cow<'a, str> { 47 | unimplemented!(); 48 | } 49 | 50 | fn make_http_only(&mut self) { 51 | unimplemented!(); 52 | } 53 | fn make_secure(&mut self) { 54 | unimplemented!(); 55 | } 56 | fn set_creation_time(&mut self, _time: &SystemTime) { 57 | unimplemented!(); 58 | } 59 | fn set_expires(&mut self, _time: &SystemTime) { 60 | unimplemented!(); 61 | } 62 | fn set_domain(&mut self, _domain: &str) { 63 | unimplemented!(); 64 | } 65 | fn set_name(&mut self, _name: &str) { 66 | unimplemented!(); 67 | } 68 | fn set_path(&mut self, _path: &str) { 69 | unimplemented!(); 70 | } 71 | fn set_value(&mut self, _value: &str) { 72 | unimplemented!(); 73 | } 74 | } 75 | 76 | pub trait CookieJarExt { 77 | fn delete( 78 | &mut self, _url: &str, _name: &str, _complete_cb: CookieDeleteCallbackFn, _cb_data: *mut (), 79 | ) { 80 | unimplemented!(); 81 | } 82 | fn free(&mut self) {} 83 | fn global() -> Option { None } 84 | fn iterator<'a>(&'a self, _url: &str, _include_http_only: bool) -> CookieIteratorImpl { 85 | unimplemented!(); 86 | } 87 | fn iterator_all<'a>(&'a self) -> CookieIteratorImpl { 88 | unimplemented!(); 89 | } 90 | fn store( 91 | &mut self, _url: &str, _cookie: &CookieImpl, _success_cb: Option, 92 | _cb_data: *mut (), 93 | ) { 94 | unimplemented!(); 95 | } 96 | } 97 | 98 | pub trait CookieIteratorExt { 99 | fn free(&mut self) {} 100 | fn next(&mut self, _on_next: CookieIteratorNextCallbackFn, _cb_data: *mut ()) -> bool { 101 | unimplemented!(); 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | pub enum CookieStorageError { 107 | Unknown, 108 | } 109 | 110 | impl fmt::Display for CookieStorageError { 111 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 112 | write!(f, "unable to set cookie (url invalid?)") 113 | } 114 | } 115 | 116 | impl Error for CookieStorageError { 117 | fn source(&self) -> Option<&(dyn Error + 'static)> { None } 118 | } 119 | -------------------------------------------------------------------------------- /src/core/cookie/unsupported.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct CookieImpl {} 4 | pub struct CookieIteratorImpl {} 5 | pub struct CookieJarImpl {} 6 | 7 | impl CookieExt for CookieImpl {} 8 | impl CookieIteratorExt for CookieIteratorImpl {} 9 | impl CookieJarExt for CookieJarImpl {} 10 | -------------------------------------------------------------------------------- /src/core/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "webkitgtk"))] 2 | mod c; 3 | #[cfg(feature = "webkitgtk")] 4 | mod common; 5 | 6 | #[cfg(not(feature = "webkitgtk"))] 7 | pub use c::Error; 8 | #[cfg(feature = "webkitgtk")] 9 | pub use common::Error; 10 | 11 | pub type Result = std::result::Result; 12 | -------------------------------------------------------------------------------- /src/core/error/c.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, fmt}; 2 | 3 | use browser_window_c::*; 4 | 5 | #[derive(Debug)] 6 | pub struct Error(cbw_Err); 7 | 8 | impl fmt::Display for Error { 9 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 10 | unsafe { 11 | let c_msg = cbw_Err_message(&self.0); 12 | 13 | let result = write!( 14 | f, 15 | "[{}] {}", 16 | self.0.code, 17 | CStr::from_ptr(c_msg) 18 | .to_str() 19 | .expect("invalid utf-8 in bw_Err error message") 20 | ); 21 | 22 | cbw_string_freeCstr(c_msg); 23 | 24 | return result; 25 | } 26 | } 27 | } 28 | 29 | impl std::error::Error for Error { 30 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } 31 | } 32 | 33 | impl From for Error { 34 | fn from(e: cbw_Err) -> Self { Self(e) } 35 | } 36 | -------------------------------------------------------------------------------- /src/core/error/common.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug)] 4 | pub enum Error {} 5 | 6 | impl fmt::Display for Error { 7 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") } 8 | } 9 | 10 | impl std::error::Error for Error { 11 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } 12 | } 13 | -------------------------------------------------------------------------------- /src/core/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::*; 2 | 3 | pub(crate) use browser_window_c::*; 4 | 5 | pub use super::cookie::*; 6 | 7 | pub type Dims2D = cbw_Dims2D; 8 | pub type Pos2D = cbw_Pos2D; 9 | 10 | pub use super::{ 11 | application::{ApplicationExt, ApplicationImpl}, 12 | browser_window::{BrowserWindowExt, BrowserWindowImpl}, 13 | window::{WindowExt, WindowImpl}, 14 | }; 15 | -------------------------------------------------------------------------------- /src/core/window.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "gtk"))] 2 | mod c; 3 | #[cfg(feature = "gtk")] 4 | mod gtk; 5 | 6 | #[cfg(not(feature = "gtk"))] 7 | pub use c::WindowImpl; 8 | #[cfg(feature = "gtk")] 9 | pub use gtk::WindowImpl; 10 | 11 | use crate::prelude::*; 12 | 13 | 14 | pub trait WindowExt: Clone { 15 | fn app(&self) -> ApplicationImpl; 16 | 17 | fn close(&self); 18 | fn free(&self); 19 | 20 | fn content_dimensions(&self) -> Dims2D; 21 | fn opacity(&self) -> u8; 22 | fn position(&self) -> Pos2D; 23 | fn title(&self) -> String; 24 | fn window_dimensions(&self) -> Dims2D; 25 | 26 | fn hide(&self); 27 | 28 | fn set_content_dimensions(&self, dimensions: Dims2D); 29 | fn set_opacity(&self, opacity: u8); 30 | fn set_position(&self, position: Pos2D); 31 | fn set_title(&self, title: &str); 32 | fn set_user_data(&self, user_data: *mut ()); 33 | fn set_window_dimensions(&self, dimensions: Dims2D); 34 | 35 | fn show(&self); 36 | } 37 | 38 | pub type WindowOptions = cbw_WindowOptions; 39 | -------------------------------------------------------------------------------- /src/core/window/c.rs: -------------------------------------------------------------------------------- 1 | //! This module implements the `Window` trait with the corresponding function 2 | //! definitions found in the C code base of `browser-window-c`. All functions 3 | //! are basically wrapping the FFI provided by crate `browser-window-c`. 4 | 5 | use std::{os::raw::c_char, ptr}; 6 | 7 | use super::{WindowExt, WindowOptions}; 8 | use crate::{core::application::ApplicationImpl, prelude::*}; 9 | 10 | #[derive(Clone)] 11 | pub struct WindowImpl { 12 | pub(crate) inner: *mut cbw_Window, 13 | } 14 | 15 | impl WindowImpl { 16 | pub fn new( 17 | app: ApplicationImpl, parent: Self, title: &str, width: Option, height: Option, 18 | options: &WindowOptions, 19 | ) -> Self { 20 | let str_slice: cbw_CStrSlice = title.into(); 21 | 22 | let w = match width { 23 | None => -1i32, 24 | Some(x) => x as i32, 25 | }; 26 | let h = match height { 27 | None => -1i32, 28 | Some(x) => x as i32, 29 | }; 30 | 31 | let handle = unsafe { cbw_Window_new(app.inner, parent.inner, str_slice, w, h, options) }; 32 | 33 | // Return 34 | Self { inner: handle } 35 | } 36 | } 37 | 38 | impl Default for WindowImpl { 39 | fn default() -> Self { 40 | Self { 41 | inner: ptr::null_mut(), 42 | } 43 | } 44 | } 45 | 46 | impl WindowExt for WindowImpl { 47 | fn app(&self) -> ApplicationImpl { 48 | ApplicationImpl { 49 | inner: unsafe { (*self.inner).app }, 50 | } 51 | } 52 | 53 | fn close(&self) { unsafe { cbw_Window_close(self.inner) } } 54 | 55 | fn free(&self) { unsafe { cbw_Window_free(self.inner) } } 56 | 57 | fn content_dimensions(&self) -> Dims2D { 58 | unsafe { cbw_Window_getContentDimensions(self.inner) } 59 | } 60 | 61 | fn opacity(&self) -> u8 { unsafe { cbw_Window_getOpacity(self.inner) } } 62 | 63 | fn position(&self) -> Pos2D { unsafe { cbw_Window_getPosition(self.inner) } } 64 | 65 | fn title(&self) -> String { 66 | // First obtain string size 67 | let mut buf: *mut c_char = ptr::null_mut(); 68 | let buf_len = unsafe { cbw_Window_getTitle(self.inner, &mut buf) }; 69 | 70 | let slice = cbw_StrSlice { 71 | data: buf, 72 | len: buf_len, 73 | }; 74 | 75 | unsafe { cbw_string_freeCstr(buf) }; 76 | 77 | // Convert to String 78 | slice.into() 79 | } 80 | 81 | fn window_dimensions(&self) -> Dims2D { unsafe { cbw_Window_getWindowDimensions(self.inner) } } 82 | 83 | fn hide(&self) { unsafe { cbw_Window_hide(self.inner) } } 84 | 85 | fn set_content_dimensions(&self, dimensions: Dims2D) { 86 | unsafe { cbw_Window_setContentDimensions(self.inner, dimensions) } 87 | } 88 | 89 | fn set_opacity(&self, opacity: u8) { unsafe { cbw_Window_setOpacity(self.inner, opacity) } } 90 | 91 | fn set_position(&self, position: Pos2D) { 92 | unsafe { cbw_Window_setPosition(self.inner, position) } 93 | } 94 | 95 | fn set_title(&self, title: &str) { 96 | let slice: cbw_CStrSlice = title.into(); 97 | unsafe { cbw_Window_setTitle(self.inner, slice) }; 98 | } 99 | 100 | fn set_user_data(&self, user_data: *mut ()) { 101 | unsafe { 102 | (*self.inner).user_data = user_data as _; 103 | } 104 | } 105 | 106 | fn set_window_dimensions(&self, dimensions: Dims2D) { 107 | unsafe { cbw_Window_setWindowDimensions(self.inner, dimensions) } 108 | } 109 | 110 | fn show(&self) { unsafe { cbw_Window_show(self.inner) } } 111 | } 112 | -------------------------------------------------------------------------------- /src/core/window/gtk.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{atomic::AtomicI32, Arc}; 2 | 3 | use glib::object::ObjectExt; 4 | use gtk::prelude::{GtkWindowExt, WidgetExt}; 5 | 6 | use super::{WindowExt, WindowOptions}; 7 | use crate::{core::application::ApplicationImpl, prelude::*}; 8 | 9 | #[derive(Clone)] 10 | pub struct WindowImpl(pub gtk::Window); 11 | 12 | impl WindowImpl { 13 | pub fn new( 14 | app: ApplicationImpl, parent: Self, title: &str, width: Option, height: Option, 15 | options: &WindowOptions, 16 | ) -> Self { 17 | let mut builder = gtk::Window::builder() 18 | .application(&app.inner) 19 | .parent(&parent.0) 20 | .destroy_with_parent(true) 21 | .title(title); 22 | 23 | builder = builder 24 | .border_width(options.borders as _) 25 | .resizable(options.resizable); 26 | 27 | if let Some(w) = width { 28 | builder = builder.width_request(w as _); 29 | } 30 | if let Some(h) = height { 31 | builder = builder.height_request(h as _); 32 | } 33 | 34 | let inner = builder.build(); 35 | // Delete user data when closing the window 36 | inner.connect_destroy(|this| { 37 | let user_data = unsafe { *this.data::<*mut ()>("bw-data").unwrap().as_ref() }; 38 | BrowserWindowImpl::free_user_data(user_data); 39 | }); 40 | 41 | Self(inner) 42 | } 43 | } 44 | 45 | impl Default for WindowImpl { 46 | fn default() -> Self { Self(gtk::Window::new(gtk::WindowType::Toplevel)) } 47 | } 48 | 49 | impl WindowExt for WindowImpl { 50 | fn app(&self) -> ApplicationImpl { 51 | ApplicationImpl { 52 | inner: self.0.application().unwrap(), 53 | exit_code: Arc::new(AtomicI32::new(0)), 54 | } 55 | } 56 | 57 | fn close(&self) { self.0.close(); } 58 | 59 | fn free(&self) {} 60 | 61 | fn content_dimensions(&self) -> Dims2D { 62 | unimplemented!(); 63 | } 64 | 65 | fn opacity(&self) -> u8 { 0 } 66 | 67 | fn position(&self) -> Pos2D { 68 | let (x, y) = self.0.position(); 69 | Pos2D { 70 | x: x as _, 71 | y: y as _, 72 | } 73 | } 74 | 75 | fn title(&self) -> String { 76 | self.0 77 | .title() 78 | .map(|g| g.to_string()) 79 | .unwrap_or(String::new()) 80 | } 81 | 82 | fn window_dimensions(&self) -> Dims2D { 83 | let (w, h) = self.0.size(); 84 | Dims2D { 85 | width: w as _, 86 | height: h as _, 87 | } 88 | } 89 | 90 | fn hide(&self) { self.0.hide(); } 91 | 92 | fn set_content_dimensions(&self, dimensions: Dims2D) {} 93 | 94 | fn set_opacity(&self, _opacity: u8) {} 95 | 96 | fn set_position(&self, position: Pos2D) { 97 | unimplemented!(); 98 | } 99 | 100 | fn set_title(&self, title: &str) { self.0.set_title(title); } 101 | 102 | fn set_user_data(&self, user_data: *mut ()) { 103 | unsafe { 104 | self.0.set_data("bw-data", user_data); 105 | } 106 | } 107 | 108 | fn set_window_dimensions(&self, dimensions: Dims2D) { 109 | self.0 110 | .set_size_request(dimensions.width as _, dimensions.height as _); 111 | } 112 | 113 | fn show(&self) { self.0.show_all(); } 114 | } 115 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | 3 | pub use super::core::error::{Error, Result}; 4 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | //! This module contains all event related types. 2 | //! 3 | //! To understand how events work in _BrowserWindow_, here is a short summary. 4 | //! The [`BrowserWindow`](../browser/struct.BrowserWindow.html) handle 5 | //! contains a bunch of functions that return different event objects, and then 6 | //! the event object can be used to register the callback at. 7 | //! 8 | //! Each event object has the 9 | //! [`register`](trait.EventExt.html#tymethod.register) method to register a 10 | //! closure to be executed on the occurence of the event. 11 | //! 12 | //! ``` 13 | //! use browser_window::{browser::*, prelude::*}; 14 | //! 15 | //! fn example(bw: BrowserWindow) { 16 | //! bw.on_message() 17 | //! .register(|h: &BrowserWindowHandle, e: MessageEventArgs| { 18 | //! // .. code here ... 19 | //! }); 20 | //! } 21 | //! ``` 22 | //! 23 | //! There is also a 24 | //! [`register_async`](trait.EventExt.html#tymethod.register_async) 25 | //! method, which can be useful in async code: 26 | //! 27 | //! ``` 28 | //! use browser_window::{browser::*, prelude::*}; 29 | //! 30 | //! fn async_example(bw: BrowserWindow) { 31 | //! bw.on_message() 32 | //! .register_async(|h: BrowserWindow, e: MessageEventArgs| async move { 33 | //! // .. code here ... 34 | //! }); 35 | //! } 36 | //! ``` 37 | //! 38 | //! Also, keep in mind that if the `register` or `register_async` methods are 39 | //! not available, that means that event object does not implement `EventExt`, 40 | //! which in turn means that the corresponding event is not supported for the 41 | //! browser framework that has been selected. 42 | //! 43 | //! CEF supports all events, unless it is stated that it is not implemented yet. 44 | //! The other browser frameworks only support a subset of what is supported for 45 | //! CEF. The reason for this is that CEF is simply the most cross-platform 46 | //! framework out there, so it gets the most care. 47 | 48 | use std::{boxed::Box, future::Future, pin::Pin}; 49 | 50 | 51 | #[cfg(not(feature = "threadsafe"))] 52 | pub type EventHandlerAsyncCallback = 53 | dyn FnMut(O, A) -> Pin + 'static>> + 'static; 54 | #[cfg(not(feature = "threadsafe"))] 55 | pub type EventHandlerSyncCallback = dyn FnMut(&H, A) + 'static; 56 | #[cfg(feature = "threadsafe")] 57 | pub type EventHandlerAsyncCallback = 58 | dyn FnMut(O, A) -> Pin + 'static>> + Send + 'static; 59 | #[cfg(feature = "threadsafe")] 60 | pub type EventHandlerSyncCallback = dyn FnMut(&H, A) + Send + 'static; 61 | 62 | 63 | pub enum EventHandler { 64 | Sync(Box>), 65 | Async(Box>), 66 | } 67 | 68 | 69 | /// An `Event` can be registered to with a regular closure or an 'async 70 | /// enclosure'. All events are implemented for CEF. 71 | /// If an event is not implemented for another browser framework, it will simply 72 | /// never be invoked. If an event _is_ supported by another browser framework, 73 | /// it should say so in its documentation. 74 | pub(crate) trait Event { 75 | fn register_handler(&mut self, handler: EventHandler); 76 | } 77 | 78 | pub trait EventExt { 79 | /// Register a closure to be invoked for this event. 80 | #[cfg(not(feature = "threadsafe"))] 81 | fn register(&mut self, handler: X) 82 | where 83 | X: FnMut(&H, A) + 'static; 84 | 85 | /// Register a closure to be invoked for this event. 86 | #[cfg(feature = "threadsafe")] 87 | fn register(&mut self, handler: X) 88 | where 89 | X: FnMut(&H, A) + Send + 'static; 90 | 91 | /// Register an 'async closure' to be invoked for this event. 92 | /// 93 | /// # Example 94 | /// ```ignore 95 | /// my_event.register_async(|args| async move { 96 | /// // Do something ... 97 | /// }); 98 | /// ``` 99 | #[cfg(not(feature = "threadsafe"))] 100 | fn register_async(&mut self, handler: X) 101 | where 102 | X: FnMut(O, A) -> F + 'static, 103 | F: Future + 'static; 104 | 105 | /// Register an 'async closure' to be invoked for this event. 106 | /// 107 | /// # Example 108 | /// ```ignore 109 | /// my_event.register_async(|args| async move { 110 | /// // Do something ... 111 | /// }); 112 | /// ``` 113 | #[cfg(feature = "threadsafe")] 114 | fn register_async(&mut self, handler: X) 115 | where 116 | X: FnMut(O, A) -> F + Send + 'static, 117 | F: Future + 'static; 118 | } 119 | 120 | 121 | impl EventExt for T 122 | where 123 | T: Event, 124 | { 125 | #[cfg(not(feature = "threadsafe"))] 126 | fn register(&mut self, mut handler: X) 127 | where 128 | X: FnMut(&H, A) + 'static, 129 | { 130 | self.register_handler(EventHandler::Sync(Box::new(move |h, args| { 131 | handler(h, args); 132 | }))); 133 | } 134 | 135 | #[cfg(feature = "threadsafe")] 136 | fn register(&mut self, mut handler: X) 137 | where 138 | X: FnMut(&H, A) + Send + 'static, 139 | { 140 | self.register_handler(EventHandler::Sync(Box::new(move |h, args| { 141 | handler(h, args); 142 | }))); 143 | } 144 | 145 | #[cfg(not(feature = "threadsafe"))] 146 | fn register_async(&mut self, mut handler: X) 147 | where 148 | X: FnMut(O, A) -> F + 'static, 149 | F: Future + 'static, 150 | { 151 | self.register_handler(EventHandler::Async(Box::new(move |h, args| { 152 | Box::pin(handler(h, args)) 153 | }))); 154 | } 155 | 156 | #[cfg(feature = "threadsafe")] 157 | fn register_async(&mut self, mut handler: X) 158 | where 159 | X: FnMut(O, A) -> F + Send + 'static, 160 | F: Future + 'static, 161 | { 162 | self.register_handler(EventHandler::Async(Box::new(move |h, args| { 163 | Box::pin(handler(h, args)) 164 | }))); 165 | } 166 | } 167 | 168 | 169 | #[doc(hidden)] 170 | #[macro_export] 171 | macro_rules! decl_event { 172 | ($name:ident < $owner:ty >) => { 173 | pub struct $name { 174 | #[allow(dead_code)] 175 | pub(crate) owner: crate::rc::Weak<$owner>, 176 | } 177 | 178 | impl $name { 179 | #[allow(dead_code)] 180 | pub(crate) fn new(owner: crate::rc::Weak<$owner>) -> Self { Self { owner } } 181 | } 182 | }; 183 | } 184 | 185 | #[doc(hidden)] 186 | #[macro_export] 187 | macro_rules! decl_browser_event { 188 | ($name:ident) => { 189 | decl_event!($name); 190 | }; 191 | } 192 | 193 | #[doc(hidden)] 194 | #[macro_export] 195 | macro_rules! def_event { 196 | ( $name:ident<$handle_type:ty, $owner_type:ty, $arg_type:ty> (&mut $this:ident, $arg_name:ident) $body:block ) => { 197 | impl crate::event::Event<$handle_type, $owner_type, $arg_type> for $name { 198 | fn register_handler(&mut $this, $arg_name: crate::event::EventHandler<$handle_type, $owner_type, $arg_type>) $body 199 | } 200 | } 201 | } 202 | 203 | #[doc(hidden)] 204 | #[macro_export] 205 | macro_rules! def_browser_event { 206 | ( $name:ident<$arg_type:ty> (&mut $this:ident, $arg_name:ident) $body:block ) => { 207 | def_event!($name (&mut $this, $arg_name) $body); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/javascript.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, fmt, str::FromStr}; 2 | 3 | use json::JsonValue; 4 | pub use num_bigfloat::BigFloat; 5 | 6 | /// A JavaScript value. 7 | #[derive(Clone, Debug)] 8 | pub enum JsValue { 9 | Array(Vec), 10 | Boolean(bool), 11 | Null, 12 | Number(BigFloat), 13 | Object(HashMap), 14 | String(String), 15 | Undefined, 16 | /// When a javascript value is returned that does not fit any of the other 17 | /// value types in this enum, `JsValue::Other` is returned with a string 18 | /// representation of the value. For example, functions get returned as an 19 | /// instance of `JsValue::Other`. 20 | Other(String), 21 | } 22 | 23 | impl JsValue { 24 | /// Parses the given JSON string into a `JsValue`. If parsing failed, the 25 | /// string is returned as a `JsValue::Other`. 26 | pub fn from_json(string: &str) -> Self { 27 | match json::parse(string) { 28 | Err(_) => JsValue::Other(string.to_string()), 29 | Ok(value) => Self::_from_json(value), 30 | } 31 | } 32 | 33 | pub fn from_string(string: &str) -> Self { 34 | if string.len() == 0 { 35 | return Self::Other(String::new()); 36 | } 37 | 38 | // If the symbol starts with a digit, interpret it as a (positive) number 39 | if "0123456789".contains(|c| c == string.chars().nth(0).unwrap()) { 40 | return match BigFloat::from_str(string) { 41 | Err(_) => Self::Other(format!("unable to parse number: {}", string)), 42 | Ok(f) => Self::Number(f), 43 | }; 44 | } 45 | if string == "null" { 46 | return Self::Null; 47 | } 48 | if string == "undefined" { 49 | return Self::Undefined; 50 | } 51 | if string == "true" { 52 | return Self::Boolean(true); 53 | } 54 | if string == "false" { 55 | return Self::Boolean(false); 56 | } 57 | if string.chars().nth(0) == Some('\"') && string.chars().last() == Some('\"') { 58 | return Self::String(string[1..(string.len() - 1)].to_string()); 59 | } 60 | if string.chars().nth(0) == Some('[') && string.chars().last() == Some(']') { 61 | let mut array = Vec::new(); 62 | for part in string[1..(string.len() - 1)].split(',') { 63 | array.push(Self::from_string(part)); 64 | } 65 | return Self::Array(array); 66 | } 67 | if string.chars().nth(0) == Some('{') && string.chars().last() == Some('}') { 68 | let mut map = HashMap::new(); 69 | for part in string[1..(string.len() - 1)].split(',') { 70 | if let Some((key, value)) = part.split_once(':') { 71 | map.insert(key.to_string(), Self::from_string(value)); 72 | } 73 | } 74 | return Self::Object(map); 75 | } 76 | 77 | Self::Other(string.to_string()) 78 | } 79 | 80 | fn _from_json(value: JsonValue) -> Self { 81 | match value { 82 | JsonValue::Null => Self::Null, 83 | JsonValue::Short(s) => Self::String(s.to_string()), 84 | JsonValue::String(s) => { 85 | println!("S '{}'", s); 86 | Self::String(s) 87 | } 88 | JsonValue::Number(n) => { 89 | let (sign, mantissa, exponent) = n.as_parts(); 90 | 91 | let big: BigFloat = mantissa.into(); 92 | for _i in 0..exponent { 93 | big.mul(&10.into()); 94 | } 95 | if !sign { 96 | big.inv_sign(); 97 | } 98 | Self::Number(big) 99 | } 100 | JsonValue::Boolean(b) => Self::Boolean(b), 101 | JsonValue::Object(o) => { 102 | let mut map = HashMap::with_capacity(o.len()); 103 | for (key, value) in o.iter() { 104 | map.insert(key.to_string(), Self::_from_json(value.clone())); 105 | } 106 | Self::Object(map) 107 | } 108 | JsonValue::Array(a) => 109 | Self::Array(a.into_iter().map(|i| Self::_from_json(i)).collect()), 110 | } 111 | } 112 | 113 | /// Gets the string of the `JsValue::String`, or otherwise just a normal 114 | /// string representation of the value. 115 | pub fn to_string_unenclosed(&self) -> Cow<'_, str> { 116 | match self { 117 | Self::String(s) => Cow::Borrowed(s), 118 | other => Cow::Owned(other.to_string()), 119 | } 120 | } 121 | } 122 | 123 | impl fmt::Display for JsValue { 124 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 125 | match self { 126 | Self::Array(a) => { 127 | write!(f, "[")?; 128 | if a.len() > 0 { 129 | write!(f, "{}", a[0])?; 130 | for i in 1..a.len() { 131 | write!(f, ",{}", a[i])?; 132 | } 133 | } 134 | write!(f, "]") 135 | } 136 | Self::Boolean(b) => write!(f, "{}", b), 137 | Self::Number(n) => write!(f, "{}", n), 138 | Self::Object(o) => { 139 | write!(f, "{{")?; 140 | for (k, v) in o.iter() { 141 | write!(f, "\"{}\":{}", k, v)?; 142 | } 143 | write!(f, "}}") 144 | } 145 | Self::Null => write!(f, "null"), 146 | Self::String(s) => write!(f, "\"{}\"", escape_string(s)), 147 | Self::Undefined => write!(f, "undefined"), 148 | Self::Other(code) => write!(f, "{}", code), 149 | } 150 | } 151 | } 152 | 153 | const UNESCAPED_CHARACTERS: &str = 154 | " -_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,./:;<=>?@[]^`{|}~"; 155 | 156 | fn escape_string(string: &str) -> Cow<'_, str> { 157 | if string.len() == 0 { 158 | return Cow::Borrowed(string); 159 | } 160 | 161 | let mut result = String::with_capacity(string.len() * 2); 162 | let mut escaped = 0; 163 | for char in string.chars() { 164 | // TODO: Clean up this code 165 | if !UNESCAPED_CHARACTERS.contains(char) { 166 | escaped += 1; 167 | if char == '\n' { 168 | result.push('\\'); 169 | result.push('n'); 170 | } else if char == '\'' { 171 | result.push('\\'); 172 | result.push('\''); 173 | } else if char == '"' { 174 | result.push('\\'); 175 | result.push('"'); 176 | } else if char == '\r' { 177 | result.push('\\'); 178 | result.push('r'); 179 | } else if char == '\t' { 180 | result.push('\\'); 181 | result.push('t'); 182 | } else if char == '\r' { 183 | result.push('\\'); 184 | result.push('r'); 185 | } else if char == '\x08' { 186 | result.push('\\'); 187 | result.push('b'); 188 | } else if char == '\x0c' { 189 | result.push('\\'); 190 | result.push('f'); 191 | } else if char == '\x0b' { 192 | result.push('\\'); 193 | result.push('v'); 194 | } else if char == '\0' { 195 | result.push('\\'); 196 | result.push('0'); 197 | } else if (char as u32) < 256 { 198 | let codepoint = char as u32; 199 | result.push('\\'); 200 | result.push('x'); 201 | result.push(char::from_digit(codepoint >> 4, 16).unwrap()); 202 | result.push(char::from_digit(codepoint & 0x0F, 16).unwrap()); 203 | } else { 204 | result.extend(char.escape_unicode().into_iter()); 205 | } 206 | } else { 207 | result.push(char); 208 | } 209 | } 210 | 211 | // Even though we've already created a new string, we still save memory by 212 | // dropping it: 213 | if escaped == 0 { 214 | Cow::Borrowed(string) 215 | } else { 216 | Cow::Owned(result) 217 | } 218 | } 219 | 220 | 221 | #[cfg(test)] 222 | mod tests { 223 | use super::*; 224 | 225 | #[test] 226 | fn test_string_escaping() { 227 | let input = " test\r\n\t\x08\x0b\x0c\0\x7f\u{1234}❤©"; 228 | let output = " test\\r\\n\\t\\b\\v\\f\\0\\x7f\\u{1234}\\u{2764}\\xa9"; 229 | assert_eq!(output, escape_string(input)) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! _BrowserWindow_ is a Rust crate that allows you to utilize a browser engine 2 | //! to build graphical interfaces, similar to Electron.js. 3 | //! You create your user interface with HTML, CSS and JavaScript. 4 | //! Then, you can communicate information to JavaScript and back to Rust. 5 | //! 6 | //! Pick the underlying browser framework by setting feature `cef`, `webkitgtk` 7 | //! or `edge2`. For more info on which on you should choose and how to set them 8 | //! up, check [this guide](https://github.com/bamidev/browser-window/tree/master/docs/GETTING-STARTED.md). 9 | 10 | //! # Getting Started 11 | //! To start building apps with Browser Window, take a look at the 12 | //! [`application`](application/index.html) module, or this quick example: 13 | //! ```no_run 14 | //! use browser_window::{application::*, browser::*, prelude::*}; 15 | //! 16 | //! fn main() { 17 | //! let app = Application::initialize(&ApplicationSettings::default()).unwrap(); 18 | //! let runtime = app.start(); 19 | //! runtime.run_async(|app| async move { 20 | //! let mut bwb = BrowserWindowBuilder::new(Source::File("file:///my-file.html".into())); 21 | //! bwb.dev_tools(true); 22 | //! bwb.size(800, 600); 23 | //! bwb.title("Example"); 24 | //! let bw = bwb.build_async(&app).await; 25 | //! bw.on_message().register_async(|h, e| async move { 26 | //! match e.cmd.as_str() { 27 | //! "command_one" => { 28 | //! h.eval_js(&format!( 29 | //! "js_function({}, {}, {})", 30 | //! 1, 31 | //! "'two'", 32 | //! JsValue::String("𝟛\n".into()) // Gets properly formatted to a JS string 33 | //! // literal 34 | //! )); 35 | //! } 36 | //! "command_two" => { 37 | //! // ... do something else here ... 38 | //! } 39 | //! _ => {} 40 | //! } 41 | //! }); 42 | //! 43 | //! bw.show(); 44 | //! }); 45 | //! app.finish(); 46 | //! } 47 | //! ``` 48 | //! 49 | //! For more detailed example code, see [this example code](https://github.com/bamidev/browser-window/tree/master/examples). 50 | //! 51 | //! # Thread safety 52 | //! To use the threadsafe version of _BrowserWindow_, enable feature 53 | //! `threadsafe`. This will use [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) 54 | //! instead of [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) 55 | //! internally, and will enable the 56 | //! [`BrowserWindowThreaded`](browser/struct.BrowserWindow.html) and 57 | //! [`ApplicationHandleThreaded`](browser/struct.BrowserWindowThreaded.html) 58 | //! handles. It will also require closures to be `Send`. Docs.rs will show the 59 | //! threadsafe versions of everything. If you need to know how everything is 60 | //! compiled in the non-threadsafe version, you need to invoke `cargo doc 61 | //! --open` in the git repo yourself. 62 | //! 63 | //! # Events 64 | //! To learn how to use events, take a quick look at the 65 | //! [`event`](event/index.html) module. 66 | 67 | mod core; 68 | #[cfg(test)] 69 | mod tests; 70 | 71 | pub mod application; 72 | pub mod browser; 73 | pub mod cookie; 74 | pub mod error; 75 | pub mod event; 76 | pub mod javascript; 77 | pub mod prelude; 78 | pub(crate) mod rc; 79 | pub mod window; 80 | 81 | #[cfg(feature = "threadsafe")] 82 | mod delegate; 83 | #[cfg(feature = "threadsafe")] 84 | pub use delegate::{DelegateError, DelegateFuture, DelegateFutureFuture}; 85 | 86 | mod common; 87 | pub use common::*; 88 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Some common traits that need to be often available. 2 | 3 | #[cfg(feature = "threadsafe")] 4 | pub use super::delegate::{DelegateError, DelegateFuture, DelegateFutureFuture}; 5 | pub use crate::{core::prelude::*, event::EventExt, javascript::JsValue}; 6 | -------------------------------------------------------------------------------- /src/rc.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "threadsafe"))] 2 | pub(crate) type Weak = std::rc::Weak; 3 | #[cfg(feature = "threadsafe")] 4 | pub(crate) type Weak = std::sync::Weak; 5 | #[cfg(not(feature = "threadsafe"))] 6 | pub(crate) type Rc = std::rc::Rc; 7 | #[cfg(feature = "threadsafe")] 8 | pub(crate) type Rc = std::sync::Arc; 9 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | time::{Duration, SystemTime, UNIX_EPOCH}, 4 | }; 5 | 6 | #[cfg(feature = "threadsafe")] 7 | use tokio; 8 | 9 | use crate::{application::*, browser::*, cookie::*}; 10 | 11 | #[test] 12 | fn tests() { 13 | let exec_path = env::current_dir() 14 | .unwrap() 15 | .join("target/debug/browser-window-se"); 16 | 17 | let settings = ApplicationSettings { 18 | engine_seperate_executable_path: Some(exec_path), 19 | resource_dir: None, 20 | remote_debugging_port: None, 21 | }; 22 | 23 | let app = Application::initialize(&settings).expect("unable to initialize application"); 24 | 25 | // Instead of marking each test with #[test], there is one actual test that runs 26 | // all different 'test' functions. This is because the Browser Window 27 | // application can only be initialized once. Also, because `Application` is not 28 | // `Send`, we can not use it acros multiple tests because they are ran in 29 | // parallel. 30 | async_tests(&app); 31 | #[cfg(feature = "threadsafe")] 32 | threaded_tests(&app); 33 | } 34 | 35 | #[cfg(feature = "threadsafe")] 36 | fn threaded_tests(application: &Application) { 37 | let bw_runtime = application.start(); 38 | 39 | let tokio_runtime = tokio::runtime::Runtime::new().unwrap(); 40 | 41 | // First run our own runtime on the main thread 42 | bw_runtime.run(|_app| { 43 | let app = _app.into_threaded(); 44 | 45 | // Spawn the main logic into the tokio runtime 46 | tokio_runtime.spawn(async move { 47 | // TODO: run tests here... 48 | 49 | app.exit(0); 50 | }); 51 | }); 52 | } 53 | 54 | fn async_tests(application: &Application) { 55 | let runtime = application.start(); 56 | 57 | let exit_code = runtime.run_async(|app| async move { 58 | let _bw = async_basic(&app).await; 59 | #[cfg(not(feature = "webkitgtk"))] 60 | async_cookies(&app).await; 61 | //async_correct_parent_cleanup(app).await; 62 | app.exit(0); 63 | }); 64 | 65 | assert!(exit_code == 0); 66 | } 67 | 68 | async fn async_basic(app: &ApplicationHandle) -> BrowserWindow { 69 | let mut bwb = BrowserWindowBuilder::new(Source::Url("https://www.duckduckgo.com/".into())); 70 | bwb.title("Basic Async Test"); 71 | return bwb.build(&app).await; 72 | } 73 | 74 | async fn async_cookies(app: &ApplicationHandle) { 75 | if let Some(mut jar) = app.cookie_jar() { 76 | let cookie = Cookie::new("name", "value"); 77 | 78 | // Store cookies 79 | jar.store("/", &cookie).await.unwrap_err(); // Should give error 80 | jar.store("http://localhost/", &cookie).await.unwrap(); 81 | 82 | // Delete cookie 83 | assert!(jar.delete("http://localhost/", "name").await == 1); 84 | jar.store("http://localhost/", &cookie).await.unwrap(); 85 | assert!(jar.delete_all("name").await == 1); 86 | jar.store("http://localhost/", &cookie).await.unwrap(); 87 | assert!(jar.clear("http://localhost/").await == 1); 88 | jar.store("http://localhost/", &cookie).await.unwrap(); 89 | assert!(jar.clear_all().await == 1); 90 | 91 | // Using a wrong url 92 | { 93 | let mut iter = jar.iter("/", true); 94 | assert!(iter.next().await.is_none()); 95 | } 96 | 97 | // Finding our set cookie back 98 | jar.store("http://localhost/", &cookie).await.unwrap(); 99 | let cookie = jar.find("http://localhost/", "name", true).await.unwrap(); 100 | assert!(cookie.name() == "name"); 101 | assert!(cookie.value() == "value"); 102 | 103 | // Finding our set cookie back in another way 104 | let cookie = jar.find_from_all("name").await.unwrap(); 105 | assert!(cookie.name() == "name"); 106 | assert!(cookie.value() == "value"); 107 | } 108 | } 109 | 110 | #[test] 111 | /// Checking if all cookie methods work correctly. 112 | fn cookie() { 113 | if !cfg!(feature = "webkitgtk") { 114 | let now = SystemTime::now(); 115 | 116 | let mut cookie = Cookie::new("name", "value"); 117 | 118 | assert!(cookie.name() == "name"); 119 | assert!(cookie.value() == "value"); 120 | assert!(cookie.domain() == ""); 121 | assert!(cookie.path() == ""); 122 | assert!(cookie.expires() == None); 123 | 124 | cookie 125 | .make_secure() 126 | .make_http_only() 127 | .set_path("/") 128 | .set_domain("127.0.0.1") 129 | .set_expires(&now) 130 | .set_creation_time(&now); 131 | 132 | assert!(cookie.domain() == "127.0.0.1"); 133 | assert!(cookie.path() == "/"); 134 | assert!( 135 | (now.duration_since(UNIX_EPOCH).unwrap() 136 | - cookie 137 | .expires() 138 | .unwrap() 139 | .duration_since(UNIX_EPOCH) 140 | .unwrap()) < Duration::from_secs(1) 141 | ); 142 | assert!( 143 | (now.duration_since(UNIX_EPOCH).unwrap() 144 | - cookie.creation_time().duration_since(UNIX_EPOCH).unwrap()) 145 | < Duration::from_secs(1) 146 | ); 147 | } 148 | } 149 | 150 | /// Closes a parent window before closing its child window, to see if the child 151 | /// window handle still is valid and doesn't cause any memory issues. 152 | async fn async_correct_parent_cleanup(app: ApplicationHandle) { 153 | // First create the parent 154 | let mut bwb_parent = 155 | BrowserWindowBuilder::new(Source::Url("https://www.duckduckgo.com/".into())); 156 | bwb_parent.title("Parent Window"); 157 | let bw_parent = bwb_parent.build(&app).await; 158 | 159 | // Then a child 160 | let mut bwb_child = BrowserWindowBuilder::new(Source::Url("https://www.google.com/".into())); 161 | bwb_child.title("Child Window"); 162 | bwb_child.parent(&bw_parent); 163 | let _bw_child = bwb_child.build(&app).await; 164 | } 165 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | //! This module contains all window related functionality. 2 | 3 | mod builder; 4 | 5 | pub use builder::WindowBuilder; 6 | 7 | pub use super::core::window::WindowExt; 8 | use super::prelude::*; 9 | 10 | 11 | /// A handle that exposes all windowing functionality. 12 | pub struct WindowHandle(pub(super) WindowImpl); 13 | 14 | 15 | impl WindowHandle { 16 | #[cfg(feature = "threadsafe")] 17 | pub(crate) unsafe fn clone(&self) -> Self { Self(self.0.clone()) } 18 | 19 | pub(super) fn new(inner: WindowImpl) -> Self { Self(inner) } 20 | 21 | pub fn content_dimensions(&self) -> Dims2D { self.0.content_dimensions() } 22 | 23 | pub fn opacity(&self) -> u8 { self.0.opacity() } 24 | 25 | pub fn position(&self) -> Pos2D { self.0.position() } 26 | 27 | pub fn title(&self) -> String { self.0.title() } 28 | 29 | pub fn window_dimensions(&self) -> Dims2D { self.0.window_dimensions() } 30 | 31 | /// Hides the window. 32 | /// Keep in mind that hiding the window is not the same as closing it. 33 | /// Hiding the window will keep it's resources alive. 34 | /// If the window is hidden, and all window handles are gone, the memory is 35 | /// effectively leaked. 36 | pub fn hide(&self) { self.0.hide(); } 37 | 38 | pub fn set_content_dimensions(&self, dimensions: Dims2D) { 39 | self.0.set_content_dimensions(dimensions); 40 | } 41 | 42 | pub fn set_opacity(&self, opacity: u8) { self.0.set_opacity(opacity); } 43 | 44 | pub fn set_position(&self, position: Pos2D) { self.0.set_position(position); } 45 | 46 | pub fn set_title(&self, title: &str) { self.0.set_title(title); } 47 | 48 | pub fn set_window_dimensions(&self, dimensions: Dims2D) { 49 | self.0.set_window_dimensions(dimensions); 50 | } 51 | 52 | /// Shows a window if it was hidden. 53 | /// Windows that were just created are hidden to start. 54 | /// This method is necessary to show it to the user. 55 | pub fn show(&self) { self.0.show(); } 56 | } 57 | -------------------------------------------------------------------------------- /src/window/builder.rs: -------------------------------------------------------------------------------- 1 | use unsafe_send_sync::UnsafeSend; 2 | 3 | use crate::{application::*, core::prelude::*, window::*, HasHandle}; 4 | 5 | 6 | /// Exposes functionality related to constructing a window. 7 | pub struct WindowBuilder { 8 | pub(crate) borders: bool, 9 | pub(crate) height: Option, 10 | pub(crate) minimizable: bool, 11 | pub(crate) parent: Option>, 12 | pub(crate) resizable: bool, 13 | pub(crate) title: Option, 14 | pub(crate) width: Option, 15 | } 16 | 17 | #[allow(dead_code)] 18 | pub type WindowOptions = cbw_WindowOptions; 19 | 20 | impl WindowBuilder { 21 | /// Sets whether or not the window has borders. 22 | /// Default is true. 23 | pub fn borders(&mut self, value: bool) { self.borders = value; } 24 | 25 | // TODO: Create a Window struct that can be created with this method. 26 | #[allow(dead_code)] 27 | fn build(self, app: ApplicationHandle) { 28 | // Title 29 | let title: &str = match self.title.as_ref() { 30 | None => "", 31 | Some(t) => t, 32 | }; 33 | 34 | // Convert options to the FFI struct 35 | let window_options = WindowOptions { 36 | borders: self.borders, 37 | minimizable: self.minimizable, 38 | resizable: self.resizable, 39 | }; 40 | 41 | // Unwrap the parent ffi handle 42 | let parent_impl_handle = match self.parent { 43 | None => WindowImpl::default(), 44 | Some(parent) => (*parent).clone(), 45 | }; 46 | 47 | let _impl_handle = WindowImpl::new( 48 | app.inner, 49 | parent_impl_handle, 50 | title.into(), 51 | self.width as _, 52 | self.height as _, 53 | &window_options, 54 | ); 55 | } 56 | 57 | /// Sets the height that the browser window will be created with initially 58 | pub fn height(&mut self, height: u32) { self.height = Some(height); } 59 | 60 | /// Sets whether or not the window has a minimize button on the title bar 61 | /// Default is true 62 | pub fn minimizable(&mut self, value: bool) { self.minimizable = value; } 63 | 64 | /// Configure a parent window. 65 | /// When a parent window closes, this browser window will close as well. 66 | /// This could be a reference to a `Browser` or `BrowserThreaded` handle. 67 | pub fn parent(&mut self, bw: &W) 68 | where 69 | W: HasHandle, 70 | { 71 | self.parent = Some(UnsafeSend::new(bw.handle().0.clone())); 72 | } 73 | 74 | pub fn new() -> Self { 75 | Self { 76 | borders: true, 77 | height: None, 78 | minimizable: true, 79 | parent: None, 80 | resizable: true, 81 | title: None, 82 | width: None, 83 | } 84 | } 85 | 86 | /// Sets the width and height of the browser window 87 | pub fn size(&mut self, width: u32, height: u32) { 88 | self.width = Some(width); 89 | self.height = Some(height); 90 | } 91 | 92 | /// Sets the title of the window. 93 | pub fn title>(&mut self, title: S) { self.title = Some(title.into()); } 94 | 95 | /// Sets the width that the browser window will be created with initially. 96 | pub fn width(&mut self, width: u32) { self.width = Some(width); } 97 | 98 | /// Sets whether or not the window will be resizable. 99 | /// Default is true. 100 | pub fn resizable(&mut self, resizable: bool) { self.resizable = resizable; } 101 | } 102 | --------------------------------------------------------------------------------