From 821f03acb0eb8894f0e115c92c3310701963ccf2 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Sun, 2 Feb 2020 05:15:05 +0000 Subject: [PATCH 01/87] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e7d354c..eee4c90 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,4 @@ Todo - Get the example_host into workable condition. It's disabled at the moment - Cleanup. Everything is quite messy. - macOS. These builds require quite a few unique things so they're not handled by the CMake scripts at all at the moment. It's still technically possible to get the builds working, though. +- Dynamic loading of the HTML implementation. Atm we just use dylib() or LoadLibrary() in each host which is kind of lame. It'd be nice to simplify it. From 3a06e1aa38e6caad606a54fa990ed1f01ac35771 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Tue, 3 Mar 2020 16:52:12 +0000 Subject: [PATCH 02/87] Update to CEF '80.0.4+g74f7b0c+chromium-80.0.3987.122' --- CMakeLists.txt | 8 ++++---- README.md | 8 ++++---- html_chromium/CMakeLists.txt | 6 ++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 415769f..ea45888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_79.1.31+gfc9ef34+chromium-79.0.3945.117_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_79.1.31+gfc9ef34+chromium-79.0.3945.117_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_79.1.31+gfc9ef34+chromium-79.0.3945.117_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") @@ -89,7 +89,7 @@ else() endif() # Our projects -# add_subdirectory(example_host) +add_subdirectory(example_host) add_subdirectory(html) add_subdirectory(html_stub) add_subdirectory(html_chromium) diff --git a/README.md b/README.md index eee4c90..af0d4ed 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ To work with this project you will need a build of the Chromium Embedded Framewo | Platform | URL | | -------- | --- | -| Windows x86 | https://files.facepunch.com/willox/46c89e07-a257-4988-96e8-81e945284469/cef_binary_79.1.31%2Bgfc9ef34%2Bchromium-79.0.3945.117_windows32.tar.bz2 | -| Windows x64 | https://files.facepunch.com/willox/8750c07c-4888-4a79-8449-a01bd6e851f7/cef_binary_79.1.31%2Bgfc9ef34%2Bchromium-79.0.3945.117_windows64.tar.bz2 | -| Linux x64 | https://files.facepunch.com/willox/3004a977-b227-4a20-bbfb-e698913d8a71/cef_binary_79.1.31%2Bgfc9ef34%2Bchromium-79.0.3945.117_linux64.tar.bz2 | -| macOS x64 | https://files.facepunch.com/willox/7fd05979-e9ad-4b94-8d85-0014bacb170c/cef_binary_79.1.35%2Bgfebbb4a%2Bchromium-79.0.3945.130_macosx64.tar.bz2 | +| Windows x86 | https://files.facepunch.com/willox/7bb55f57-7c70-4140-83d7-2d61d8b57816/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_windows32.tar.bz2 | +| Windows x64 | https://files.facepunch.com/willox/14ceed65-8809-4fba-97a7-2d524b6d45ec/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_windows64.tar.bz2 | +| Linux x64 | https://files.facepunch.com/willox/dfd5d7fe-2184-40a9-90b0-49f9eef9a9b5/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_linux64.tar.bz2 | +| macOS x64 | https://files.facepunch.com/willox/17e19274-4112-4734-ac20-22cd8644bcb4/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_macosx64.tar.bz2 | These belong in the `./thirdparty/cef3/` directory (after extraction.) The paths are currently hardcoded into the root `CMakelists.txt` file. diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index 6d0a958..f7d3227 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -5,7 +5,6 @@ if(WIN32) ${CEF_PATH}/Release/libcef.dll ${CEF_PATH}/Release/libEGL.dll ${CEF_PATH}/Release/libGLESv2.dll - ${CEF_PATH}/Release/natives_blob.bin ${CEF_PATH}/Release/snapshot_blob.bin ${CEF_PATH}/Release/v8_context_snapshot.bin ${CEF_PATH}/Resources/icudtl.dat) @@ -15,7 +14,6 @@ elseif(UNIX AND NOT APPLE) ${CEF_PATH}/Release/libcef.so ${CEF_PATH}/Release/libEGL.so ${CEF_PATH}/Release/libGLESv2.so - ${CEF_PATH}/Release/natives_blob.bin ${CEF_PATH}/Release/snapshot_blob.bin ${CEF_PATH}/Release/v8_context_snapshot.bin ${CEF_PATH}/Resources/icudtl.dat) @@ -31,8 +29,8 @@ set(RESOURCES ${CEF_PATH}/Resources/devtools_resources.pak) install(FILES ${BINARIES} DESTINATION ${GAME_BIN_DIR}) -install(FILES ${RESOURCES} DESTINATION chromium) -install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION chromium) +install(FILES ${RESOURCES} DESTINATION ${CMAKE_BINARY_DIR}/bin/chromium) +install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION ${CMAKE_BINARY_DIR}/bin/chromium) # Actual lib set(SOURCES From 333c476bad117a3737de59f6d5a127775cec593d Mon Sep 17 00:00:00 2001 From: William Wallace Date: Tue, 3 Mar 2020 18:37:08 +0000 Subject: [PATCH 03/87] useragents --- html_chromium/ChromiumSystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 4c497f1..d9e3c4a 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -156,7 +156,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); @@ -164,7 +164,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -178,7 +178,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OSX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Intel Mac OS X; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Intel Mac OS X; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); #else #error #endif From 3abf94e860cc28ba42214f8161f9d91c87abf713 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Wed, 4 Mar 2020 21:59:34 +0000 Subject: [PATCH 04/87] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index af0d4ed..b6abf97 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # GMod HTML This is pretty much just an abstraction layer around The Chromium Embedded Framework (https://bitbucket.org/chromiumembedded/cef) -Note: None of this works with the current release of GMod. You'll have to wait for the full 64-bit release before using these with a live build of GMod. ## Chromium Embedded Framework Binary Distribution To work with this project you will need a build of the Chromium Embedded Framework. You can download the builds used by Garry's Mod here if you don't want to compile your own: From c45043c8120aa61485d60bd76b660ec0362aed5f Mon Sep 17 00:00:00 2001 From: William Wallace Date: Wed, 4 Mar 2020 22:49:13 +0000 Subject: [PATCH 05/87] re-enable http imports for now --- html_chromium/ChromiumSystem.cpp | 3 +++ html_chromium/chromium_process/ChromiumApp.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index d9e3c4a..32a9daf 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -52,6 +52,9 @@ class ChromiumApp // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); + + // Chromium 80 removed this but only sometimes. + command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); } void OnRegisterCustomSchemes( CefRawPtr registrar ) override diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 5d6562d..35bf902 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -190,6 +190,9 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); + + // Chromium 80 removed this but only sometimes. + command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); } void ChromiumApp::OnRegisterCustomSchemes( CefRawPtr registrar ) From 61955e38875012d1f3d78123485db81caa7872cf Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 5 Mar 2020 03:57:30 -0500 Subject: [PATCH 06/87] Updated CEF Binaries for Windows to "cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122", and Updated .gitignore to ignore *all* of /thirdparty/cef3/ as well as /build_x64 and /build_x86 --- .gitignore | 6 ++++-- CMakeLists.txt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3d43c45..7ade018 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # The directory I like to build under /build +/build_x64 +/build_x86 # Dependencies that are downloaded/installed separately -/thirdparty/cef3/* -!/thirdparty/cef3/README.txt \ No newline at end of file +/thirdparty/cef3/** +!/thirdparty/cef3/README.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ea45888..f172c4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,9 +54,9 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122_windows64) endif() elseif(UNIX AND NOT APPLE) set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_linux64) From 19126d9fb6909d5980446110e08e0391a79e4f51 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Thu, 5 Mar 2020 18:07:27 +0000 Subject: [PATCH 07/87] temporary workaround to stop site-isolation from destroying RegisteredFunctions state in render process --- html_chromium/ChromiumSystem.cpp | 3 +++ html_chromium/chromium_process/ChromiumApp.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 32a9daf..e8f9088 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -55,6 +55,9 @@ class ChromiumApp // Chromium 80 removed this but only sometimes. command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); + + // Disable site isolation until we implement passing registered Lua functions between processes + command_line->AppendSwitch( "disable-site-isolation-trials" ); } void OnRegisterCustomSchemes( CefRawPtr registrar ) override diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 35bf902..ee78284 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -193,6 +193,9 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, // Chromium 80 removed this but only sometimes. command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); + + // Disable site isolation until we implement passing registered Lua functions between processes + command_line->AppendSwitch( "disable-site-isolation-trials" ); } void ChromiumApp::OnRegisterCustomSchemes( CefRawPtr registrar ) From 730d5b816070c9ffcc6e575d423cb1cf259ad5a5 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 5 Mar 2020 13:27:51 -0500 Subject: [PATCH 08/87] Disable building example_host since it doesn't even work right now --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f172c4e..24dd759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ else() endif() # Our projects -add_subdirectory(example_host) +#add_subdirectory(example_host) add_subdirectory(html) add_subdirectory(html_stub) add_subdirectory(html_chromium) From 28a75a9c9590707235cf8a470b6f8c0a0557675c Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 26 Mar 2020 03:23:21 -0400 Subject: [PATCH 09/87] Software WebGL is now enabled (after a huge amount of effort, can't get GPU acceleration to work with GMod CEF at all), WIP making Linux builds work, and some style fixes --- CMakeLists.txt | 8 +- html_chromium/ChromiumSystem.cpp | 4 +- .../chromium_process/ChromiumApp.cpp | 388 +++++++++--------- 3 files changed, 200 insertions(+), 200 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24dd759..f827e5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ else() cmake_minimum_required(VERSION 2.8.7) endif() -project(gmod-html LANGUAGES CXX) +project(gmod-html) # We can only do release builds set(CMAKE_CONFIGURATION_TYPES Release Debug) @@ -56,10 +56,10 @@ if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.8+gf96cd1d+chromium-80.0.3987.122_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.4+g74f7b0c+chromium-80.0.3987.122_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.7+gd9fd476+chromium-80.0.3987.122_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") @@ -85,7 +85,7 @@ add_library(cef_sandbox STATIC IMPORTED) if(WIN32) set_target_properties(cef_sandbox PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/cef_sandbox.lib) else() - message(FATAL_ERROR "No cef_sandbox library set") + #message(FATAL_ERROR "No cef_sandbox library set") endif() # Our projects diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index e8f9088..c0bd266 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -275,7 +275,7 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) browserSettings.javascript_close_windows = STATE_DISABLED; browserSettings.universal_access_from_file_urls = STATE_DISABLED; browserSettings.file_access_from_file_urls = STATE_DISABLED; - browserSettings.webgl = STATE_DISABLED; + //browserSettings.webgl = STATE_DISABLED; CefRefPtr cefClient( new ChromiumBrowser ); @@ -360,4 +360,4 @@ JSValue ChromiumSystem::CreateHashMap( const char** pKeys, const size_t* pKeySiz } ChromiumSystem g_ChromiumSystem; -HTMLSYSTEM_EXPORT( g_ChromiumSystem ); \ No newline at end of file +HTMLSYSTEM_EXPORT( g_ChromiumSystem ); diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index ee78284..4d44962 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -69,9 +69,9 @@ static bool V8ValuesToCefList( CefRefPtr& outList, const CefV8Valu { outList->SetSize( inList.size() ); - size_t index = 0; - for ( const auto& x : inList ) - { + size_t index = 0; + for ( const auto& x : inList ) + { auto newValue = CefValue::Create(); if ( !V8ValueToCefValue( newValue, x ) ) @@ -79,9 +79,9 @@ static bool V8ValuesToCefList( CefRefPtr& outList, const CefV8Valu outList->SetValue( index, newValue ); index++; - } + } - return true; + return true; } static bool CefValueToV8Value( CefRefPtr& outValue, const CefRefPtr& inValue, int depth = 0 ) @@ -168,45 +168,45 @@ static bool CefListToV8Values( CefV8ValueList& outList, const CefRefPtr command_line ) { - command_line->AppendSwitch( "disable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); - command_line->AppendSwitch( "disable-smooth-scrolling" ); + command_line->AppendSwitch( "disable-gpu" ); + command_line->AppendSwitch( "disable-gpu-compositing" ); + command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 - command_line->AppendSwitch( "enable-begin-frame-scheduling" ); + command_line->AppendSwitch( "enable-begin-frame-scheduling" ); #endif - command_line->AppendSwitch( "enable-system-flash" ); + command_line->AppendSwitch( "enable-system-flash" ); - // This can interfere with posix signals and break Breakpad + // This can interfere with posix signals and break Breakpad #ifdef POSIX - command_line->AppendSwitch( "disable-in-process-stack-traces" ); + command_line->AppendSwitch( "disable-in-process-stack-traces" ); #endif #ifdef OSX - command_line->AppendSwitch( "use-mock-keychain" ); + command_line->AppendSwitch( "use-mock-keychain" ); #endif - // https://bitbucket.org/chromiumembedded/cef/issues/2400 - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents" ); + // https://bitbucket.org/chromiumembedded/cef/issues/2400 + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents" ); - // Auto-play media - command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); + // Auto-play media + command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); - // Chromium 80 removed this but only sometimes. - command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); + // Chromium 80 removed this but only sometimes. + command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); - // Disable site isolation until we implement passing registered Lua functions between processes - command_line->AppendSwitch( "disable-site-isolation-trials" ); + // Disable site isolation until we implement passing registered Lua functions between processes + command_line->AppendSwitch( "disable-site-isolation-trials" ); } void ChromiumApp::OnRegisterCustomSchemes( CefRawPtr registrar ) { - // TODO: are these bools what we want them to be? - registrar->AddCustomScheme( "asset", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CSP_BYPASSING ); + // TODO: are these bools what we want them to be? + registrar->AddCustomScheme( "asset", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CSP_BYPASSING ); } CefRefPtr ChromiumApp::GetRenderProcessHandler() { - return this; + return this; } // @@ -214,27 +214,27 @@ CefRefPtr ChromiumApp::GetRenderProcessHandler() // void ChromiumApp::OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) { - // - // CEF3 doesn't support implementing the print dialog, so we've gotta just remove window.print. - // - context->Enter(); - { - context->GetGlobal()->DeleteValue( "print" ); - - // Removing WebSQL for now - we can add it back after CEF3 has been updated - context->GetGlobal()->DeleteValue( "openDatabase" ); - - } - context->Exit(); - - // If this is a web worker, we want nothing to do with it - if ( !browser ) - return; - - for ( auto& pair : m_RegisteredFunctions ) - { - RegisterFunctionInFrame( frame, pair.first, pair.second ); - } + // + // CEF3 doesn't support implementing the print dialog, so we've gotta just remove window.print. + // + context->Enter(); + { + context->GetGlobal()->DeleteValue( "print" ); + + // Removing WebSQL for now - we can add it back after CEF3 has been updated + context->GetGlobal()->DeleteValue( "openDatabase" ); + + } + context->Exit(); + + // If this is a web worker, we want nothing to do with it + if ( !browser ) + return; + + for ( auto& pair : m_RegisteredFunctions ) + { + RegisterFunctionInFrame( frame, pair.first, pair.second ); + } } void ChromiumApp::OnContextReleased( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) @@ -244,26 +244,26 @@ void ChromiumApp::OnContextReleased( CefRefPtr browser, CefRefPtr browser, CefRefPtr frame, CefProcessId source_process, CefRefPtr message ) { - auto name = message->GetName(); - auto args = message->GetArgumentList(); - - if ( name == "RegisterFunction" ) - { - RegisterFunction( browser, args ); - return true; - } - - if ( name == "ExecuteCallback" ) - { - ExecuteCallback( browser, args ); - return true; - } - - if ( name == "ForgetCallback" ) - { - ForgetCallback( browser, args ); - return true; - } + auto name = message->GetName(); + auto args = message->GetArgumentList(); + + if ( name == "RegisterFunction" ) + { + RegisterFunction( browser, args ); + return true; + } + + if ( name == "ExecuteCallback" ) + { + ExecuteCallback( browser, args ); + return true; + } + + if ( name == "ForgetCallback" ) + { + ForgetCallback( browser, args ); + return true; + } if ( name == "ExecuteJavaScript" ) { @@ -271,7 +271,7 @@ bool ChromiumApp::OnProcessMessageReceived( CefRefPtr browser, CefRe return true; } - return false; + return false; } // @@ -279,34 +279,34 @@ bool ChromiumApp::OnProcessMessageReceived( CefRefPtr browser, CefRe // bool ChromiumApp::Execute( const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception ) { - auto context = CefV8Context::GetCurrentContext(); - auto browser = context->GetBrowser(); - - if ( !browser ) - { - // This could happen inside of a web worker (but our functions shouldn't be registered there) - exception = "CefV8Context::GetBrowser == nullptr"; - return true; - } - - // - // We need to rip the objName/funcName out of this function's name - // - std::string objName, funcName; - { - std::string fullName = name.ToString(); - size_t delimPos = fullName.find( '#' ); - - if ( delimPos == std::string::npos ) - { - // Shouldn't happen - exception = "ChromiumApp::Execute couldn't parse this function's name"; - return true; - } - - objName = fullName.substr( 0, delimPos ); - funcName = fullName.substr( delimPos + 1 ); - } + auto context = CefV8Context::GetCurrentContext(); + auto browser = context->GetBrowser(); + + if ( !browser ) + { + // This could happen inside of a web worker (but our functions shouldn't be registered there) + exception = "CefV8Context::GetBrowser == nullptr"; + return true; + } + + // + // We need to rip the objName/funcName out of this function's name + // + std::string objName, funcName; + { + std::string fullName = name.ToString(); + size_t delimPos = fullName.find( '#' ); + + if ( delimPos == std::string::npos ) + { + // Shouldn't happen + exception = "ChromiumApp::Execute couldn't parse this function's name"; + return true; + } + + objName = fullName.substr( 0, delimPos ); + funcName = fullName.substr( delimPos + 1 ); + } auto argsList = CefListValue::Create(); if ( !V8ValuesToCefList( argsList, arguments ) ) @@ -315,98 +315,98 @@ bool ChromiumApp::Execute( const CefString& name, CefRefPtr object, return true; } - CefRefPtr callback; + CefRefPtr callback; - auto message = CefProcessMessage::Create( "ExecuteFunction" ); - auto args = message->GetArgumentList(); + auto message = CefProcessMessage::Create( "ExecuteFunction" ); + auto args = message->GetArgumentList(); - args->SetString( 0, objName ); - args->SetString( 1, funcName ); + args->SetString( 0, objName ); + args->SetString( 1, funcName ); - // No callback = easy mode - if ( arguments.empty() || !arguments.back()->IsFunction() ) - { - args->SetInt( 2, -1 ); // Invalid callback id - args->SetList( 3, argsList ); - } - else - { - // Now register a callback index that Lua will send back to us later - int callbackId = CreateCallback( context, arguments.back() ); + // No callback = easy mode + if ( arguments.empty() || !arguments.back()->IsFunction() ) + { + args->SetInt( 2, -1 ); // Invalid callback id + args->SetList( 3, argsList ); + } + else + { + // Now register a callback index that Lua will send back to us later + int callbackId = CreateCallback( context, arguments.back() ); - // We pass argsList to Lua, so pop the callback off of it + // We pass argsList to Lua, so pop the callback off of it argsList->Remove( argsList->GetSize() - 1 ); - args->SetInt( 2, callbackId ); - args->SetList( 3, argsList ); - } + args->SetInt( 2, callbackId ); + args->SetList( 3, argsList ); + } - browser->GetMainFrame()->SendProcessMessage( PID_BROWSER, message ); - return true; + browser->GetMainFrame()->SendProcessMessage( PID_BROWSER, message ); + return true; } // int ChromiumApp::CreateCallback( CefRefPtr context, CefRefPtr func ) { - if ( m_Callbacks.find( m_NextCallbackId ) != m_Callbacks.end() ) - { - // We're overlapping? Probably shouldn't happen - return -1; - } + if ( m_Callbacks.find( m_NextCallbackId ) != m_Callbacks.end() ) + { + // We're overlapping? Probably shouldn't happen + return -1; + } - int callbackId = m_NextCallbackId; - Callback cb; - cb.Context = context; - cb.Function = func; + int callbackId = m_NextCallbackId; + Callback cb; + cb.Context = context; + cb.Function = func; - m_Callbacks.emplace( callbackId, cb ); + m_Callbacks.emplace( callbackId, cb ); - m_NextCallbackId++; - if ( m_NextCallbackId >= 16384 ) - m_NextCallbackId = 0; + m_NextCallbackId++; + if ( m_NextCallbackId >= 16384 ) + m_NextCallbackId = 0; - return callbackId; + return callbackId; } void ChromiumApp::RegisterFunctionInFrame( CefRefPtr frame, const CefString& objName, const CefString& funcName ) { - if ( !frame ) - return; - - auto context = frame->GetV8Context(); - - if ( !context ) - return; - - // - // We can only associate one string with a function in CEF. So we'll use "{objName}#{funcName}". - // - CefString fullName; - { - std::stringstream ss; - ss << objName.ToString(); - ss << '#'; - ss << funcName.ToString(); - fullName = CefString( ss.str() ); - } - - context->Enter(); - { - auto window = context->GetGlobal(); - auto obj = window->GetValue( objName ); - - // If our object doesn't exist, create it - if ( !obj || !obj->IsObject() ) - { - obj = CefV8Value::CreateObject( nullptr, nullptr ); - window->SetValue( objName, obj, V8_PROPERTY_ATTRIBUTE_NONE ); - } - - auto func = CefV8Value::CreateFunction( fullName, this ); - obj->SetValue( funcName, func, V8_PROPERTY_ATTRIBUTE_NONE ); - } - context->Exit(); + if ( !frame ) + return; + + auto context = frame->GetV8Context(); + + if ( !context ) + return; + + // + // We can only associate one string with a function in CEF. So we'll use "{objName}#{funcName}". + // + CefString fullName; + { + std::stringstream ss; + ss << objName.ToString(); + ss << '#'; + ss << funcName.ToString(); + fullName = CefString( ss.str() ); + } + + context->Enter(); + { + auto window = context->GetGlobal(); + auto obj = window->GetValue( objName ); + + // If our object doesn't exist, create it + if ( !obj || !obj->IsObject() ) + { + obj = CefV8Value::CreateObject( nullptr, nullptr ); + window->SetValue( objName, obj, V8_PROPERTY_ATTRIBUTE_NONE ); + } + + auto func = CefV8Value::CreateFunction( fullName, this ); + obj->SetValue( funcName, func, V8_PROPERTY_ATTRIBUTE_NONE ); + } + context->Exit(); } void ChromiumApp::ExecuteJavaScript( CefRefPtr browser, CefRefPtr args ) @@ -435,53 +435,53 @@ void ChromiumApp::ExecuteJavaScript( CefRefPtr browser, CefRefPtr browser, CefRefPtr args ) { - CefString objName = args->GetString( 0 ); - CefString funcName = args->GetString( 1 ); + CefString objName = args->GetString( 0 ); + CefString funcName = args->GetString( 1 ); - // Register this function in any frames that already exist - { - std::vector frames; - browser->GetFrameIdentifiers( frames ); + // Register this function in any frames that already exist + { + std::vector frames; + browser->GetFrameIdentifiers( frames ); - for ( auto frameId : frames ) - { - RegisterFunctionInFrame( browser->GetFrame( frameId ), objName, funcName ); - } - } + for ( auto frameId : frames ) + { + RegisterFunctionInFrame( browser->GetFrame( frameId ), objName, funcName ); + } + } - m_RegisteredFunctions.emplace_back( std::make_pair( objName, funcName ) ); + m_RegisteredFunctions.emplace_back( std::make_pair( objName, funcName ) ); } void ChromiumApp::ExecuteCallback( CefRefPtr browser, CefRefPtr args ) { - int callbackId = args->GetInt( 0 ); - auto it = m_Callbacks.find( callbackId ); - if ( it == m_Callbacks.end() ) - return; + int callbackId = args->GetInt( 0 ); + auto it = m_Callbacks.find( callbackId ); + if ( it == m_Callbacks.end() ) + return; - auto context = it->second.Context; - auto func = it->second.Function; + auto context = it->second.Context; + auto func = it->second.Function; - m_Callbacks.erase( it ); + m_Callbacks.erase( it ); - // Context has been destroyed for some reason - if ( !context->IsValid() ) - return; + // Context has been destroyed for some reason + if ( !context->IsValid() ) + return; auto argList = args->GetList( 1 ); - context->Enter(); - { + context->Enter(); + { CefV8ValueList arguments; if ( CefListToV8Values( arguments, argList ) ) { func->ExecuteFunction( nullptr, arguments ); - } - } - context->Exit(); + } + } + context->Exit(); } void ChromiumApp::ForgetCallback( CefRefPtr browser, CefRefPtr args ) { - m_Callbacks.erase( args->GetInt( 0 ) ); -} \ No newline at end of file + m_Callbacks.erase( args->GetInt( 0 ) ); +} From ce0d4471165c32cd05c272dd90450160f5c0524e Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 14 Jan 2021 19:00:00 -0500 Subject: [PATCH 10/87] - WIP Web Notification JS Stub - Changed log_severity to LOGSEVERITY_DEFAULT, since VERBOSE spews megabytes of log info - Disabled HardwareMediaKeys in chromium_process (Linux/macOS) as well --- CMakeLists.txt | 6 +-- html_chromium/ChromiumBrowser.h | 34 ++++++------ html_chromium/ChromiumSystem.cpp | 54 +++++++++++++++++-- .../chromium_process/ChromiumApp.cpp | 2 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f827e5d..31decfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.5+gdf7fb8e+chromium-80.0.3987.122_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.8+gf96cd1d+chromium-80.0.3987.122_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_80.0.7+gd9fd476+chromium-80.0.3987.122_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 2be7372..d611044 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -54,7 +54,7 @@ class ChromiumBrowser public: // - // CefClient interface + // CefClient interface // CefRefPtr GetLifeSpanHandler() override { return this; } CefRefPtr GetLoadHandler() override { return this; } @@ -85,9 +85,9 @@ class ChromiumBrowser CefRefPtr&, bool* ) override; -// -// CefLoadHandler interface -// + // + // CefLoadHandler interface + // void OnLoadStart( CefRefPtr, CefRefPtr frame, CefLoadHandler::TransitionType ) override; void OnLoadEnd( CefRefPtr, CefRefPtr frame, int httpStatusCode ) override; void OnLoadError( CefRefPtr, CefRefPtr frame, CefLoadHandler::ErrorCode errorCode, const CefString& errorText, const CefString& failedURL ) override; @@ -127,9 +127,9 @@ class ChromiumBrowser bool, bool ) override; -// -// CefResourceRequestHandler interface -// + // + // CefResourceRequestHandler interface + // ReturnValue OnBeforeResourceLoad( CefRefPtr, CefRefPtr, CefRefPtr request, @@ -140,17 +140,17 @@ class ChromiumBrowser CefRefPtr, bool& allow_os_execution ) override; -// -// CefContextMenuHandler interface -// + // + // CefContextMenuHandler interface + // void OnBeforeContextMenu( CefRefPtr, CefRefPtr, CefRefPtr, CefRefPtr model ) override; -// -// CefDialogHandler interface -// + // + // CefDialogHandler interface + // bool OnFileDialog( CefRefPtr, FileDialogMode, const CefString&, @@ -159,9 +159,9 @@ class ChromiumBrowser int, CefRefPtr callback ) override; -// -// CefJSDialogHandler -// + // + // CefJSDialogHandler + // bool OnJSDialog( CefRefPtr, const CefString&, JSDialogType, @@ -198,4 +198,4 @@ class ChromiumBrowser private: IMPLEMENT_REFCOUNTING( ChromiumBrowser ); -}; \ No newline at end of file +}; diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index c0bd266..96b2b05 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -21,8 +21,21 @@ namespace fs = std::experimental::filesystem; #endif +class EmptyV8Handler : public CefV8Handler { +public: + EmptyV8Handler() {} + + virtual bool Execute( const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception ) override + { + return true; + } + + IMPLEMENT_REFCOUNTING(EmptyV8Handler); +}; + class ChromiumApp : public CefApp + , public CefRenderProcessHandler { public: // @@ -48,7 +61,7 @@ class ChromiumApp #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents" ); + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); @@ -66,6 +79,37 @@ class ChromiumApp registrar->AddCustomScheme( "asset", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CSP_BYPASSING ); } + // + // CefRenderProcessHandler: This implements a Stub for Notifications, allowing Netflix to work + // + // TODO: Figure out why neither GetRenderProcessHandler nor OnContextCreated is being called (or is it and LOG just isn't working?) + /* + CefRefPtr GetRenderProcessHandler() override + { + LOG(ERROR) << "GetRenderProcessHandler"; + return this; + } + + void OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override + { + LOG(ERROR) << "OnContextCreated"; + + CefRefPtr object = context->GetGlobal(); + + CefRefPtr emptyFunc = new EmptyV8Handler(); + + CefRefPtr notificationFunc = CefV8Value::CreateFunction("Notification", emptyFunc); + + object->SetValue("Notification", notificationFunc, V8_PROPERTY_ATTRIBUTE_NONE); + + std::string notificationStubExtension = + "window.Notification = function() {};" + "window.Notification.requestPermission = function() { return new Promise(function(resolve, reject) { resolve('denied'); }) };" + "window.Notification.permission = 'denied';"; + + CefRegisterExtension("solsticegamestudios/notificationStub", notificationStubExtension, NULL); + } + */ private: IMPLEMENT_REFCOUNTING( ChromiumApp ); }; @@ -108,7 +152,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.no_sandbox = false; #endif settings.command_line_args_disabled = true; - settings.log_severity = LOGSEVERITY_VERBOSE; + settings.log_severity = LOGSEVERITY_DEFAULT; #ifdef _WIN32 // Chromium will be sad if we don't resolve any NTFS junctions for it @@ -162,7 +206,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); @@ -170,7 +214,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -184,7 +228,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OSX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Intel Mac OS X; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Intel Mac OS X; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); #else #error #endif diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 4d44962..380cb01 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -186,7 +186,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents" ); + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); From 97d5514012b3ae9bf386491a4e2831e1f52bf77a Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 10 Feb 2021 01:11:50 -0500 Subject: [PATCH 11/87] Implement scaffolding for OnAcceleratedPaint (once we can use it) and Enabled GPU acceleration but NOT GPU compositing (I don't know why it doesn't work) --- html_chromium/ChromiumBrowser.cpp | 11 ++++++++++- html_chromium/ChromiumBrowser.h | 1 + html_chromium/ChromiumSystem.cpp | 6 ++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 86c123c..d4541a3 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -684,7 +684,7 @@ void ChromiumBrowser::OnPopupSize( CefRefPtr, const CefRect& rect ) } void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height ) -{ +{ // // We blit the popup straight on to the main image. That means gmod won't need to use multiple textures (+1) // @@ -722,6 +722,15 @@ void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintEle } } +void ChromiumBrowser::OnAcceleratedPaint(CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, void* shared_handle) +{ + // TODO: Implement once fixed for OSR on Viz + // TODO: See ChromiumSystem::CreateClient + // https://bitbucket.org/chromiumembedded/cef/pull-requests/285/reimplement-shared-texture-support-for-viz + + LOG(ERROR) << "ChromiumBrowser::OnAcceleratedPaint"; +} + // // CefRequestHandler interface // diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index d611044..619eaca 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -107,6 +107,7 @@ class ChromiumBrowser void OnPopupShow( CefRefPtr, bool show ) override; void OnPopupSize( CefRefPtr, const CefRect& rect ) override; void OnPaint( CefRefPtr, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height ) override; + void OnAcceleratedPaint( CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, void* shared_handle ) override; // // CefRequestHandler interface diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 96b2b05..a7f3503 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -43,8 +43,8 @@ class ChromiumApp // void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override { - command_line->AppendSwitch( "disable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); + command_line->AppendSwitch( "enable-gpu" ); + command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); @@ -311,6 +311,8 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) { CefWindowInfo windowInfo; windowInfo.SetAsWindowless( 0 ); + // TODO: See ChromiumBrowser::OnAcceleratedPaint + //windowInfo.shared_texture_enabled = true; CefBrowserSettings browserSettings; CefString( &browserSettings.default_encoding ).FromString( "UTF-8" ); From 095d658c36e6daeaba9cbf599584daa5195dbf4e Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 5 Nov 2021 17:20:04 -0400 Subject: [PATCH 12/87] Merge Willox's changes for gmod_launcher --- CMakeLists.txt | 7 +- gmod_launcher/CMakeLists.txt | 36 +++++ gmod_launcher/HtmlPanel.cpp | 135 ++++++++++++++++++ gmod_launcher/HtmlPanel.h | 45 ++++++ gmod_launcher/HtmlResourceHandler.cpp | 32 +++++ gmod_launcher/HtmlResourceHandler.h | 15 ++ gmod_launcher/HtmlSystemLoader.cpp | 51 +++++++ gmod_launcher/HtmlSystemLoader.h | 9 ++ gmod_launcher/Main.cpp | 52 +++++++ gmod_launcher/Window.cpp | 91 ++++++++++++ gmod_launcher/Window.h | 39 +++++ html_chromium/ChromiumBrowser.cpp | 2 +- .../chromium_process/ChromiumApp.cpp | 2 +- 13 files changed, 511 insertions(+), 5 deletions(-) create mode 100644 gmod_launcher/CMakeLists.txt create mode 100644 gmod_launcher/HtmlPanel.cpp create mode 100644 gmod_launcher/HtmlPanel.h create mode 100644 gmod_launcher/HtmlResourceHandler.cpp create mode 100644 gmod_launcher/HtmlResourceHandler.h create mode 100644 gmod_launcher/HtmlSystemLoader.cpp create mode 100644 gmod_launcher/HtmlSystemLoader.h create mode 100644 gmod_launcher/Main.cpp create mode 100644 gmod_launcher/Window.cpp create mode 100644 gmod_launcher/Window.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 31decfc..d9c67e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ else() cmake_minimum_required(VERSION 2.8.7) endif() -project(gmod-html) +project(gmod-html LANGUAGES CXX) # We can only do release builds set(CMAKE_CONFIGURATION_TYPES Release Debug) @@ -85,11 +85,12 @@ add_library(cef_sandbox STATIC IMPORTED) if(WIN32) set_target_properties(cef_sandbox PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/cef_sandbox.lib) else() - #message(FATAL_ERROR "No cef_sandbox library set") + message(FATAL_ERROR "No cef_sandbox library set") endif() # Our projects -#add_subdirectory(example_host) +add_subdirectory(example_host) +add_subdirectory(gmod_launcher) add_subdirectory(html) add_subdirectory(html_stub) add_subdirectory(html_chromium) diff --git a/gmod_launcher/CMakeLists.txt b/gmod_launcher/CMakeLists.txt new file mode 100644 index 0000000..795008d --- /dev/null +++ b/gmod_launcher/CMakeLists.txt @@ -0,0 +1,36 @@ +set(SOURCES + Main.cpp) + +if(WIN32) + set(SOURCES + ${SOURCES} + ../html_chromium/chromium_process/ChromiumApp.cpp + ../html_chromium/chromium_process/ChromiumApp.h + ../html_chromium/chromium_process/Windows.cpp) +endif() + +# Lame +include_directories(${CEF_PATH}) + +add_executable(gmod_launcher WIN32 ${SOURCES}) +target_link_libraries(gmod_launcher html libcef_imp libcef_dll_wrapper) +target_link_libraries(gmod_launcher optimized cef_sandbox) + +if(WIN32) + target_link_libraries(gmod_launcher + shlwapi + winmm + wsock32 + comctl32 + rpcrt4 + version + DbgHelp + Psapi + wbemuuid + OleAut32 + SetupAPI + Propsys + Cfgmgr32 + PowrProf + Delayimp.lib) +endif() diff --git a/gmod_launcher/HtmlPanel.cpp b/gmod_launcher/HtmlPanel.cpp new file mode 100644 index 0000000..a0fa664 --- /dev/null +++ b/gmod_launcher/HtmlPanel.cpp @@ -0,0 +1,135 @@ +#include + +#include "HtmlPanel.h" +#include "HtmlSystemLoader.h" +#include "glad/glad.h" + +HtmlPanel::HtmlPanel() + : m_Texture( nullptr ) + , m_TextureWidth( -1 ) + , m_TextureHeight( -1 ) +{ + m_HtmlClient = g_pHtmlSystem->CreateClient( this ); +} + +HtmlPanel::~HtmlPanel() +{ + m_HtmlClient->Close(); + m_HtmlClient = nullptr; + + DestroyTexture(); +} + +void HtmlPanel::LoadUrl( const char* pUrl ) +{ + m_HtmlClient->LoadUrl( pUrl ); +} + +void HtmlPanel::UpdateTexture() +{ + int width, height; + const unsigned char* data; + + if ( !m_HtmlClient->LockImageData() ) + return; + + data = m_HtmlClient->GetImageData( width, height ); + + if ( m_Texture == nullptr || m_TextureWidth != width || m_TextureHeight != height ) + { + // Make a new texture (technically an unnecessary destroy but it looks like this in Source) + DestroyTexture(); + + // No error handling? + GLuint texture; + glGenTextures( 1, &texture ); + glBindTexture( GL_TEXTURE_2D, texture ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_BGRA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); + + m_Texture = reinterpret_cast( texture ); + m_TextureWidth = width; + m_TextureHeight = height; + } + else + { + // Update current texture + glBindTexture( GL_TEXTURE_2D, reinterpret_cast( m_Texture ) ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); + } + + m_HtmlClient->UnlockImageData(); +} + +void HtmlPanel::DestroyTexture() +{ + m_Texture = nullptr; + m_TextureWidth = -1; + m_TextureHeight = -1; +} + +void HtmlPanel::Render() +{ + UpdateTexture(); + + if ( m_Texture == nullptr ) + return; + + if ( ImGui::Begin( "Browser", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar ) ) + { + ImGui::Image( m_Texture, ImVec2( m_TextureWidth, m_TextureHeight ) ); + } + + ImGui::End(); +} + +void HtmlPanel::OnAddressChange( const char* address ) +{ + std::cout << "OnAddressChange: " << address << std::endl; +} + +void HtmlPanel::OnConsoleMessage( const char* message, const char* source, int lineNumber ) +{ + std::cout << source << ":" << lineNumber << ": " << message << std::endl; +} + +void HtmlPanel::OnTitleChange( const char* title ) +{ + std::cout << "OnTitleChange: " << title << std::endl; +} + +void HtmlPanel::OnTargetUrlChange( const char* url ) +{ + std::cout << "OnTargetUrlChange: " << url << std::endl; +} + +void HtmlPanel::OnCursorChange( CursorType cursorType ) +{ + +} + +void HtmlPanel::OnLoadStart( const char* address ) +{ + std::cout << "OnLoadStart: " << address << std::endl; +} + +void HtmlPanel::OnLoadEnd( const char* address ) +{ + std::cout << "OnLoadEnd: " << address << std::endl; +} + +void HtmlPanel::OnDocumentReady( const char* address ) +{ + std::cout << "OnDocumentReady: " << address << std::endl; +} + +void HtmlPanel::OnCreateChildView( const char* sourceUrl, const char* targetUrl, bool isPopup ) +{ + +} + +JSValue HtmlPanel::OnJavaScriptCall( const char* objName, const char* funcName, const JSValue& params ) +{ + return JSValue(); +} diff --git a/gmod_launcher/HtmlPanel.h b/gmod_launcher/HtmlPanel.h new file mode 100644 index 0000000..6004784 --- /dev/null +++ b/gmod_launcher/HtmlPanel.h @@ -0,0 +1,45 @@ +#pragma once + +#include "html/IHtmlSystem.h" +#include "imgui.h" + +class HtmlPanel : public IHtmlClientListener +{ +public: + HtmlPanel(); + ~HtmlPanel(); + + HtmlPanel( const HtmlPanel& ) = delete; + HtmlPanel( HtmlPanel&& ) = delete; + + HtmlPanel& operator=( const HtmlPanel& ) = delete; + HtmlPanel& operator=( HtmlPanel&& ) = delete; + + void LoadUrl( const char* pUrl ); + + void Render(); + + // + // IHtmlClientListener + // + void OnAddressChange( const char* address ) override; + void OnConsoleMessage( const char* message, const char* source, int lineNumber ) override; + void OnTitleChange( const char* title ) override; + void OnTargetUrlChange( const char* url ) override; + void OnCursorChange( CursorType cursorType ) override; + void OnLoadStart( const char* address ) override; + void OnLoadEnd( const char* address ) override; + void OnDocumentReady( const char* address ) override; + void OnCreateChildView( const char* sourceUrl, const char* targetUrl, bool isPopup ) override; + JSValue OnJavaScriptCall( const char* objName, const char* funcName, const JSValue& params ) override; + +private: + void UpdateTexture(); + void DestroyTexture(); + + IHtmlClient* m_HtmlClient; + + ImTextureID m_Texture; + int m_TextureWidth; + int m_TextureHeight; +}; \ No newline at end of file diff --git a/gmod_launcher/HtmlResourceHandler.cpp b/gmod_launcher/HtmlResourceHandler.cpp new file mode 100644 index 0000000..60de7f6 --- /dev/null +++ b/gmod_launcher/HtmlResourceHandler.cpp @@ -0,0 +1,32 @@ +#include "HtmlResourceHandler.h" +#include + +struct HtmlResource +{ + +}; + +HtmlResource* HtmlResourceHandler::OpenResource( const char* pHost, const char* pPath ) +{ + return nullptr; +} + +void HtmlResourceHandler::CloseResource( HtmlResource* resource ) +{ + delete resource; +} + +size_t HtmlResourceHandler::GetLength( HtmlResource* resource ) +{ + return 0; +} + +void HtmlResourceHandler::ReadData( HtmlResource* resource, char* pDestination, size_t length ) +{ + +} + +void HtmlResourceHandler::Message( const char* data ) +{ + std::cout << data << std::endl; +} \ No newline at end of file diff --git a/gmod_launcher/HtmlResourceHandler.h b/gmod_launcher/HtmlResourceHandler.h new file mode 100644 index 0000000..10ac9bd --- /dev/null +++ b/gmod_launcher/HtmlResourceHandler.h @@ -0,0 +1,15 @@ +#pragma once + +#include "html/IHtmlResourceHandler.h" + +class HtmlResourceHandler : public IHtmlResourceHandler +{ + HtmlResource* OpenResource( const char* pHost, const char* pPath ) override; + void CloseResource( HtmlResource* resource ) override; + + size_t GetLength( HtmlResource* resource ) override; + void ReadData( HtmlResource* resource, char* pDestination, size_t length ) override; + + void Message( const char* data ) override; +}; + diff --git a/gmod_launcher/HtmlSystemLoader.cpp b/gmod_launcher/HtmlSystemLoader.cpp new file mode 100644 index 0000000..dcddf21 --- /dev/null +++ b/gmod_launcher/HtmlSystemLoader.cpp @@ -0,0 +1,51 @@ +#include +#include +#include + +#include "HtmlSystemLoader.h" +#include "HtmlResourceHandler.h" + +HtmlResourceHandler g_ResourceHandler; +IHtmlSystem* g_pHtmlSystem; + +// +// Platform specific shit to dynamically find our html lib +// +bool HtmlSystem_Init() +{ +#ifdef _WIN32 + HMODULE hDLL = LoadLibrary( "html_chromium.dll" ); + + if ( hDLL == nullptr ) + return false; + + IHtmlSystem** ppHtmlSystem = reinterpret_cast( GetProcAddress( hDLL, "g_pHtmlSystem" ) ); + + if ( ppHtmlSystem == nullptr || *ppHtmlSystem == nullptr ) + return false; + + g_pHtmlSystem = *ppHtmlSystem; + + char pPath[MAX_PATH] = { 0 }; + if ( _getcwd( pPath, sizeof( pPath ) ) != pPath ) + return false; + + std::string finalPath( pPath ); + finalPath.append( "/../../" ); // This has to point to where our 'hl2.exe' would live + + return g_pHtmlSystem->Init( finalPath.c_str(), &g_ResourceHandler ); +#else + #error +#endif +} + +void HtmlSystem_Tick() +{ + g_pHtmlSystem->Update(); +} + +void HtmlSystem_Shutdown() +{ + g_pHtmlSystem->Shutdown(); + g_pHtmlSystem = nullptr; +} \ No newline at end of file diff --git a/gmod_launcher/HtmlSystemLoader.h b/gmod_launcher/HtmlSystemLoader.h new file mode 100644 index 0000000..f7780e0 --- /dev/null +++ b/gmod_launcher/HtmlSystemLoader.h @@ -0,0 +1,9 @@ +#pragma once + +#include "html/IHtmlSystem.h" + +extern IHtmlSystem* g_pHtmlSystem; + +bool HtmlSystem_Init(); +void HtmlSystem_Tick(); +void HtmlSystem_Shutdown(); \ No newline at end of file diff --git a/gmod_launcher/Main.cpp b/gmod_launcher/Main.cpp new file mode 100644 index 0000000..b7d7264 --- /dev/null +++ b/gmod_launcher/Main.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include +#include + +#if defined( _WIN32 ) + #include "include/cef_sandbox_win.h" + + extern "C" + { + __declspec( dllexport ) void* CreateCefSandboxInfo() + { + return cef_sandbox_info_create(); + } + + __declspec( dllexport ) void DestroyCefSandboxInfo( void* info ) + { + cef_sandbox_info_destroy( info ); + } + } +#endif + +using FuncLauncherMain = int (*)(HINSTANCE, HINSTANCE, LPSTR, int); + +int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) +{ + // Sub-process +#ifdef _WIN32 + if ( strstr( lpCmdLine, "--type=" ) ) + { + int ChromiumMain( HINSTANCE hInstance ); + + int exit_code = ChromiumMain( hInstance ); + + if ( exit_code != -1 ) + { + return exit_code; + } + } +#endif + + std::string env{std::getenv("PATH")}; + env += ";D:\\Steam\\steamapps\\common\\GarrysMod\\bin\\win64"; + SetEnvironmentVariable("PATH", env.c_str()); + + HMODULE hLauncher = LoadLibraryA("launcher.dll"); + void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); + (static_cast(mainFn))(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + + return 0; +} \ No newline at end of file diff --git a/gmod_launcher/Window.cpp b/gmod_launcher/Window.cpp new file mode 100644 index 0000000..cb736a5 --- /dev/null +++ b/gmod_launcher/Window.cpp @@ -0,0 +1,91 @@ +#include + +#include "Window.h" +#include "glad/glad.h" +#include "GLFW/glfw3.h" + + +static void glfw_framebuffersize_callback( GLFWwindow* glfwWindow, int width, int height ) +{ + auto* window = Window::FromInternal( glfwWindow ); + + if ( window == nullptr ) + return; + + window->OnFramebufferResized( width, height ); +} + +// + +std::unique_ptr Window::Create( const char* pTitle ) +{ + GLFWwindow* glfwWindow; + + glfwWindowHint( GLFW_CLIENT_API, GLFW_OPENGL_API ); + glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 ); + glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 0 ); + + glfwWindow = glfwCreateWindow( 640, 480, pTitle, NULL, NULL ); + if ( !glfwWindow ) + return nullptr; + + glfwMakeContextCurrent( glfwWindow ); + glfwSwapInterval( 1 ); + + if ( !gladLoadGLLoader( reinterpret_cast( glfwGetProcAddress ) ) ) + return nullptr; + + return std::unique_ptr( new Window( glfwWindow ) ); +} + +Window* Window::FromInternal( GLFWwindow* glfwWindow ) +{ + return static_cast( glfwGetWindowUserPointer( glfwWindow ) ); +} + +// + +Window::Window( GLFWwindow* glfwWindow ) + : _glfwWindow( glfwWindow ) +{ + glfwSetWindowUserPointer( _glfwWindow, this ); + + // GLFW Events + glfwSetFramebufferSizeCallback( _glfwWindow, glfw_framebuffersize_callback ); + + // Trigger some default events + int width, height; + glfwGetFramebufferSize( glfwWindow, &width, &height ); + OnFramebufferResized( width, height ); +} + +Window::~Window() +{ + glfwSetWindowUserPointer( _glfwWindow, nullptr ); + glfwDestroyWindow( _glfwWindow ); +} + +bool Window::ShouldClose() +{ + return glfwWindowShouldClose( _glfwWindow ); +} + +void Window::SwapBuffers() +{ + return glfwSwapBuffers( _glfwWindow ); +} + +void Window::PollEvents() +{ + glfwPollEvents(); +} + +GLFWwindow* Window::GetInternal() +{ + return _glfwWindow; +} + +void Window::OnFramebufferResized( int width, int height ) +{ + glViewport( 0, 0, width, height ); +} \ No newline at end of file diff --git a/gmod_launcher/Window.h b/gmod_launcher/Window.h new file mode 100644 index 0000000..1c959af --- /dev/null +++ b/gmod_launcher/Window.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +struct GLFWwindow; + +class Window +{ +public: + Window( GLFWwindow* glfwWindow ); + ~Window(); + + Window( const Window& ) = delete; + Window( Window&& ) = delete; + + Window& operator=( const Window& ) = delete; + Window& operator=( Window&& ) = delete; + +private: + GLFWwindow* _glfwWindow; + int _viewportWidth; + int _viewportHeight; + +public: + static std::unique_ptr Create( const char* pTitle ); + static Window* FromInternal( GLFWwindow* glfwWindow ); + + bool ShouldClose(); + void SwapBuffers(); + void PollEvents(); + + GLFWwindow* GetInternal(); + + int GetViewportWidth() { return _viewportWidth; } + int GetViewportHeight() { return _viewportHeight; } + +public: + void OnFramebufferResized( int width, int height ); +}; \ No newline at end of file diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index d4541a3..d0c7643 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -684,7 +684,7 @@ void ChromiumBrowser::OnPopupSize( CefRefPtr, const CefRect& rect ) } void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height ) -{ +{ // // We blit the popup straight on to the main image. That means gmod won't need to use multiple textures (+1) // diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 380cb01..7705eb0 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -476,7 +476,7 @@ void ChromiumApp::ExecuteCallback( CefRefPtr browser, CefRefPtrExecuteFunction( nullptr, arguments ); - } + } } context->Exit(); } From aa6fcb3e5be5899ce44fa34f6bc81ee6d817eda2 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 5 Nov 2021 22:14:58 -0400 Subject: [PATCH 13/87] - Update to CEF 95.0.4638.69 - Add winsock2 library to example_host and gmod_launcher, as CEF uses it now - Disabled hopefully unneeded PATH manipulation in gmod_launcher - Updated CEF implementation for changes since 86 - Remove `universal_access_from_file_urls` and `file_access_from_file_urls` CefBrowserSettings as they no longer exist - Updated User Agent string - TODO: Figure out crashing issues with Bind/Closure/CefPostTask changes when not on `/DEBUG /OPT:ICF` --- CMakeLists.txt | 6 +++--- example_host/CMakeLists.txt | 1 + gmod_launcher/CMakeLists.txt | 1 + gmod_launcher/Main.cpp | 8 +++---- html_chromium/ChromiumBrowser.cpp | 15 +++++++------ html_chromium/ChromiumBrowser.h | 4 ++-- html_chromium/ChromiumClient.cpp | 36 +++++++++++++++---------------- html_chromium/ChromiumSystem.cpp | 8 +++---- 8 files changed, 40 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9c67e9..589049d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_86.0.24+g85e79d4+chromium-86.0.4240.198_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") diff --git a/example_host/CMakeLists.txt b/example_host/CMakeLists.txt index 137e5ff..4053ded 100644 --- a/example_host/CMakeLists.txt +++ b/example_host/CMakeLists.txt @@ -29,6 +29,7 @@ if(WIN32) shlwapi winmm wsock32 + WS2_32 comctl32 rpcrt4 version diff --git a/gmod_launcher/CMakeLists.txt b/gmod_launcher/CMakeLists.txt index 795008d..88ff96a 100644 --- a/gmod_launcher/CMakeLists.txt +++ b/gmod_launcher/CMakeLists.txt @@ -21,6 +21,7 @@ if(WIN32) shlwapi winmm wsock32 + WS2_32 comctl32 rpcrt4 version diff --git a/gmod_launcher/Main.cpp b/gmod_launcher/Main.cpp index b7d7264..f2db4e3 100644 --- a/gmod_launcher/Main.cpp +++ b/gmod_launcher/Main.cpp @@ -40,13 +40,13 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, } #endif - std::string env{std::getenv("PATH")}; - env += ";D:\\Steam\\steamapps\\common\\GarrysMod\\bin\\win64"; - SetEnvironmentVariable("PATH", env.c_str()); + //std::string env{std::getenv("PATH")}; + //env += ";D:\\Program Files (x86)\\Steam\\steamapps\\common\\GarrysMod\\bin\\win64"; + //SetEnvironmentVariable("PATH", env.c_str()); HMODULE hLauncher = LoadLibraryA("launcher.dll"); void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); (static_cast(mainFn))(hInstance, hPrevInstance, lpCmdLine, nCmdShow); return 0; -} \ No newline at end of file +} diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index d0c7643..266f898 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -255,7 +255,7 @@ void ChromiumBrowser::SetSize( int wide, int tall ) void ChromiumBrowser::SetFocused( bool hasFocus ) { - m_BrowserHost->SendFocusEvent( hasFocus ); + m_BrowserHost->SetFocus( hasFocus ); } void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) @@ -566,10 +566,7 @@ void ChromiumBrowser::OnTitleChange( CefRefPtr, const CefString& tit QueueMessage( std::move( msg ) ); } -// -// CefRenderHandler interface -// -void ChromiumBrowser::OnCursorChange( CefRefPtr, CefCursorHandle, CefRenderHandler::CursorType chromeCursor, const CefCursorInfo& ) +bool ChromiumBrowser::OnCursorChange( CefRefPtr browser, CefCursorHandle, cef_cursor_type_t chromeCursor, const CefCursorInfo& ) { using GModCursorType = IHtmlClientListener::CursorType; GModCursorType gmodCursor; @@ -645,9 +642,13 @@ void ChromiumBrowser::OnCursorChange( CefRefPtr, CefCursorHandle, Ce msg.type = MessageQueue::Type::OnCursorChange; msg.integer = static_cast( gmodCursor ); QueueMessage( std::move( msg ) ); -} + return false; +} +// +// CefRenderHandler interface +// void ChromiumBrowser::GetViewRect( CefRefPtr, CefRect& rect ) { rect.x = 0; @@ -789,7 +790,7 @@ bool ChromiumBrowser::OnBeforeBrowse( CefRefPtr, ChromiumBrowser::ReturnValue ChromiumBrowser::OnBeforeResourceLoad( CefRefPtr, CefRefPtr, CefRefPtr request, - CefRefPtr ) + CefRefPtr ) { CefURLParts urlParts; if ( !CefParseURL( request->GetURL(), urlParts ) ) diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 619eaca..f7ca69f 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -98,11 +98,11 @@ class ChromiumBrowser void OnAddressChange( CefRefPtr, CefRefPtr frame, const CefString& url ) override; bool OnConsoleMessage( CefRefPtr, cef_log_severity_t level, const CefString& message, const CefString& source, int line ) override; void OnTitleChange( CefRefPtr, const CefString& title ) override; + bool OnCursorChange( CefRefPtr browser, CefCursorHandle, cef_cursor_type_t chromeCursor, const CefCursorInfo& ) override; // // CefRenderHandler interface // - void OnCursorChange( CefRefPtr, CefCursorHandle, CefRenderHandler::CursorType chromeCursor, const CefCursorInfo& ) override; void GetViewRect( CefRefPtr, CefRect& rect ) override; void OnPopupShow( CefRefPtr, bool show ) override; void OnPopupSize( CefRefPtr, const CefRect& rect ) override; @@ -134,7 +134,7 @@ class ChromiumBrowser ReturnValue OnBeforeResourceLoad( CefRefPtr, CefRefPtr, CefRefPtr request, - CefRefPtr ) override; + CefRefPtr ) override; void OnProtocolExecution( CefRefPtr, CefRefPtr, diff --git a/html_chromium/ChromiumClient.cpp b/html_chromium/ChromiumClient.cpp index 53f665c..27f3e47 100644 --- a/html_chromium/ChromiumClient.cpp +++ b/html_chromium/ChromiumClient.cpp @@ -4,7 +4,7 @@ #include "JSObjects.h" #include "cef_start.h" -#include "include/base/cef_bind.h" +#include "include/base/cef_callback.h" #include "include/wrapper/cef_closure_task.h" #include "cef_end.h" @@ -70,7 +70,7 @@ void ChromiumClient::HandleJavaScriptCall( const char* objName, const char* func return; // TODO: This seems to be copying retArray - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::ExecuteCallback, m_Browser, callbackId, std::move( retArray ) ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::ExecuteCallback, m_Browser, callbackId, std::move( retArray ) ) ); } // @@ -78,7 +78,7 @@ void ChromiumClient::HandleJavaScriptCall( const char* objName, const char* func // void ChromiumClient::Close() { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::Close, m_Browser ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::Close, m_Browser ) ); g_ChromiumSystem.OnClientClose( this ); delete this; @@ -86,77 +86,77 @@ void ChromiumClient::Close() void ChromiumClient::SetSize( int wide, int tall ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SetSize, m_Browser, wide, tall ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SetSize, m_Browser, wide, tall ) ); } void ChromiumClient::SetFocused( bool hasFocus ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SetFocused, m_Browser, hasFocus ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SetFocused, m_Browser, hasFocus ) ); } void ChromiumClient::SendKeyEvent( KeyEvent keyEvent ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SendKeyEvent, m_Browser, keyEvent ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SendKeyEvent, m_Browser, keyEvent ) ); } void ChromiumClient::SendMouseMoveEvent( MouseEvent mouseEvent, bool mouseLeave ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SendMouseMoveEvent, m_Browser, mouseEvent, mouseLeave ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SendMouseMoveEvent, m_Browser, mouseEvent, mouseLeave ) ); } void ChromiumClient::SendMouseWheelEvent( MouseEvent mouseEvent, int deltaX, int deltaY ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SendMouseWheelEvent, m_Browser, mouseEvent, deltaX, deltaY ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SendMouseWheelEvent, m_Browser, mouseEvent, deltaX, deltaY ) ); } void ChromiumClient::SendMouseClickEvent( MouseEvent mouseEvent, MouseButton mouseButton, bool mouseUp, int clickCount ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SendMouseClickEvent, m_Browser, mouseEvent, mouseButton, mouseUp, clickCount ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SendMouseClickEvent, m_Browser, mouseEvent, mouseButton, mouseUp, clickCount ) ); } void ChromiumClient::LoadUrl( const char* url ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::LoadUrl, m_Browser, std::string( url ) ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::LoadUrl, m_Browser, std::string( url ) ) ); } void ChromiumClient::SetHtml( const char* html ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SetHtml, m_Browser, std::string( html ) ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SetHtml, m_Browser, std::string( html ) ) ); } void ChromiumClient::Refresh() { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::Refresh, m_Browser ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::Refresh, m_Browser ) ); } void ChromiumClient::Stop() { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::Stop, m_Browser ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::Stop, m_Browser ) ); } void ChromiumClient::GoBack() { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::GoBack, m_Browser ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::GoBack, m_Browser ) ); } void ChromiumClient::GoForward() { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::GoForward, m_Browser ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::GoForward, m_Browser ) ); } void ChromiumClient::RunJavaScript( const char* code ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::RunJavaScript, m_Browser, std::string( code ) ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::RunJavaScript, m_Browser, std::string( code ) ) ); } void ChromiumClient::RegisterJavaScriptFunction( const char* objName, const char* funcName ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::RegisterJavaScriptFunction, m_Browser, std::string( objName ), std::string( funcName ) ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::RegisterJavaScriptFunction, m_Browser, std::string( objName ), std::string( funcName ) ) ); } void ChromiumClient::SetOpenLinksExternally( bool openLinksExternally ) { - CefPostTask( TID_UI, base::Bind( &ChromiumBrowser::SetOpenLinksExternally, m_Browser, openLinksExternally ) ); + CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::SetOpenLinksExternally, m_Browser, openLinksExternally ) ); } bool ChromiumClient::LockImageData() diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index a7f3503..2a1ace5 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -206,7 +206,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); @@ -214,7 +214,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -228,7 +228,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OSX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Intel Mac OS X; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); #else #error #endif @@ -319,8 +319,6 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) browserSettings.windowless_frame_rate = 60; browserSettings.javascript_access_clipboard = STATE_DISABLED; browserSettings.javascript_close_windows = STATE_DISABLED; - browserSettings.universal_access_from_file_urls = STATE_DISABLED; - browserSettings.file_access_from_file_urls = STATE_DISABLED; //browserSettings.webgl = STATE_DISABLED; CefRefPtr cefClient( new ChromiumBrowser ); From 1c21ffc2c797161b7be13f93fea4d1fa7d2fee5c Mon Sep 17 00:00:00 2001 From: William Wallace Date: Sat, 6 Nov 2021 19:59:31 +0000 Subject: [PATCH 14/87] defer use of m_Browser/m_BrowserHost until after creation --- html_chromium/ChromiumBrowser.cpp | 153 ++++++++++++++++++++++++++++++ html_chromium/ChromiumBrowser.h | 6 ++ 2 files changed, 159 insertions(+) diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 266f898..df583d8 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -243,11 +243,31 @@ void ChromiumBrowser::QueueMessage( MessageQueue::Message&& message ) // void ChromiumBrowser::Close() { + if (m_BrowserHost == nullptr) { + m_Deferred.emplace_back( + [] (auto& self) { + self.Close(); + } + ); + + return; + } + m_BrowserHost->CloseBrowser( true ); } void ChromiumBrowser::SetSize( int wide, int tall ) { + if (m_BrowserHost == nullptr) { + m_Deferred.emplace_back( + [=] (auto& self) { + self.SetSize(wide, tall); + } + ); + + return; + } + m_Wide = wide; m_Tall = tall; m_BrowserHost->WasResized(); @@ -255,11 +275,25 @@ void ChromiumBrowser::SetSize( int wide, int tall ) void ChromiumBrowser::SetFocused( bool hasFocus ) { + if (m_BrowserHost == nullptr) { + m_Deferred.emplace_back( + [=] (auto& self) { + self.SetFocused(hasFocus); + } + ); + + return; + } + m_BrowserHost->SetFocus( hasFocus ); } void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) { + if (m_BrowserHost == nullptr) { + return; + } + CefKeyEvent chromiumKeyEvent; chromiumKeyEvent.modifiers = GetModifiers( keyEvent.modifiers ); @@ -302,6 +336,10 @@ void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) void ChromiumBrowser::SendMouseMoveEvent( IHtmlClient::MouseEvent gmodMouseEvent, bool mouseLeave ) { + if (m_BrowserHost == nullptr) { + return; + } + CefMouseEvent mouseEvent; mouseEvent.x = gmodMouseEvent.x; mouseEvent.y = gmodMouseEvent.y; @@ -312,6 +350,10 @@ void ChromiumBrowser::SendMouseMoveEvent( IHtmlClient::MouseEvent gmodMouseEvent void ChromiumBrowser::SendMouseWheelEvent( IHtmlClient::MouseEvent gmodMouseEvent, int deltaX, int deltaY ) { + if (m_BrowserHost == nullptr) { + return; + } + // Some CEF bug is fucking this up. I don't care much for worrying about it yet CefMouseEvent mouseEvent; mouseEvent.x = gmodMouseEvent.x; @@ -323,6 +365,10 @@ void ChromiumBrowser::SendMouseWheelEvent( IHtmlClient::MouseEvent gmodMouseEven void ChromiumBrowser::SendMouseClickEvent( IHtmlClient::MouseEvent gmodMouseEvent, IHtmlClient::MouseButton gmodButtonType, bool mouseUp, int clickCount ) { + if (m_BrowserHost == nullptr) { + return; + } + CefMouseEvent mouseEvent; mouseEvent.x = gmodMouseEvent.x; mouseEvent.y = gmodMouseEvent.y; @@ -348,11 +394,35 @@ void ChromiumBrowser::SendMouseClickEvent( IHtmlClient::MouseEvent gmodMouseEven void ChromiumBrowser::LoadUrl( const std::string& url ) { + if (m_Browser == nullptr) { + std::string url_Copy {url}; + + m_Deferred.emplace_back( + [=] (auto& self) { + self.LoadUrl(url_Copy); + } + ); + + return; + } + m_Browser->GetMainFrame()->LoadURL( CefString( url ) ); } void ChromiumBrowser::SetHtml( const std::string& html ) { + if (m_Browser == nullptr) { + std::string html_Copy {html}; + + m_Deferred.emplace_back( + [=] (auto& self) { + self.SetHtml(html_Copy); + } + ); + + return; + } + // asset://html/?{myhtml} CefURLParts urlParts; CefString( &urlParts.scheme ).FromString( "asset" ); @@ -369,26 +439,78 @@ void ChromiumBrowser::SetHtml( const std::string& html ) void ChromiumBrowser::Refresh() { + if (m_Browser == nullptr) { + m_Deferred.emplace_back( + [] (auto& self) { + self.Refresh(); + } + ); + + return; + } + m_Browser->Reload(); } void ChromiumBrowser::Stop() { + if (m_Browser == nullptr) { + m_Deferred.emplace_back( + [] (auto& self) { + self.Stop(); + } + ); + + return; + } + m_Browser->StopLoad(); } void ChromiumBrowser::GoBack() { + if (m_Browser == nullptr) { + m_Deferred.emplace_back( + [] (auto& self) { + self.GoBack(); + } + ); + + return; + } + m_Browser->GoBack(); } void ChromiumBrowser::GoForward() { + if (m_Browser == nullptr) { + m_Deferred.emplace_back( + [] (auto& self) { + self.GoForward(); + } + ); + + return; + } + m_Browser->GoForward(); } void ChromiumBrowser::RunJavaScript( const std::string& code ) { + if (m_Browser == nullptr) { + std::string code_Copy {code}; + + m_Deferred.emplace_back( + [=] (auto& self) { + self.RunJavaScript(code_Copy); + } + ); + + return; + } + auto message = CefProcessMessage::Create( "ExecuteJavaScript" ); auto args = message->GetArgumentList(); @@ -399,6 +521,19 @@ void ChromiumBrowser::RunJavaScript( const std::string& code ) void ChromiumBrowser::RegisterJavaScriptFunction( const std::string& objName, const std::string& funcName ) { + if (m_Browser == nullptr) { + std::string objName_Copy {objName}; + std::string funcName_Copy {funcName}; + + m_Deferred.emplace_back( + [=] (auto& self) { + self.RegisterJavaScriptFunction(objName_Copy, funcName_Copy); + } + ); + + return; + } + auto message = CefProcessMessage::Create( "RegisterFunction" ); auto args = message->GetArgumentList(); @@ -414,6 +549,18 @@ void ChromiumBrowser::SetOpenLinksExternally( bool openLinksExternally ) void ChromiumBrowser::ExecuteCallback( int callbackId, const JSValue& paramsArray ) { + if (m_Browser == nullptr) { + JSValue paramsArray_Copy {paramsArray}; + + m_Deferred.emplace_back( + [=] (auto& self) { + self.ExecuteCallback(callbackId, paramsArray_Copy); + } + ); + + return; + } + const auto& paramsVector = ( static_cast( paramsArray.GetInternalArray() ) )->GetInternalData(); // spaghetti auto message = CefProcessMessage::Create( "ExecuteCallback" ); auto outArgs = message->GetArgumentList(); @@ -462,6 +609,12 @@ void ChromiumBrowser::OnAfterCreated( CefRefPtr browser ) { m_Browser = browser; m_BrowserHost = browser->GetHost(); + + for (auto& func : m_Deferred) { + func(*this); + } + + m_Deferred.clear(); } void ChromiumBrowser::OnBeforeClose( CefRefPtr browser ) diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index f7ca69f..4f4e321 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "cef_start.h" #include "include/cef_client.h" @@ -11,6 +12,7 @@ #include "html/IHtmlClient.h" + class ChromiumBrowser : public CefClient , public CefLifeSpanHandler @@ -199,4 +201,8 @@ class ChromiumBrowser private: IMPLEMENT_REFCOUNTING( ChromiumBrowser ); + +private: + // Functions in this vector will be executed once our underlying CefBrowser is available + std::vector> m_Deferred; }; From d9aaa875145e6a45c860573409927351e9164092 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 9 Nov 2021 16:12:13 -0500 Subject: [PATCH 15/87] Bump CEF_PATH version to 95.7.18 --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 589049d..bd2160c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") From 630058613d571602b68e69657d665849a8751861 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sat, 13 Nov 2021 20:17:19 -0500 Subject: [PATCH 16/87] - gmod_launcher now automatically fixes the CWD based on the binary location (Windows only) - Add GMod ICO --- gmod.ico | Bin 0 -> 90567 bytes gmod_launcher/Main.cpp | 42 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 gmod.ico diff --git a/gmod.ico b/gmod.ico new file mode 100644 index 0000000000000000000000000000000000000000..609cf76068c3a3e87f1939f3a612cbb81242ffc7 GIT binary patch literal 90567 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 literal 0 HcmV?d00001 diff --git a/gmod_launcher/Main.cpp b/gmod_launcher/Main.cpp index f2db4e3..90c7b5d 100644 --- a/gmod_launcher/Main.cpp +++ b/gmod_launcher/Main.cpp @@ -3,6 +3,31 @@ #include #include +#include + +#ifdef _WIN32 + #include + #define cd _chdir +#else + #include "unistd.h" + #define cd chdir +#endif + +// Is this exe 32-bit or 64-bit? +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENVIRONMENT64 + #else + #define ENVIRONMENT32 + #endif +#endif +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENVIRONMENT64 + #else + #define ENVIRONMENT32 + #endif +#endif #if defined( _WIN32 ) #include "include/cef_sandbox_win.h" @@ -40,9 +65,20 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, } #endif - //std::string env{std::getenv("PATH")}; - //env += ";D:\\Program Files (x86)\\Steam\\steamapps\\common\\GarrysMod\\bin\\win64"; - //SetEnvironmentVariable("PATH", env.c_str()); + // Make sure the CWD is always GarrysMod root (where hl2.exe is) + char exePath[MAX_PATH]; + GetModuleFileNameA(NULL, exePath, MAX_PATH); + + std::string::size_type lastSlashPos = std::string(exePath).find_last_of("\\/"); + std::string exeDir = std::string(exePath).substr(0, lastSlashPos); + +#ifdef ENVIRONMENT64 + exeDir += "\\..\\.."; +#else + exeDir += "\\.."; +#endif + + cd(exeDir.c_str()); HMODULE hLauncher = LoadLibraryA("launcher.dll"); void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); From 44566084c565e515e3007a85fc91eec7796c0305 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 17 Nov 2021 00:08:15 -0500 Subject: [PATCH 17/87] - Update README for CEF 95 + Where you can get CEF Distributions - Remove Notification Stub + CefRenderProcessHandler implementation - `enable-gpu` on Linux/macOS --- README.md | 18 ++++---- html_chromium/ChromiumBrowser.h | 1 - html_chromium/ChromiumSystem.cpp | 44 ------------------- .../chromium_process/ChromiumApp.cpp | 6 +-- 4 files changed, 13 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index b6abf97..eaef8f5 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,18 @@ This is pretty much just an abstraction layer around The Chromium Embedded Framework (https://bitbucket.org/chromiumembedded/cef) ## Chromium Embedded Framework Binary Distribution -To work with this project you will need a build of the Chromium Embedded Framework. You can download the builds used by Garry's Mod here if you don't want to compile your own: +To work with this project you will need a build of the Chromium Embedded Framework. You can download prebuilt versions of CEF from Spotify's Automated Builder if you don't want to compile your own: -| Platform | URL | -| -------- | --- | -| Windows x86 | https://files.facepunch.com/willox/7bb55f57-7c70-4140-83d7-2d61d8b57816/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_windows32.tar.bz2 | -| Windows x64 | https://files.facepunch.com/willox/14ceed65-8809-4fba-97a7-2d524b6d45ec/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_windows64.tar.bz2 | -| Linux x64 | https://files.facepunch.com/willox/dfd5d7fe-2184-40a9-90b0-49f9eef9a9b5/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_linux64.tar.bz2 | -| macOS x64 | https://files.facepunch.com/willox/17e19274-4112-4734-ac20-22cd8644bcb4/cef_binary_80.0.4%2Bg74f7b0c%2Bchromium-80.0.3987.122_macosx64.tar.bz2 | +https://cef-builds.spotifycdn.com/index.html -These belong in the `./thirdparty/cef3/` directory (after extraction.) The paths are currently hardcoded into the root `CMakelists.txt` file. +You'll probably want the **Minimal Distribution** unless you need to debug with symbols or something. These belong in the `./thirdparty/cef3/` directory (after extraction.) The paths are currently hardcoded into the root `CMakelists.txt` file. + +## Currently supported CEF version +The current version of CEF that's supported by this library is: + +- **95.7.18+g0d6005e+chromium-95.0.4638.69** + +This is not the only version that could be supported, but it's the version that's currently configured and tested to work. ## Getting started ### Windows diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 4f4e321..2994735 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -12,7 +12,6 @@ #include "html/IHtmlClient.h" - class ChromiumBrowser : public CefClient , public CefLifeSpanHandler diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 2a1ace5..808652f 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -21,21 +21,8 @@ namespace fs = std::experimental::filesystem; #endif -class EmptyV8Handler : public CefV8Handler { -public: - EmptyV8Handler() {} - - virtual bool Execute( const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception ) override - { - return true; - } - - IMPLEMENT_REFCOUNTING(EmptyV8Handler); -}; - class ChromiumApp : public CefApp - , public CefRenderProcessHandler { public: // @@ -79,37 +66,6 @@ class ChromiumApp registrar->AddCustomScheme( "asset", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CSP_BYPASSING ); } - // - // CefRenderProcessHandler: This implements a Stub for Notifications, allowing Netflix to work - // - // TODO: Figure out why neither GetRenderProcessHandler nor OnContextCreated is being called (or is it and LOG just isn't working?) - /* - CefRefPtr GetRenderProcessHandler() override - { - LOG(ERROR) << "GetRenderProcessHandler"; - return this; - } - - void OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override - { - LOG(ERROR) << "OnContextCreated"; - - CefRefPtr object = context->GetGlobal(); - - CefRefPtr emptyFunc = new EmptyV8Handler(); - - CefRefPtr notificationFunc = CefV8Value::CreateFunction("Notification", emptyFunc); - - object->SetValue("Notification", notificationFunc, V8_PROPERTY_ATTRIBUTE_NONE); - - std::string notificationStubExtension = - "window.Notification = function() {};" - "window.Notification.requestPermission = function() { return new Promise(function(resolve, reject) { resolve('denied'); }) };" - "window.Notification.permission = 'denied';"; - - CefRegisterExtension("solsticegamestudios/notificationStub", notificationStubExtension, NULL); - } - */ private: IMPLEMENT_REFCOUNTING( ChromiumApp ); }; diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 7705eb0..690d595 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -168,8 +168,8 @@ static bool CefListToV8Values( CefV8ValueList& outList, const CefRefPtr command_line ) { - command_line->AppendSwitch( "disable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); + command_line->AppendSwitch( "enable-gpu" ); + command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); @@ -182,7 +182,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, #endif #ifdef OSX - command_line->AppendSwitch( "use-mock-keychain" ); + command_line->AppendSwitch( "use-mock-keychain" ); #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 From 728fdb5359faef341a4fa948e6ab52fb8e14e134 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 19 Nov 2021 14:56:56 -0500 Subject: [PATCH 18/87] Turn on OnAcceleratedPaint --- html_chromium/ChromiumSystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 808652f..b7deab8 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -31,7 +31,7 @@ class ChromiumApp void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override { command_line->AppendSwitch( "enable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) + //command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); @@ -268,7 +268,7 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) CefWindowInfo windowInfo; windowInfo.SetAsWindowless( 0 ); // TODO: See ChromiumBrowser::OnAcceleratedPaint - //windowInfo.shared_texture_enabled = true; + windowInfo.shared_texture_enabled = true; CefBrowserSettings browserSettings; CefString( &browserSettings.default_encoding ).FromString( "UTF-8" ); From 1bd964815d40ea6eff96b24a2c50d3d5d71ab983 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 16 Nov 2021 23:51:14 -0500 Subject: [PATCH 19/87] - Update CEF to 95.7.18 (Chromium 95.0.4638.69) - Add gmod_launcher - Enable GPU, but NOT GPU Compositing - Enable WebGL - Disable Hardware Media Keys control of media - Change Log Severity from VERBOSE to DEFAULT - Fix missing Winsock2 dependency for example_host - Update README for where to get current CEF distributions - Update .gitignore for different build paths --- html_chromium/ChromiumSystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index b7deab8..13baa8c 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -23,6 +23,7 @@ namespace fs = std::experimental::filesystem; class ChromiumApp : public CefApp + , public CefRenderProcessHandler { public: // @@ -31,7 +32,7 @@ class ChromiumApp void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override { command_line->AppendSwitch( "enable-gpu" ); - //command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) + //command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); From 5521e6278306c22a18c234606dc0bddf638e181c Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 17 Nov 2021 00:04:41 -0500 Subject: [PATCH 20/87] We want GPU Compositing *Disabled* not *Enabled* --- html_chromium/ChromiumSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 13baa8c..55c8410 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -32,7 +32,7 @@ class ChromiumApp void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override { command_line->AppendSwitch( "enable-gpu" ); - //command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) + command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); From 4548424d43661548f686756bf8476d8dbf1a2300 Mon Sep 17 00:00:00 2001 From: John Peel Date: Wed, 17 Nov 2021 20:06:06 -0500 Subject: [PATCH 21/87] Fixed compile on Linux and cleaned up CMake files. --- CMakeLists.txt | 41 ++++++++++--------- README.md | 8 ++-- html/CMakeLists.txt | 2 +- html_chromium/CMakeLists.txt | 36 ++++++++++++---- html_chromium/ChromiumSystem.cpp | 1 + html_chromium/chromium_process/CMakeLists.txt | 7 ++-- html_stub/CMakeLists.txt | 5 +++ thirdparty/glad/CMakeLists.txt | 3 ++ 8 files changed, 68 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd2160c..e30f367 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,21 +3,24 @@ if(WIN32) cmake_minimum_required(VERSION 3.15) cmake_policy(SET CMP0091 NEW) else() - cmake_minimum_required(VERSION 2.8.7) + cmake_minimum_required(VERSION 3.19) endif() project(gmod-html LANGUAGES CXX) # We can only do release builds set(CMAKE_CONFIGURATION_TYPES Release Debug) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Match GMod and our prebuilt libraries set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/bin CACHE PATH "..." FORCE) + set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/dist CACHE PATH "..." FORCE) endif() if(WIN32) @@ -32,11 +35,6 @@ else() message(FATAL_ERROR "No GAME_DIR definition") endif() -# Kind of lame but I'm having PDB timestamp mismatches -set (CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/${GAME_BIN_DIR}) -set (CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/${GAME_BIN_DIR}) -set (CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/${GAME_BIN_DIR}) -set (CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/${GAME_BIN_DIR}) # ImGui add_subdirectory(thirdparty/imgui-1.74) @@ -54,12 +52,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_95.7.18+g0d6005e+chromium-95.0.4638.69_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") @@ -79,22 +77,27 @@ else() message(FATAL_ERROR "No libcef_imp import library set") endif() -# Chromium Sandbox -add_library(cef_sandbox STATIC IMPORTED) +# Chromium Sandbox (Not a library on Linux) +if(WIN32 OR APPLE) + add_library(cef_sandbox STATIC IMPORTED) -if(WIN32) - set_target_properties(cef_sandbox PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/cef_sandbox.lib) -else() - message(FATAL_ERROR "No cef_sandbox library set") + if(WIN32) + set_target_properties(cef_sandbox PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/cef_sandbox.lib) + else() + message(FATAL_ERROR "No cef_sandbox library set") + endif() endif() # Our projects -add_subdirectory(example_host) -add_subdirectory(gmod_launcher) add_subdirectory(html) add_subdirectory(html_stub) add_subdirectory(html_chromium) +if(WIN32) + add_subdirectory(example_host) + add_subdirectory(gmod_launcher) +endif() + if(UNIX) add_subdirectory(html_chromium/chromium_process) endif() diff --git a/README.md b/README.md index eaef8f5..cb9e78e 100644 --- a/README.md +++ b/README.md @@ -34,22 +34,22 @@ cd build_x64 cmake -G "Visual Studio 16 2019" -A x64 .. ``` -After running either of these sets of commands, you can enter your created directory and open the `gmod-html.sln` solution in Visual Studio. Compiling the `INSTALL` project will place a complete build into `/bin` by default. +After running either of these sets of commands, you can enter your created directory and open the `gmod-html.sln` solution in Visual Studio. Compiling the `INSTALL` project will place a complete build into the `dist` folder by default. ### Linux #### Requirements - A version of GCC/G++ or Clang/Clang++ with C++11 support -- CMake 2.8.7 or newer +- CMake 3.19 or newer #### Compiling ```sh mkdir build cd build -cmake -G "Unix Makefiles" .. +cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. make && make install ``` -This will place a complete build into `build/bin` by default. +This will place a complete build into the `dist` folder by default. ### macOS Todo diff --git a/html/CMakeLists.txt b/html/CMakeLists.txt index 7713298..f27b6b6 100644 --- a/html/CMakeLists.txt +++ b/html/CMakeLists.txt @@ -11,4 +11,4 @@ target_include_directories(html INTERFACE ./) # Let us see the interface in Visual Studio if(MSVC) add_custom_target(html.interface SOURCES ${SOURCES}) -endif() \ No newline at end of file +endif() diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index f7d3227..d092d2e 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -21,16 +21,31 @@ else() message(FATAL_ERROR "No IMPORTED_LOCATION for libcef") endif() -set(RESOURCES - ${CEF_PATH}/Resources/cef.pak - ${CEF_PATH}/Resources/cef_100_percent.pak - ${CEF_PATH}/Resources/cef_200_percent.pak - ${CEF_PATH}/Resources/cef_extensions.pak - ${CEF_PATH}/Resources/devtools_resources.pak) +if(WIN32) + set(RESOURCES + ${CEF_PATH}/Resources/cef.pak + ${CEF_PATH}/Resources/cef_100_percent.pak + ${CEF_PATH}/Resources/cef_200_percent.pak + ${CEF_PATH}/Resources/cef_extensions.pak + ${CEF_PATH}/Resources/devtools_resources.pak) +elseif(UNIX AND NOT APPLE) + set(RESOURCES + ${CEF_PATH}/Resources/chrome_100_percent.pak + ${CEF_PATH}/Resources/chrome_200_percent.pak + ${CEF_PATH}/Resources/resources.pak) +else() + message(FATAL_ERROR "No RESOURCES set") +endif() install(FILES ${BINARIES} DESTINATION ${GAME_BIN_DIR}) -install(FILES ${RESOURCES} DESTINATION ${CMAKE_BINARY_DIR}/bin/chromium) -install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION ${CMAKE_BINARY_DIR}/bin/chromium) + +if(WIN32 OR APPLE) + install(FILES ${RESOURCES} DESTINATION chromium) + install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION chromium) +else() + install(FILES ${RESOURCES} DESTINATION linux32/chromium) + install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION linux32/chromium) +endif() # Actual lib set(SOURCES @@ -57,3 +72,8 @@ include_directories(${CEF_PATH}) add_library(html_chromium SHARED ${SOURCES}) target_link_libraries(html_chromium html libcef_imp libcef_dll_wrapper) SET_TARGET_PROPERTIES(html_chromium PROPERTIES PREFIX "") + +if(UNIX) + set_target_properties(html_chromium PROPERTIES OUTPUT_NAME "html_chromium_client") +endif() +install(TARGETS html_chromium LIBRARY DESTINATION ${GAME_BIN_DIR}) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 55c8410..7d094c6 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -13,6 +13,7 @@ #endif #include "cef_end.h" +#include #include #ifdef _WIN32 diff --git a/html_chromium/chromium_process/CMakeLists.txt b/html_chromium/chromium_process/CMakeLists.txt index c08353e..53df377 100644 --- a/html_chromium/chromium_process/CMakeLists.txt +++ b/html_chromium/chromium_process/CMakeLists.txt @@ -2,10 +2,12 @@ set(SOURCES ChromiumApp.cpp ChromiumApp.h) -if(UNIX AND NOT APPLE) +if(WIN32) + set(SOURCES ${SOURCES} Windows.cpp) +elseif(UNIX AND NOT APPLE) set(SOURCES ${SOURCES} Linux.cpp) else() - message(FATAL_ERROR "No ChromiumApp Main implementation") + set(SOURCES ${SOURCES} macOS.cpp) endif() # Lame @@ -14,4 +16,3 @@ include_directories(${CEF_PATH}) add_executable(chromium_process ${SOURCES}) target_link_libraries(chromium_process libcef_imp libcef_dll_wrapper) install(TARGETS chromium_process RUNTIME DESTINATION ${GAME_BIN_DIR}) - diff --git a/html_stub/CMakeLists.txt b/html_stub/CMakeLists.txt index ff932e0..6be8245 100644 --- a/html_stub/CMakeLists.txt +++ b/html_stub/CMakeLists.txt @@ -7,3 +7,8 @@ set(SOURCES add_library(html_stub SHARED ${SOURCES}) target_link_libraries(html_stub html) SET_TARGET_PROPERTIES(html_stub PROPERTIES PREFIX "") + +if(UNIX) + set_target_properties(html_stub PROPERTIES OUTPUT_NAME "html_stub_client") +endif() +install(TARGETS html_stub LIBRARY DESTINATION ${GAME_BIN_DIR}) diff --git a/thirdparty/glad/CMakeLists.txt b/thirdparty/glad/CMakeLists.txt index 47da0fe..3b95e59 100644 --- a/thirdparty/glad/CMakeLists.txt +++ b/thirdparty/glad/CMakeLists.txt @@ -1,8 +1,11 @@ + set(SOURCES src/blank.cpp src/glad.c include/glad/glad.h include/KHR/khrplatform.h) +set_source_files_properties(src/glad.c PROPERTIES LANGUAGE CXX) + add_library(glad STATIC ${SOURCES}) target_include_directories(glad PUBLIC include) From cfe521d7a71d252a1f967d7eb16909380eb19535 Mon Sep 17 00:00:00 2001 From: John Peel Date: Fri, 19 Nov 2021 13:15:20 -0500 Subject: [PATCH 22/87] Use dirtyRects to Blit only the needed parts --- html_chromium/ChromiumBrowser.cpp | 34 ++++++++++++------ html_chromium/ImageData.h | 57 +++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index df583d8..5944d0e 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -837,6 +837,15 @@ void ChromiumBrowser::OnPopupSize( CefRefPtr, const CefRect& rect ) m_PopupY = rect.y; } +bool ShouldFullCopy( const CefRenderHandler::RectList& dirtyRects, int width, int height ) { + int dirty_area = 0; + for (auto &&rect : dirtyRects) { + dirty_area += rect.width * rect.height; + } + // TODO: Find optimal threshold using benchmarking. + return dirty_area > 0.8 * width * height; +} + void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height ) { // @@ -856,21 +865,26 @@ void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintEle case PET_VIEW: m_ImageData.Lock(); - m_ImageData.SetData( static_cast( buffer ), width, height ); - // Blit our popup over this image - if ( m_PopupWide > 0 && m_PopupTall > 0 ) - { - // Copy row-by-row because the destination pixels may not be contiguous - for ( int SrcY = 0; SrcY < m_PopupTall; SrcY++ ) + if ( m_ImageData.ResizeData( width, height ) || ShouldFullCopy( dirtyRects, width, height ) ) { + // Copy whole buffer over ImageData + m_ImageData.SetData(static_cast(buffer), width, height); + } else { + // Blit the dirty parts of buffer over ImageData + for (auto &&rect : dirtyRects) { - memcpy( &m_ImageData.m_Data[( SrcY + m_PopupY ) * width * 4 + m_PopupX * 4], - &m_PopupData[SrcY * m_PopupWide * 4], - m_PopupWide * 4 ); + if (!rect.IsEmpty()) { + m_ImageData.Blit(static_cast(buffer), rect.x, rect.y, rect.width, rect.height); + } } } - m_ImageData.SetDirty( true ); + // Blit our popup over the ImageData + if ( m_PopupWide > 0 && m_PopupTall > 0 ) + { + m_ImageData.BlitRelative(m_PopupData, m_PopupX, m_PopupY, m_PopupWide, m_PopupTall); + } + m_ImageData.Unlock(); return; } diff --git a/html_chromium/ImageData.h b/html_chromium/ImageData.h index e052aeb..cab2790 100644 --- a/html_chromium/ImageData.h +++ b/html_chromium/ImageData.h @@ -12,6 +12,7 @@ class ImageData : m_Dirty( false ) , m_Wide( 0 ) , m_Tall( 0 ) + , m_Size( 0 ) , m_Data( nullptr ) {} @@ -30,21 +31,63 @@ class ImageData m_Lock.Release(); } - void SetData( const unsigned char* data, int wide, int tall ) + bool ResizeData(int wide, int tall) { - if ( m_Wide != wide || m_Tall != tall ) + bool resized = wide * tall * 4 > m_Size; + if ( resized ) { - delete[] m_Data; - m_Data = new unsigned char[wide * tall * 4]; + unsigned char* old_Data = m_Data; + m_Size = wide * tall * 4; + m_Data = new unsigned char[m_Size]; + delete[] old_Data; } - memcpy( m_Data, data, wide * tall * 4 ); m_Wide = wide; m_Tall = tall; + return resized; + } + + void Blit(const unsigned char* data, int x, int y, int width, int height) + { + if (x + width > m_Wide || y + height > m_Tall) { + // TODO: Add error log here for invalid Blit. + return; + } + + for ( int SrcY = 0; SrcY < height; SrcY++ ) + { + // TODO: See if AVX copy or SIMD can speed this up? + memcpy( &m_Data[( SrcY + y ) * m_Wide * 4 + x * 4], &data[( SrcY + y ) * m_Wide * 4 + x * 4], width * 4 ); + } + m_Dirty = true; + } + + void BlitRelative(const unsigned char* data, int x, int y, int width, int height) + { + if (x + width > m_Wide || y + height > m_Tall) { + // TODO: Add error log here for invalid Blit. + return; + } + + for ( int SrcY = 0; SrcY < height; SrcY++ ) + { + memcpy( &m_Data[( SrcY + y ) * m_Wide * 4 + x * 4], &data[SrcY * width * 4], width * 4 ); + } + m_Dirty = true; + } + + void SetData( const unsigned char* data, int wide, int tall ) + { + if (wide > m_Wide || tall > m_Tall) { + // TODO: Add warning log here for invalid data set. Call ResizeData first. + ResizeData(wide, tall); + } + + memcpy( m_Data, data, wide * tall * 4 ); m_Dirty = true; } - unsigned char* GetData( int& wide, int& tall ) + const unsigned char* GetData( int& wide, int& tall ) { wide = m_Wide; tall = m_Tall; @@ -62,11 +105,11 @@ class ImageData } private: - friend class ChromiumBrowser; bool m_Dirty; int m_Wide; int m_Tall; + int m_Size; base::Lock m_Lock; unsigned char* m_Data; From 78d75b429406db3ba67ec9dd184a6a2b6430672d Mon Sep 17 00:00:00 2001 From: William Wallace Date: Fri, 19 Nov 2021 23:07:04 +0000 Subject: [PATCH 23/87] replace boring deferring with fun deferring --- html_chromium/ChromiumBrowser.cpp | 236 ++++++++---------------------- html_chromium/ChromiumBrowser.h | 17 ++- 2 files changed, 80 insertions(+), 173 deletions(-) diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 5944d0e..fa04132 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -243,49 +243,25 @@ void ChromiumBrowser::QueueMessage( MessageQueue::Message&& message ) // void ChromiumBrowser::Close() { - if (m_BrowserHost == nullptr) { - m_Deferred.emplace_back( - [] (auto& self) { - self.Close(); - } - ); - - return; - } - - m_BrowserHost->CloseBrowser( true ); + RunOrDeferForInit([this] { + m_BrowserHost->CloseBrowser(true); + }); } void ChromiumBrowser::SetSize( int wide, int tall ) { - if (m_BrowserHost == nullptr) { - m_Deferred.emplace_back( - [=] (auto& self) { - self.SetSize(wide, tall); - } - ); - - return; - } - - m_Wide = wide; - m_Tall = tall; - m_BrowserHost->WasResized(); + RunOrDeferForInit([this, wide, tall] { + m_Wide = wide; + m_Tall = tall; + m_BrowserHost->WasResized(); + }); } void ChromiumBrowser::SetFocused( bool hasFocus ) { - if (m_BrowserHost == nullptr) { - m_Deferred.emplace_back( - [=] (auto& self) { - self.SetFocused(hasFocus); - } - ); - - return; - } - - m_BrowserHost->SetFocus( hasFocus ); + RunOrDeferForInit([this, hasFocus] { + m_BrowserHost->SetFocus(hasFocus); + }); } void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) @@ -394,152 +370,78 @@ void ChromiumBrowser::SendMouseClickEvent( IHtmlClient::MouseEvent gmodMouseEven void ChromiumBrowser::LoadUrl( const std::string& url ) { - if (m_Browser == nullptr) { - std::string url_Copy {url}; - - m_Deferred.emplace_back( - [=] (auto& self) { - self.LoadUrl(url_Copy); - } - ); - - return; - } - - m_Browser->GetMainFrame()->LoadURL( CefString( url ) ); + RunOrDeferForInit([this, url] { + m_Browser->GetMainFrame()->LoadURL(CefString(url)); + }); } void ChromiumBrowser::SetHtml( const std::string& html ) { - if (m_Browser == nullptr) { - std::string html_Copy {html}; - - m_Deferred.emplace_back( - [=] (auto& self) { - self.SetHtml(html_Copy); - } - ); - - return; - } + RunOrDeferForInit([this, html] { + CefURLParts urlParts; + CefString(&urlParts.scheme).FromString("asset"); + CefString(&urlParts.host).FromString("html"); + CefString(&urlParts.path).FromString("/"); + CefString(&urlParts.query).FromString(CefBase64Encode(html.c_str(), html.size())); - // asset://html/?{myhtml} - CefURLParts urlParts; - CefString( &urlParts.scheme ).FromString( "asset" ); - CefString( &urlParts.host ).FromString( "html" ); - CefString( &urlParts.path ).FromString( "/" ); - CefString( &urlParts.query ).FromString( CefBase64Encode( html.c_str(), html.size() ) ); - - CefString url; - if ( !CefCreateURL( urlParts, url ) ) - return; + CefString url; + if (!CefCreateURL(urlParts, url)) + return; - m_Browser->GetMainFrame()->LoadURL( url ); + m_Browser->GetMainFrame()->LoadURL(url); + }); } void ChromiumBrowser::Refresh() { - if (m_Browser == nullptr) { - m_Deferred.emplace_back( - [] (auto& self) { - self.Refresh(); - } - ); - - return; - } - - m_Browser->Reload(); + RunOrDeferForInit([this] { + m_Browser->Reload(); + }); } void ChromiumBrowser::Stop() { - if (m_Browser == nullptr) { - m_Deferred.emplace_back( - [] (auto& self) { - self.Stop(); - } - ); - - return; - } - - m_Browser->StopLoad(); + RunOrDeferForInit([this] { + m_Browser->StopLoad(); + }); } void ChromiumBrowser::GoBack() { - if (m_Browser == nullptr) { - m_Deferred.emplace_back( - [] (auto& self) { - self.GoBack(); - } - ); - - return; - } - - m_Browser->GoBack(); + RunOrDeferForInit([this] { + m_Browser->GoBack(); + }); } void ChromiumBrowser::GoForward() { - if (m_Browser == nullptr) { - m_Deferred.emplace_back( - [] (auto& self) { - self.GoForward(); - } - ); - - return; - } - - m_Browser->GoForward(); + RunOrDeferForInit([this] { + m_Browser->GoForward(); + }); } void ChromiumBrowser::RunJavaScript( const std::string& code ) { - if (m_Browser == nullptr) { - std::string code_Copy {code}; + RunOrDeferForInit([this, code] { + auto message = CefProcessMessage::Create("ExecuteJavaScript"); + auto args = message->GetArgumentList(); - m_Deferred.emplace_back( - [=] (auto& self) { - self.RunJavaScript(code_Copy); - } - ); - - return; - } - - auto message = CefProcessMessage::Create( "ExecuteJavaScript" ); - auto args = message->GetArgumentList(); - - args->SetString( 0, "Lua File" ); - args->SetString( 1, code ); - m_Browser->GetMainFrame()->SendProcessMessage( PID_RENDERER, message ); + args->SetString(0, "Lua File"); + args->SetString(1, code); + m_Browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, message); + }); } void ChromiumBrowser::RegisterJavaScriptFunction( const std::string& objName, const std::string& funcName ) { - if (m_Browser == nullptr) { - std::string objName_Copy {objName}; - std::string funcName_Copy {funcName}; - - m_Deferred.emplace_back( - [=] (auto& self) { - self.RegisterJavaScriptFunction(objName_Copy, funcName_Copy); - } - ); - - return; - } - - auto message = CefProcessMessage::Create( "RegisterFunction" ); - auto args = message->GetArgumentList(); + RunOrDeferForInit([this, objName, funcName] { + auto message = CefProcessMessage::Create("RegisterFunction"); + auto args = message->GetArgumentList(); - args->SetString( 0, objName ); - args->SetString( 1, funcName ); - m_Browser->GetMainFrame()->SendProcessMessage( PID_RENDERER, message ); + args->SetString(0, objName); + args->SetString(1, funcName); + m_Browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, message); + }); } void ChromiumBrowser::SetOpenLinksExternally( bool openLinksExternally ) @@ -549,30 +451,20 @@ void ChromiumBrowser::SetOpenLinksExternally( bool openLinksExternally ) void ChromiumBrowser::ExecuteCallback( int callbackId, const JSValue& paramsArray ) { - if (m_Browser == nullptr) { - JSValue paramsArray_Copy {paramsArray}; - - m_Deferred.emplace_back( - [=] (auto& self) { - self.ExecuteCallback(callbackId, paramsArray_Copy); - } - ); + RunOrDeferForInit([this, callbackId, paramsArray] { + const auto& paramsVector = (static_cast(paramsArray.GetInternalArray()))->GetInternalData(); // spaghetti + auto message = CefProcessMessage::Create("ExecuteCallback"); + auto outArgs = message->GetArgumentList(); - return; - } - - const auto& paramsVector = ( static_cast( paramsArray.GetInternalArray() ) )->GetInternalData(); // spaghetti - auto message = CefProcessMessage::Create( "ExecuteCallback" ); - auto outArgs = message->GetArgumentList(); - - auto cefArgs = CefListValue::Create(); - if ( !JSValuesToCefList( cefArgs, paramsVector ) ) - return; + auto cefArgs = CefListValue::Create(); + if (!JSValuesToCefList(cefArgs, paramsVector)) + return; - outArgs->SetInt( 0, callbackId ); - outArgs->SetList( 1, cefArgs ); + outArgs->SetInt(0, callbackId); + outArgs->SetList(1, cefArgs); - m_Browser->GetMainFrame()->SendProcessMessage( PID_RENDERER, message ); + m_Browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, message); + }); } // @@ -611,10 +503,10 @@ void ChromiumBrowser::OnAfterCreated( CefRefPtr browser ) m_BrowserHost = browser->GetHost(); for (auto& func : m_Deferred) { - func(*this); + func(); } - m_Deferred.clear(); + m_Deferred = {}; } void ChromiumBrowser::OnBeforeClose( CefRefPtr browser ) diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 2994735..f56c2ec 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -28,6 +28,9 @@ class ChromiumBrowser ChromiumBrowser(); ~ChromiumBrowser(); + ChromiumBrowser(const ChromiumBrowser&) = delete; + ChromiumBrowser& operator=(const ChromiumBrowser&) = delete; + ImageData& GetImageData(); MessageQueue& GetMessageQueue(); void QueueMessage( MessageQueue::Message&& message ); @@ -203,5 +206,17 @@ class ChromiumBrowser private: // Functions in this vector will be executed once our underlying CefBrowser is available - std::vector> m_Deferred; + std::vector> m_Deferred; + + template + void RunOrDeferForInit(T func) + { + if (m_Browser != nullptr && m_BrowserHost != nullptr) + { + func(); + return; + } + + m_Deferred.push_back(std::move(func)); + } }; From 3ca386a9b3cbf031f191e63a65adf2b3cdc29077 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Sun, 21 Nov 2021 14:18:44 +0000 Subject: [PATCH 24/87] remove unnecessary files --- gmod_launcher/HtmlPanel.cpp | 135 -------------------------- gmod_launcher/HtmlPanel.h | 45 --------- gmod_launcher/HtmlResourceHandler.cpp | 32 ------ gmod_launcher/HtmlResourceHandler.h | 15 --- gmod_launcher/HtmlSystemLoader.cpp | 51 ---------- gmod_launcher/HtmlSystemLoader.h | 9 -- gmod_launcher/Window.cpp | 91 ----------------- gmod_launcher/Window.h | 39 -------- 8 files changed, 417 deletions(-) delete mode 100644 gmod_launcher/HtmlPanel.cpp delete mode 100644 gmod_launcher/HtmlPanel.h delete mode 100644 gmod_launcher/HtmlResourceHandler.cpp delete mode 100644 gmod_launcher/HtmlResourceHandler.h delete mode 100644 gmod_launcher/HtmlSystemLoader.cpp delete mode 100644 gmod_launcher/HtmlSystemLoader.h delete mode 100644 gmod_launcher/Window.cpp delete mode 100644 gmod_launcher/Window.h diff --git a/gmod_launcher/HtmlPanel.cpp b/gmod_launcher/HtmlPanel.cpp deleted file mode 100644 index a0fa664..0000000 --- a/gmod_launcher/HtmlPanel.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#include "HtmlPanel.h" -#include "HtmlSystemLoader.h" -#include "glad/glad.h" - -HtmlPanel::HtmlPanel() - : m_Texture( nullptr ) - , m_TextureWidth( -1 ) - , m_TextureHeight( -1 ) -{ - m_HtmlClient = g_pHtmlSystem->CreateClient( this ); -} - -HtmlPanel::~HtmlPanel() -{ - m_HtmlClient->Close(); - m_HtmlClient = nullptr; - - DestroyTexture(); -} - -void HtmlPanel::LoadUrl( const char* pUrl ) -{ - m_HtmlClient->LoadUrl( pUrl ); -} - -void HtmlPanel::UpdateTexture() -{ - int width, height; - const unsigned char* data; - - if ( !m_HtmlClient->LockImageData() ) - return; - - data = m_HtmlClient->GetImageData( width, height ); - - if ( m_Texture == nullptr || m_TextureWidth != width || m_TextureHeight != height ) - { - // Make a new texture (technically an unnecessary destroy but it looks like this in Source) - DestroyTexture(); - - // No error handling? - GLuint texture; - glGenTextures( 1, &texture ); - glBindTexture( GL_TEXTURE_2D, texture ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_BGRA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); - - m_Texture = reinterpret_cast( texture ); - m_TextureWidth = width; - m_TextureHeight = height; - } - else - { - // Update current texture - glBindTexture( GL_TEXTURE_2D, reinterpret_cast( m_Texture ) ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); - } - - m_HtmlClient->UnlockImageData(); -} - -void HtmlPanel::DestroyTexture() -{ - m_Texture = nullptr; - m_TextureWidth = -1; - m_TextureHeight = -1; -} - -void HtmlPanel::Render() -{ - UpdateTexture(); - - if ( m_Texture == nullptr ) - return; - - if ( ImGui::Begin( "Browser", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar ) ) - { - ImGui::Image( m_Texture, ImVec2( m_TextureWidth, m_TextureHeight ) ); - } - - ImGui::End(); -} - -void HtmlPanel::OnAddressChange( const char* address ) -{ - std::cout << "OnAddressChange: " << address << std::endl; -} - -void HtmlPanel::OnConsoleMessage( const char* message, const char* source, int lineNumber ) -{ - std::cout << source << ":" << lineNumber << ": " << message << std::endl; -} - -void HtmlPanel::OnTitleChange( const char* title ) -{ - std::cout << "OnTitleChange: " << title << std::endl; -} - -void HtmlPanel::OnTargetUrlChange( const char* url ) -{ - std::cout << "OnTargetUrlChange: " << url << std::endl; -} - -void HtmlPanel::OnCursorChange( CursorType cursorType ) -{ - -} - -void HtmlPanel::OnLoadStart( const char* address ) -{ - std::cout << "OnLoadStart: " << address << std::endl; -} - -void HtmlPanel::OnLoadEnd( const char* address ) -{ - std::cout << "OnLoadEnd: " << address << std::endl; -} - -void HtmlPanel::OnDocumentReady( const char* address ) -{ - std::cout << "OnDocumentReady: " << address << std::endl; -} - -void HtmlPanel::OnCreateChildView( const char* sourceUrl, const char* targetUrl, bool isPopup ) -{ - -} - -JSValue HtmlPanel::OnJavaScriptCall( const char* objName, const char* funcName, const JSValue& params ) -{ - return JSValue(); -} diff --git a/gmod_launcher/HtmlPanel.h b/gmod_launcher/HtmlPanel.h deleted file mode 100644 index 6004784..0000000 --- a/gmod_launcher/HtmlPanel.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "html/IHtmlSystem.h" -#include "imgui.h" - -class HtmlPanel : public IHtmlClientListener -{ -public: - HtmlPanel(); - ~HtmlPanel(); - - HtmlPanel( const HtmlPanel& ) = delete; - HtmlPanel( HtmlPanel&& ) = delete; - - HtmlPanel& operator=( const HtmlPanel& ) = delete; - HtmlPanel& operator=( HtmlPanel&& ) = delete; - - void LoadUrl( const char* pUrl ); - - void Render(); - - // - // IHtmlClientListener - // - void OnAddressChange( const char* address ) override; - void OnConsoleMessage( const char* message, const char* source, int lineNumber ) override; - void OnTitleChange( const char* title ) override; - void OnTargetUrlChange( const char* url ) override; - void OnCursorChange( CursorType cursorType ) override; - void OnLoadStart( const char* address ) override; - void OnLoadEnd( const char* address ) override; - void OnDocumentReady( const char* address ) override; - void OnCreateChildView( const char* sourceUrl, const char* targetUrl, bool isPopup ) override; - JSValue OnJavaScriptCall( const char* objName, const char* funcName, const JSValue& params ) override; - -private: - void UpdateTexture(); - void DestroyTexture(); - - IHtmlClient* m_HtmlClient; - - ImTextureID m_Texture; - int m_TextureWidth; - int m_TextureHeight; -}; \ No newline at end of file diff --git a/gmod_launcher/HtmlResourceHandler.cpp b/gmod_launcher/HtmlResourceHandler.cpp deleted file mode 100644 index 60de7f6..0000000 --- a/gmod_launcher/HtmlResourceHandler.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "HtmlResourceHandler.h" -#include - -struct HtmlResource -{ - -}; - -HtmlResource* HtmlResourceHandler::OpenResource( const char* pHost, const char* pPath ) -{ - return nullptr; -} - -void HtmlResourceHandler::CloseResource( HtmlResource* resource ) -{ - delete resource; -} - -size_t HtmlResourceHandler::GetLength( HtmlResource* resource ) -{ - return 0; -} - -void HtmlResourceHandler::ReadData( HtmlResource* resource, char* pDestination, size_t length ) -{ - -} - -void HtmlResourceHandler::Message( const char* data ) -{ - std::cout << data << std::endl; -} \ No newline at end of file diff --git a/gmod_launcher/HtmlResourceHandler.h b/gmod_launcher/HtmlResourceHandler.h deleted file mode 100644 index 10ac9bd..0000000 --- a/gmod_launcher/HtmlResourceHandler.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "html/IHtmlResourceHandler.h" - -class HtmlResourceHandler : public IHtmlResourceHandler -{ - HtmlResource* OpenResource( const char* pHost, const char* pPath ) override; - void CloseResource( HtmlResource* resource ) override; - - size_t GetLength( HtmlResource* resource ) override; - void ReadData( HtmlResource* resource, char* pDestination, size_t length ) override; - - void Message( const char* data ) override; -}; - diff --git a/gmod_launcher/HtmlSystemLoader.cpp b/gmod_launcher/HtmlSystemLoader.cpp deleted file mode 100644 index dcddf21..0000000 --- a/gmod_launcher/HtmlSystemLoader.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include - -#include "HtmlSystemLoader.h" -#include "HtmlResourceHandler.h" - -HtmlResourceHandler g_ResourceHandler; -IHtmlSystem* g_pHtmlSystem; - -// -// Platform specific shit to dynamically find our html lib -// -bool HtmlSystem_Init() -{ -#ifdef _WIN32 - HMODULE hDLL = LoadLibrary( "html_chromium.dll" ); - - if ( hDLL == nullptr ) - return false; - - IHtmlSystem** ppHtmlSystem = reinterpret_cast( GetProcAddress( hDLL, "g_pHtmlSystem" ) ); - - if ( ppHtmlSystem == nullptr || *ppHtmlSystem == nullptr ) - return false; - - g_pHtmlSystem = *ppHtmlSystem; - - char pPath[MAX_PATH] = { 0 }; - if ( _getcwd( pPath, sizeof( pPath ) ) != pPath ) - return false; - - std::string finalPath( pPath ); - finalPath.append( "/../../" ); // This has to point to where our 'hl2.exe' would live - - return g_pHtmlSystem->Init( finalPath.c_str(), &g_ResourceHandler ); -#else - #error -#endif -} - -void HtmlSystem_Tick() -{ - g_pHtmlSystem->Update(); -} - -void HtmlSystem_Shutdown() -{ - g_pHtmlSystem->Shutdown(); - g_pHtmlSystem = nullptr; -} \ No newline at end of file diff --git a/gmod_launcher/HtmlSystemLoader.h b/gmod_launcher/HtmlSystemLoader.h deleted file mode 100644 index f7780e0..0000000 --- a/gmod_launcher/HtmlSystemLoader.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "html/IHtmlSystem.h" - -extern IHtmlSystem* g_pHtmlSystem; - -bool HtmlSystem_Init(); -void HtmlSystem_Tick(); -void HtmlSystem_Shutdown(); \ No newline at end of file diff --git a/gmod_launcher/Window.cpp b/gmod_launcher/Window.cpp deleted file mode 100644 index cb736a5..0000000 --- a/gmod_launcher/Window.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include - -#include "Window.h" -#include "glad/glad.h" -#include "GLFW/glfw3.h" - - -static void glfw_framebuffersize_callback( GLFWwindow* glfwWindow, int width, int height ) -{ - auto* window = Window::FromInternal( glfwWindow ); - - if ( window == nullptr ) - return; - - window->OnFramebufferResized( width, height ); -} - -// - -std::unique_ptr Window::Create( const char* pTitle ) -{ - GLFWwindow* glfwWindow; - - glfwWindowHint( GLFW_CLIENT_API, GLFW_OPENGL_API ); - glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 ); - glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 0 ); - - glfwWindow = glfwCreateWindow( 640, 480, pTitle, NULL, NULL ); - if ( !glfwWindow ) - return nullptr; - - glfwMakeContextCurrent( glfwWindow ); - glfwSwapInterval( 1 ); - - if ( !gladLoadGLLoader( reinterpret_cast( glfwGetProcAddress ) ) ) - return nullptr; - - return std::unique_ptr( new Window( glfwWindow ) ); -} - -Window* Window::FromInternal( GLFWwindow* glfwWindow ) -{ - return static_cast( glfwGetWindowUserPointer( glfwWindow ) ); -} - -// - -Window::Window( GLFWwindow* glfwWindow ) - : _glfwWindow( glfwWindow ) -{ - glfwSetWindowUserPointer( _glfwWindow, this ); - - // GLFW Events - glfwSetFramebufferSizeCallback( _glfwWindow, glfw_framebuffersize_callback ); - - // Trigger some default events - int width, height; - glfwGetFramebufferSize( glfwWindow, &width, &height ); - OnFramebufferResized( width, height ); -} - -Window::~Window() -{ - glfwSetWindowUserPointer( _glfwWindow, nullptr ); - glfwDestroyWindow( _glfwWindow ); -} - -bool Window::ShouldClose() -{ - return glfwWindowShouldClose( _glfwWindow ); -} - -void Window::SwapBuffers() -{ - return glfwSwapBuffers( _glfwWindow ); -} - -void Window::PollEvents() -{ - glfwPollEvents(); -} - -GLFWwindow* Window::GetInternal() -{ - return _glfwWindow; -} - -void Window::OnFramebufferResized( int width, int height ) -{ - glViewport( 0, 0, width, height ); -} \ No newline at end of file diff --git a/gmod_launcher/Window.h b/gmod_launcher/Window.h deleted file mode 100644 index 1c959af..0000000 --- a/gmod_launcher/Window.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -struct GLFWwindow; - -class Window -{ -public: - Window( GLFWwindow* glfwWindow ); - ~Window(); - - Window( const Window& ) = delete; - Window( Window&& ) = delete; - - Window& operator=( const Window& ) = delete; - Window& operator=( Window&& ) = delete; - -private: - GLFWwindow* _glfwWindow; - int _viewportWidth; - int _viewportHeight; - -public: - static std::unique_ptr Create( const char* pTitle ); - static Window* FromInternal( GLFWwindow* glfwWindow ); - - bool ShouldClose(); - void SwapBuffers(); - void PollEvents(); - - GLFWwindow* GetInternal(); - - int GetViewportWidth() { return _viewportWidth; } - int GetViewportHeight() { return _viewportHeight; } - -public: - void OnFramebufferResized( int width, int height ); -}; \ No newline at end of file From a9bb5f7661c4b277a20845721e2efbdda1839570 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Fri, 19 Nov 2021 23:19:46 +0000 Subject: [PATCH 25/87] always init all members (ish) of JSvalue --- html/html/JSValue.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/html/html/JSValue.h b/html/html/JSValue.h index 9c48caa..68c7ec1 100644 --- a/html/html/JSValue.h +++ b/html/html/JSValue.h @@ -50,6 +50,7 @@ class JSValue JSValue() : _type( Type::Undefined ) + , _bool( false ) {} JSValue( bool value ) @@ -79,6 +80,7 @@ class JSValue JSValue( const JSValue& other ) : _type( other._type ) + , _bool( false ) { switch ( _type ) { @@ -104,6 +106,7 @@ class JSValue JSValue( JSValue&& other ) noexcept : _type( other._type ) + , _bool(false) { switch ( _type ) { From fbbbd95d48cf7cf45b2a028961f218e97c7d4e35 Mon Sep 17 00:00:00 2001 From: William Wallace Date: Fri, 19 Nov 2021 23:25:27 +0000 Subject: [PATCH 26/87] fix texture id conversions in example_host --- example_host/HtmlPanel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example_host/HtmlPanel.cpp b/example_host/HtmlPanel.cpp index a0fa664..587274d 100644 --- a/example_host/HtmlPanel.cpp +++ b/example_host/HtmlPanel.cpp @@ -48,14 +48,14 @@ void HtmlPanel::UpdateTexture() glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexImage2D( GL_TEXTURE_2D, 0, GL_BGRA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); - m_Texture = reinterpret_cast( texture ); + m_Texture = reinterpret_cast( static_cast( texture ) ); m_TextureWidth = width; m_TextureHeight = height; } else { // Update current texture - glBindTexture( GL_TEXTURE_2D, reinterpret_cast( m_Texture ) ); + glBindTexture( GL_TEXTURE_2D, static_cast( reinterpret_cast( m_Texture ) ) ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, data ); } From bf479c0570c67761bb3027564afe6f35628ebfdb Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Nov 2021 13:09:57 -0500 Subject: [PATCH 27/87] Update CEF target to 96.0.16+g89c902b+chromium-96.0.4664.55 --- CMakeLists.txt | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e30f367..a0e1650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,12 +52,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.13+g622afbd+chromium-96.0.4664.0_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") diff --git a/README.md b/README.md index cb9e78e..4ae7396 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **95.7.18+g0d6005e+chromium-95.0.4638.69** +- **96.0.16+g89c902b+chromium-96.0.4664.55** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From c714a77554e42079f86b81f198df5a3f6b976eb9 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 1 Dec 2021 23:11:25 -0500 Subject: [PATCH 28/87] Update User Agent strings to 96.0.4664.55 --- html_chromium/ChromiumSystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 7d094c6..b7db4d9 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -164,7 +164,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); @@ -172,7 +172,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -186,7 +186,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OSX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); #else #error #endif From 2a65cd470d99fc3191c164b682252faedfa4521c Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 1 Dec 2021 23:12:13 -0500 Subject: [PATCH 29/87] Enable GPU Compositing on chromium_process too --- html_chromium/chromium_process/ChromiumApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 690d595..971317d 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -169,7 +169,7 @@ static bool CefListToV8Values( CefV8ValueList& outList, const CefRefPtr command_line ) { command_line->AppendSwitch( "enable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) + //command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); From f7443309fb4649627197444b0866f71b8f0cda0e Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 11 Feb 2022 15:11:07 -0500 Subject: [PATCH 30/87] - Update CEF target to 98.1.19+g57be9e2+chromium-98.0.4758.80 - Update User Agent strings to Chrome/98.0.4758.80 - Disable OnAcceleratedPaint: GPU Composition and Shared Texture Enabled (Turns out GPU acceleration has extremely poor latency when you can't give GMod the texture) --- CMakeLists.txt | 6 +++--- html_chromium/ChromiumSystem.cpp | 10 +++++----- html_chromium/chromium_process/ChromiumApp.cpp | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0e1650..16b6747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,12 +52,12 @@ add_subdirectory(thirdparty/glfw-3.3.2) # Chromium Project if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_windows32) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_windows32) else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_windows64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_windows64) endif() elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_96.0.16+g89c902b+chromium-96.0.4664.55_linux64) + set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_linux64) add_definitions(-DPOSIX -DLINUX) else() message(FATAL_ERROR "No CEF_PATH") diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index b7db4d9..88f424e 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -33,7 +33,7 @@ class ChromiumApp void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override { command_line->AppendSwitch( "enable-gpu" ); - command_line->AppendSwitch( "disable-gpu-compositing" ); // TODO: Figure out why GPU Compositing being enabled causes OnPaint not to be called (regardless of enable/disable-gpu) + command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); @@ -164,7 +164,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); @@ -172,7 +172,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -186,7 +186,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OSX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 GMod/13" ); + CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); #else #error #endif @@ -270,7 +270,7 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) CefWindowInfo windowInfo; windowInfo.SetAsWindowless( 0 ); // TODO: See ChromiumBrowser::OnAcceleratedPaint - windowInfo.shared_texture_enabled = true; + //windowInfo.shared_texture_enabled = true; CefBrowserSettings browserSettings; CefString( &browserSettings.default_encoding ).FromString( "UTF-8" ); diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/html_chromium/chromium_process/ChromiumApp.cpp index 971317d..a3467b2 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/html_chromium/chromium_process/ChromiumApp.cpp @@ -169,7 +169,7 @@ static bool CefListToV8Values( CefV8ValueList& outList, const CefRefPtr command_line ) { command_line->AppendSwitch( "enable-gpu" ); - //command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) + command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); From 1c7fe84e8af50bb3f5a25b563e9c7c71539df477 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sun, 13 Feb 2022 22:09:27 -0500 Subject: [PATCH 31/87] - Update CEF version in README - Add App Icon to gmod_launcher --- README.md | 2 +- gmod_launcher/CMakeLists.txt | 4 +++- gmod.ico => gmod_launcher/gmod.ico | Bin gmod_launcher/gmod_icon.rc | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) rename gmod.ico => gmod_launcher/gmod.ico (100%) create mode 100644 gmod_launcher/gmod_icon.rc diff --git a/README.md b/README.md index 4ae7396..28c6a1f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **96.0.16+g89c902b+chromium-96.0.4664.55** +- **98.1.19+g57be9e2+chromium-98.0.4758.80** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. diff --git a/gmod_launcher/CMakeLists.txt b/gmod_launcher/CMakeLists.txt index 88ff96a..33827a2 100644 --- a/gmod_launcher/CMakeLists.txt +++ b/gmod_launcher/CMakeLists.txt @@ -12,7 +12,9 @@ endif() # Lame include_directories(${CEF_PATH}) -add_executable(gmod_launcher WIN32 ${SOURCES}) +set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/gmod_icon.rc") + +add_executable(gmod_launcher WIN32 ${SOURCES} ${APP_ICON_RESOURCE_WINDOWS}) target_link_libraries(gmod_launcher html libcef_imp libcef_dll_wrapper) target_link_libraries(gmod_launcher optimized cef_sandbox) diff --git a/gmod.ico b/gmod_launcher/gmod.ico similarity index 100% rename from gmod.ico rename to gmod_launcher/gmod.ico diff --git a/gmod_launcher/gmod_icon.rc b/gmod_launcher/gmod_icon.rc new file mode 100644 index 0000000..f8574f0 --- /dev/null +++ b/gmod_launcher/gmod_icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "gmod.ico" From 66aaa611fa5dcc4af116fe3785fedda63decc410 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 16 Feb 2022 00:27:34 -0500 Subject: [PATCH 32/87] Update README for accurate info --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28c6a1f..7baa101 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ cd build_x64 cmake -G "Visual Studio 16 2019" -A x64 .. ``` -After running either of these sets of commands, you can enter your created directory and open the `gmod-html.sln` solution in Visual Studio. Compiling the `INSTALL` project will place a complete build into the `dist` folder by default. +After running either of these sets of commands, you can enter your created directory and open the `gmod-html.sln` solution in Visual Studio. Compiling the `INSTALL` project will place a complete build into the `/out` folder by default. ### Linux #### Requirements @@ -49,10 +49,10 @@ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. make && make install ``` -This will place a complete build into the `dist` folder by default. +This will place a complete build into the `build/out` folder by default. ### macOS -Todo +TODO: Still working on this. ## TODO - Improve the CMake files. We don't use them for GMod builds so they're a bit wonky. From 02ad7b3c97ea30fa83c0918499fe61038136fd19 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 18 Feb 2022 13:27:29 -0500 Subject: [PATCH 33/87] Fixing a couple of things that fell through the cracks --- html_chromium/CMakeLists.txt | 1 + html_chromium/ChromiumSystem.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index d092d2e..8fdaa09 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -1,3 +1,4 @@ +# TODO: Update Resources for CEF 95+ if(WIN32) set(BINARIES ${CEF_PATH}/Release/chrome_elf.dll diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 88f424e..feeda85 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -24,7 +24,6 @@ namespace fs = std::experimental::filesystem; class ChromiumApp : public CefApp - , public CefRenderProcessHandler { public: // From fb8f9392df754893686efb1ac579133359ec57b3 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 01:21:45 -0500 Subject: [PATCH 34/87] Initial work on remaking cmake files to align with CEF recommendations --- .gitignore | 7 +- CMakeLists.txt | 132 +++++++----------- chromium_process/CMakeLists.txt | 85 +++++++++++ .../ChromiumApp.cpp | 2 +- .../ChromiumApp.h | 0 chromium_process/Linux.cpp | 8 ++ chromium_process/Windows.cpp | 25 ++++ chromium_process/macOS.cpp | 24 ++++ .../resources/mac/helper-Info.plist | 33 +++++ example_host/CMakeLists.txt | 42 ++---- example_host/HtmlPanel.cpp | 1 + example_host/HtmlPanel.h | 3 +- example_host/HtmlResourceHandler.cpp | 3 +- example_host/HtmlResourceHandler.h | 2 +- example_host/HtmlSystemLoader.cpp | 35 ++++- example_host/Main.cpp | 64 ++++----- example_host/Window.cpp | 5 +- example_host/Window.h | 3 +- gmod_launcher/Main.cpp | 2 +- html/CMakeLists.txt | 1 - html/html/IHtmlClient.h | 2 +- html/html/IHtmlClientListener.h | 2 +- html/html/IHtmlResourceHandler.h | 2 +- html/html/IHtmlSystem.h | 3 - html/html/JSValue.h | 7 +- html_chromium/CMakeLists.txt | 84 ++++------- html_chromium/ChromiumBrowser.cpp | 11 +- html_chromium/ChromiumClient.cpp | 3 +- html_chromium/ChromiumSystem.cpp | 47 ++++--- html_chromium/HtmlResourceHandler.cpp | 2 +- html_chromium/ImageData.h | 2 +- html_chromium/JSObjects.cpp | 2 +- html_chromium/JSObjects.h | 2 +- html_chromium/MessageQueue.h | 2 +- html_chromium/cef_end.h | 2 +- html_chromium/cef_start.h | 2 +- html_chromium/chromium_process/CMakeLists.txt | 18 --- html_chromium/chromium_process/Linux.cpp | 15 -- html_chromium/chromium_process/Windows.cpp | 20 --- .../chromium_process/chromium_process.vpc | 45 ------ html_chromium/chromium_process/macOS.cpp | 28 ---- html_chromium/html_chromium.vpc | 74 ---------- html_stub/CMakeLists.txt | 10 +- html_stub/html_stub.vpc | 35 ----- thirdparty/imgui-1.74/CMakeLists.txt | 7 +- 45 files changed, 410 insertions(+), 494 deletions(-) create mode 100644 chromium_process/CMakeLists.txt rename {html_chromium/chromium_process => chromium_process}/ChromiumApp.cpp (95%) rename {html_chromium/chromium_process => chromium_process}/ChromiumApp.h (100%) create mode 100644 chromium_process/Linux.cpp create mode 100644 chromium_process/Windows.cpp create mode 100644 chromium_process/macOS.cpp create mode 100644 chromium_process/resources/mac/helper-Info.plist delete mode 100644 html_chromium/chromium_process/CMakeLists.txt delete mode 100644 html_chromium/chromium_process/Linux.cpp delete mode 100644 html_chromium/chromium_process/Windows.cpp delete mode 100644 html_chromium/chromium_process/chromium_process.vpc delete mode 100644 html_chromium/chromium_process/macOS.cpp delete mode 100644 html_chromium/html_chromium.vpc delete mode 100644 html_stub/html_stub.vpc diff --git a/.gitignore b/.gitignore index 7ade018..40c04d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -# The directory I like to build under + +# Common build directories /build /build_x64 /build_x86 @@ -6,3 +7,7 @@ # Dependencies that are downloaded/installed separately /thirdparty/cef3/** !/thirdparty/cef3/README.txt + +# IDE files +/.vscode +/.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 16b6747..c79860a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,103 +1,77 @@ -if(WIN32) - # Required for MSVC_RUNTIME_LIBRARY - cmake_minimum_required(VERSION 3.15) - cmake_policy(SET CMP0091 NEW) -else() - cmake_minimum_required(VERSION 3.19) -endif() -project(gmod-html LANGUAGES CXX) - -# We can only do release builds -set(CMAKE_CONFIGURATION_TYPES Release Debug) +cmake_minimum_required(VERSION 3.19) +set(CMAKE_CONFIGURATION_TYPES Debug Release) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Match GMod and our prebuilt libraries -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/dist CACHE PATH "..." FORCE) +project(gmod_html) + +set_property(GLOBAL PROPERTY OS_FOLDERS ON) + +set(CEF_VERSION "98.1.21+g9782362+chromium-98.0.4758.102") + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") + if("${PROJECT_ARCH}" STREQUAL "arm64") + set(CEF_PLATFORM "macosarm64") + else() + set(CEF_PLATFORM "macosx64") + endif() +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(CEF_PLATFORM "linux64") + else() + set(CEF_PLATFORM "linux32") + endif() +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(CEF_PLATFORM "windows64") + else() + set(CEF_PLATFORM "windows32") + endif() endif() -if(WIN32) - if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(GAME_BIN_DIR .) - else() - set(GAME_BIN_DIR win64) - endif() -elseif(UNIX AND NOT APPLE) - set(GAME_BIN_DIR linux64) -else() - message(FATAL_ERROR "No GAME_DIR definition") -endif() - - -# ImGui -add_subdirectory(thirdparty/imgui-1.74) +set(CEF_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_${CEF_VERSION}_${CEF_PLATFORM}") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CEF_ROOT}/cmake") -# glad -add_subdirectory(thirdparty/glad) +find_package(CEF REQUIRED) +add_subdirectory(thirdparty/glad EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/imgui-1.74 EXCLUDE_FROM_ALL) -# GLFW set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -add_subdirectory(thirdparty/glfw-3.3.2) - -# Chromium Project -if(WIN32) - if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_windows32) - else() - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_windows64) - endif() -elseif(UNIX AND NOT APPLE) - set(CEF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cef3/cef_binary_98.1.19+g57be9e2+chromium-98.0.4758.80_linux64) - add_definitions(-DPOSIX -DLINUX) -else() - message(FATAL_ERROR "No CEF_PATH") -endif() - -add_subdirectory(${CEF_PATH} EXCLUDE_FROM_ALL) - -# Chromium Import Lib -add_library(libcef_imp SHARED IMPORTED) +add_subdirectory(thirdparty/glfw-3.3.2 EXCLUDE_FROM_ALL) -if(WIN32) - set_target_properties(libcef_imp PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/libcef.dll) - set_target_properties(libcef_imp PROPERTIES IMPORTED_IMPLIB ${CEF_PATH}/Release/libcef.lib) -elseif(UNIX AND NOT APPLE) - set_target_properties(libcef_imp PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/libcef.so) +if(GEN_NINJA OR GEN_MAKEFILES) + set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/${CMAKE_BUILD_TYPE}") else() - message(FATAL_ERROR "No libcef_imp import library set") + set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/$") endif() +set(CEF_TARGET_OUT_DIR ${CEF_TARGET_OUT_DIR}/bin/${CEF_PLATFORM}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CEF_TARGET_OUT_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CEF_TARGET_OUT_DIR}) -# Chromium Sandbox (Not a library on Linux) -if(WIN32 OR APPLE) - add_library(cef_sandbox STATIC IMPORTED) +add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper EXCLUDE_FROM_ALL) - if(WIN32) - set_target_properties(cef_sandbox PROPERTIES IMPORTED_LOCATION ${CEF_PATH}/Release/cef_sandbox.lib) - else() - message(FATAL_ERROR "No cef_sandbox library set") - endif() -endif() +add_subdirectory(${CEF_ROOT}/tests/cefclient EXCLUDE_FROM_ALL) +add_subdirectory(${CEF_ROOT}/tests/cefsimple EXCLUDE_FROM_ALL) +add_subdirectory(${CEF_ROOT}/tests/gtest EXCLUDE_FROM_ALL) +add_subdirectory(${CEF_ROOT}/tests/ceftests EXCLUDE_FROM_ALL) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -# Our projects add_subdirectory(html) +# NOTE: This can probably be removed. It would only need rebuilding if the html interface changes (and this is unlikely). add_subdirectory(html_stub) add_subdirectory(html_chromium) -if(WIN32) - add_subdirectory(example_host) - add_subdirectory(gmod_launcher) +if(OS_LINUX OR OS_MAC) + add_subdirectory(chromium_process) +elseif(OS_WIN) + add_subdirectory(gmod_launcher) endif() -if(UNIX) - add_subdirectory(html_chromium/chromium_process) -endif() +add_subdirectory(example_host EXCLUDE_FROM_ALL) + +PRINT_CEF_CONFIG() \ No newline at end of file diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt new file mode 100644 index 0000000..b2aeae8 --- /dev/null +++ b/chromium_process/CMakeLists.txt @@ -0,0 +1,85 @@ + +set(TARGET chromium_process) +set(SOURCES + ChromiumApp.cpp + ChromiumApp.h) +set(RESOURCES ) + +set(SOURCES_LINUX Linux.cpp) +set(SOURCES_MAC macOS.cpp) +set(SOURCES_WINDOWS Windows.cpp) + +APPEND_PLATFORM_SOURCES(SOURCES) + +if(OS_LINUX OR OS_WINDOWS) + ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") + + add_executable(${TARGET} ${SOURCES}) + SET_EXECUTABLE_TARGET_PROPERTIES(${TARGET}) + add_dependencies(${TARGET} libcef_dll_wrapper) + + target_link_libraries(${TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS}) + + if(OS_LINUX) + set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") + set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + elseif(USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) + endif() + + LIST(REMOVE_ITEM CEF_RESOURCE_FILES "icudtl.dat") + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}") + if(OS_LINUX) + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}/../linux32/chromium") + else() + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}/chromium") + endif() +else() + set(HELPER_TARGET "gmod_Helper") + set(HELPER_OUTPUT_NAME "gmod Helper") + add_custom_target(${TARGET} ALL) + + if(USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + endif() + + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework" + "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks/Chromium Embedded Framework.framework" + VERBATIM) + + foreach(_suffix_list ${CEF_HELPER_APP_SUFFIXES}) + string(REPLACE ":" ";" _suffix_list ${_suffix_list}) + list(GET _suffix_list 0 _name_suffix) + list(GET _suffix_list 1 _target_suffix) + list(GET _suffix_list 2 _plist_suffix) + + set(_helper_target "${HELPER_TARGET}${_target_suffix}") + set(_helper_output_name "${HELPER_OUTPUT_NAME}${_name_suffix}") + + set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist") + file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/helper-Info.plist" _plist_contents) + string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) + string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) + string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents}) + file(WRITE ${_helper_info_plist} ${_plist_contents}) + + add_executable(${_helper_target} MACOSX_BUNDLE ${SOURCES}) + SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target}) + add_dependencies(${_helper_target} libcef_dll_wrapper) + target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS}) + set_target_properties(${_helper_target} PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist} + OUTPUT_NAME ${_helper_output_name} + RUNTIME_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks") + + if(USE_SANDBOX) + target_link_libraries(${_helper_target} cef_sandbox_lib) + endif() + endforeach() +endif() diff --git a/html_chromium/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp similarity index 95% rename from html_chromium/chromium_process/ChromiumApp.cpp rename to chromium_process/ChromiumApp.cpp index a3467b2..98a54f3 100644 --- a/html_chromium/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -192,7 +192,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); // Chromium 80 removed this but only sometimes. - command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); + // command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); // Disable site isolation until we implement passing registered Lua functions between processes command_line->AppendSwitch( "disable-site-isolation-trials" ); diff --git a/html_chromium/chromium_process/ChromiumApp.h b/chromium_process/ChromiumApp.h similarity index 100% rename from html_chromium/chromium_process/ChromiumApp.h rename to chromium_process/ChromiumApp.h diff --git a/chromium_process/Linux.cpp b/chromium_process/Linux.cpp new file mode 100644 index 0000000..780ded4 --- /dev/null +++ b/chromium_process/Linux.cpp @@ -0,0 +1,8 @@ +#include "include/cef_app.h" +#include "ChromiumApp.h" + +int main(int argc, char* argv[]) { + CefMainArgs main_args(argc, argv); + CefRefPtr app(new ChromiumApp()); + return CefExecuteProcess(main_args, app, nullptr); +} diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp new file mode 100644 index 0000000..a88a686 --- /dev/null +++ b/chromium_process/Windows.cpp @@ -0,0 +1,25 @@ +#include +#include "include/cef_app.h" + +#ifdef CEF_USE_SANDBOX + #include "include/cef_sandbox_win.h" +#endif + +#include "ChromiumApp.h" + +int ChromiumMain(HINSTANCE hInstance) +{ + CefEnableHighDPISupport(); + + void* sandbox_info = nullptr; + +#ifdef CEF_USE_SANDBOX + CefScopedSandboxInfo scoped_sandbox; + sandbox_info = scoped_sandbox.sandbox_info(); +#endif + + CefMainArgs main_args(hInstance); + CefRefPtr app(new ChromiumApp()); + + return CefExecuteProcess(main_args, app, sandbox_info); +} diff --git a/chromium_process/macOS.cpp b/chromium_process/macOS.cpp new file mode 100644 index 0000000..d8f5f12 --- /dev/null +++ b/chromium_process/macOS.cpp @@ -0,0 +1,24 @@ +#include "include/cef_app.h" +#include "include/wrapper/cef_library_loader.h" + +#if defined(CEF_USE_SANDBOX) +#include "include/cef_sandbox_mac.h" +#endif + +#include "ChromiumApp.h" + +int main(int argc, char* argv[]) { +#if defined(CEF_USE_SANDBOX) + CefScopedSandboxContext sandbox_context; + if (!sandbox_context.Initialize(argc, argv)) + return 1; +#endif + + CefScopedLibraryLoader library_loader; + if (!library_loader.LoadInHelper()) + return 1; + + CefMainArgs main_args(argc, argv); + CefRefPtr app(new ChromiumApp()); + return CefExecuteProcess(main_args, app, nullptr); +} diff --git a/chromium_process/resources/mac/helper-Info.plist b/chromium_process/resources/mac/helper-Info.plist new file mode 100644 index 0000000..e4d500e --- /dev/null +++ b/chromium_process/resources/mac/helper-Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleDisplayName + ${EXECUTABLE_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.facepunch.garrysmod_webhelper${BUNDLE_ID_SUFFIX} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + LSEnvironment + + MallocNanoZone + 0 + + LSFileQuarantineEnabled + + LSMinimumSystemVersion + 10.11.0 + LSUIElement + 1 + NSSupportsAutomaticGraphicsSwitching + + + \ No newline at end of file diff --git a/example_host/CMakeLists.txt b/example_host/CMakeLists.txt index 4053ded..0a19732 100644 --- a/example_host/CMakeLists.txt +++ b/example_host/CMakeLists.txt @@ -1,3 +1,5 @@ + +set(TARGET example_host) set(SOURCES Main.cpp Window.cpp @@ -9,37 +11,13 @@ set(SOURCES HtmlPanel.cpp HtmlPanel.h) -if(WIN32) - set(SOURCES - ${SOURCES} - ../html_chromium/chromium_process/ChromiumApp.cpp - ../html_chromium/chromium_process/ChromiumApp.h - ../html_chromium/chromium_process/Windows.cpp) -endif() - -# Lame -include_directories(${CEF_PATH}) - -add_executable(example_host WIN32 ${SOURCES}) -target_link_libraries(example_host glfw glad imgui html libcef_imp libcef_dll_wrapper) -target_link_libraries(example_host optimized cef_sandbox) +add_executable(${TARGET} ${SOURCES}) +SET_EXECUTABLE_TARGET_PROPERTIES(${TARGET}) +add_dependencies(${TARGET} html_chromium html chromium_process glfw glad imgui) +target_compile_definitions(${TARGET} PRIVATE IMGUI_IMPL_OPENGL_LOADER_GLAD) +target_link_libraries(${TARGET} glfw glad imgui html) -if(WIN32) - target_link_libraries(example_host - shlwapi - winmm - wsock32 - WS2_32 - comctl32 - rpcrt4 - version - DbgHelp - Psapi - wbemuuid - OleAut32 - SetupAPI - Propsys - Cfgmgr32 - PowrProf - Delayimp.lib) +if(OS_LINUX) + set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") + set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) endif() diff --git a/example_host/HtmlPanel.cpp b/example_host/HtmlPanel.cpp index 587274d..b5fdbd8 100644 --- a/example_host/HtmlPanel.cpp +++ b/example_host/HtmlPanel.cpp @@ -1,3 +1,4 @@ + #include #include "HtmlPanel.h" diff --git a/example_host/HtmlPanel.h b/example_host/HtmlPanel.h index 6004784..90d1ef9 100644 --- a/example_host/HtmlPanel.h +++ b/example_host/HtmlPanel.h @@ -1,3 +1,4 @@ + #pragma once #include "html/IHtmlSystem.h" @@ -42,4 +43,4 @@ class HtmlPanel : public IHtmlClientListener ImTextureID m_Texture; int m_TextureWidth; int m_TextureHeight; -}; \ No newline at end of file +}; diff --git a/example_host/HtmlResourceHandler.cpp b/example_host/HtmlResourceHandler.cpp index 78b3ced..5997d2e 100644 --- a/example_host/HtmlResourceHandler.cpp +++ b/example_host/HtmlResourceHandler.cpp @@ -1,3 +1,4 @@ + #include "HtmlResourceHandler.h" #include @@ -29,4 +30,4 @@ void HtmlResourceHandler::ReadData( HtmlResource* resource, char* pDestination, void HtmlResourceHandler::Message( const char* data ) { std::cout << data << std::endl; -} \ No newline at end of file +} diff --git a/example_host/HtmlResourceHandler.h b/example_host/HtmlResourceHandler.h index 7f37a57..e093be9 100644 --- a/example_host/HtmlResourceHandler.h +++ b/example_host/HtmlResourceHandler.h @@ -1,3 +1,4 @@ + #pragma once #include "html/IHtmlResourceHandler.h" @@ -12,4 +13,3 @@ class HtmlResourceHandler : public IHtmlResourceHandler void Message( const char* data ) override; }; - diff --git a/example_host/HtmlSystemLoader.cpp b/example_host/HtmlSystemLoader.cpp index 7241a95..99eda94 100644 --- a/example_host/HtmlSystemLoader.cpp +++ b/example_host/HtmlSystemLoader.cpp @@ -1,7 +1,19 @@ -#include -#include + #include +#ifdef _WIN32 + #include + #include +#endif + +#ifdef __linux__ + #include + #include + #include + #include + #include +#endif + #include "HtmlSystemLoader.h" #include "HtmlResourceHandler.h" @@ -34,6 +46,25 @@ bool HtmlSystem_Init() finalPath.append( "/../../" ); // This has to point to where our 'hl2.exe' would live return g_pHtmlSystem->Init( finalPath.c_str(), &g_ResourceHandler ); +#elif __linux__ + void* library = dlopen( "html_chromium_client.so", RTLD_LAZY ); + + if ( library == nullptr ) + return false; + + IHtmlSystem** ppHtmlSystem = reinterpret_cast( dlsym( library, "g_pHtmlSystem" ) ); + + if ( ppHtmlSystem == nullptr || *ppHtmlSystem == nullptr ) + return false; + + g_pHtmlSystem = *ppHtmlSystem; + + char pPath[PATH_MAX] = { 0 }; + if ( getcwd( pPath, sizeof( pPath ) ) == NULL ) + return false; + strcat(pPath, "/../../"); + + return g_pHtmlSystem->Init( canonicalize_file_name(pPath), &g_ResourceHandler ); #else #error #endif diff --git a/example_host/Main.cpp b/example_host/Main.cpp index 0632f26..74abcad 100644 --- a/example_host/Main.cpp +++ b/example_host/Main.cpp @@ -1,43 +1,31 @@ -#include -#include -#include "Window.h" -#include "HtmlSystemLoader.h" -#include "HtmlPanel.h" +#include +#include -#include "glad/glad.h" -#include "GLFW/glfw3.h" +#ifndef IMGUI_IMPL_OPENGL_LOADER_GLAD +#define IMGUI_IMPL_OPENGL_LOADER_GLAD +#endif #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" -#if defined( _WIN32 ) && defined( NDEBUG ) - #include "include/cef_sandbox_win.h" +#include "glad/glad.h" +#include "GLFW/glfw3.h" - extern "C" - { - __declspec( dllexport ) void* CreateCefSandboxInfo() - { - return cef_sandbox_info_create(); - } +#include "Window.h" +#include "HtmlSystemLoader.h" +#include "HtmlPanel.h" - __declspec( dllexport ) void DestroyCefSandboxInfo( void* info ) - { - cef_sandbox_info_destroy( info ); - } - } -#endif static void glfw_error_callback( int error, const char* description ) { fprintf( stderr, "Glfw Error %d: %s\n", error, description ); } -int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) -{ - // Sub-process #ifdef _WIN32 +int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { + if ( strstr( lpCmdLine, "--type=" ) ) { int ChromiumMain( HINSTANCE hInstance ); @@ -49,20 +37,26 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, return exit_code; } } +#else +int main( int argc, char** argv ) { #endif - if ( !HtmlSystem_Init() ) + if ( !HtmlSystem_Init() ) { + std::cout << "Failed to initialize HtmlSystem" << std::endl; return -1; + } glfwSetErrorCallback( glfw_error_callback ); - - if ( !glfwInit() ) + if ( !glfwInit() ){ + std::cout << "Failed to initialize GLFW" << std::endl; return -1; + } { auto window = Window::Create( "Example Host" ); if ( !window ) { + std::cout << "Failed to create window" << std::endl; glfwTerminate(); return -1; } @@ -73,25 +67,30 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + ImGui_ImplGlfw_InitForOpenGL( window->GetInternal(), true ); ImGui_ImplOpenGL3_Init( "#version 130" ); - ImGui::GetIO().IniFilename = nullptr; while ( !window->ShouldClose() ) { HtmlSystem_Tick(); window->PollEvents(); - glClearColor( 0.f, 0.f, 0.f, 1.f ); - glClear( GL_COLOR_BUFFER_BIT ); - ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); panel->Render(); - + ImGui::Render(); + + int display_w, display_h; + glfwGetFramebufferSize( window->GetInternal(), &display_w, &display_h ); + glViewport( 0, 0, display_w, display_h ); + glClearColor( 0.f, 0.f, 0.f, 1.f ); + glClear( GL_COLOR_BUFFER_BIT ); + ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() ); window->SwapBuffers(); @@ -104,5 +103,6 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, glfwTerminate(); HtmlSystem_Shutdown(); + return 0; } \ No newline at end of file diff --git a/example_host/Window.cpp b/example_host/Window.cpp index cb736a5..570c386 100644 --- a/example_host/Window.cpp +++ b/example_host/Window.cpp @@ -1,9 +1,12 @@ -#include #include "Window.h" #include "glad/glad.h" #include "GLFW/glfw3.h" +#ifdef _WIN32 + #include +#endif + static void glfw_framebuffersize_callback( GLFWwindow* glfwWindow, int width, int height ) { diff --git a/example_host/Window.h b/example_host/Window.h index 1c959af..d06474a 100644 --- a/example_host/Window.h +++ b/example_host/Window.h @@ -1,3 +1,4 @@ + #pragma once #include @@ -36,4 +37,4 @@ class Window public: void OnFramebufferResized( int width, int height ); -}; \ No newline at end of file +}; diff --git a/gmod_launcher/Main.cpp b/gmod_launcher/Main.cpp index 90c7b5d..6aad617 100644 --- a/gmod_launcher/Main.cpp +++ b/gmod_launcher/Main.cpp @@ -58,7 +58,7 @@ int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, int exit_code = ChromiumMain( hInstance ); - if ( exit_code != -1 ) + if ( exit_code >= 0 ) { return exit_code; } diff --git a/html/CMakeLists.txt b/html/CMakeLists.txt index f27b6b6..83bbbe4 100644 --- a/html/CMakeLists.txt +++ b/html/CMakeLists.txt @@ -8,7 +8,6 @@ set(SOURCES add_library(html INTERFACE) target_include_directories(html INTERFACE ./) -# Let us see the interface in Visual Studio if(MSVC) add_custom_target(html.interface SOURCES ${SOURCES}) endif() diff --git a/html/html/IHtmlClient.h b/html/html/IHtmlClient.h index 9abfc0e..d96527a 100644 --- a/html/html/IHtmlClient.h +++ b/html/html/IHtmlClient.h @@ -95,4 +95,4 @@ class IHtmlClient virtual bool LockImageData() = 0; virtual void UnlockImageData() = 0; virtual const unsigned char* GetImageData( int& imageWide, int& imageTall ) = 0; -}; \ No newline at end of file +}; diff --git a/html/html/IHtmlClientListener.h b/html/html/IHtmlClientListener.h index 44feef3..2dca250 100644 --- a/html/html/IHtmlClientListener.h +++ b/html/html/IHtmlClientListener.h @@ -40,4 +40,4 @@ class IHtmlClientListener // The input and output JSValue instances should be arrays!!! virtual JSValue OnJavaScriptCall( const char* objName, const char* funcName, const JSValue& params ) = 0; -}; \ No newline at end of file +}; diff --git a/html/html/IHtmlResourceHandler.h b/html/html/IHtmlResourceHandler.h index 2a21331..6a4523a 100644 --- a/html/html/IHtmlResourceHandler.h +++ b/html/html/IHtmlResourceHandler.h @@ -20,4 +20,4 @@ class IHtmlResourceHandler // This doesn't belong here at all virtual void Message( const char* data ) = 0; -}; \ No newline at end of file +}; diff --git a/html/html/IHtmlSystem.h b/html/html/IHtmlSystem.h index 336760f..60c07d2 100644 --- a/html/html/IHtmlSystem.h +++ b/html/html/IHtmlSystem.h @@ -43,6 +43,3 @@ class IHtmlSystem #else #error HTMLSYSTEM_EXPORT not defined for platform #endif - - -// Aaaaaaaaaaa \ No newline at end of file diff --git a/html/html/JSValue.h b/html/html/JSValue.h index 68c7ec1..20ba20f 100644 --- a/html/html/JSValue.h +++ b/html/html/JSValue.h @@ -240,17 +240,17 @@ class JSValue // - const bool HashMap_Begin( const char*& pKey, size_t& keySize, const JSValue*& pValue ) const + bool HashMap_Begin( const char*& pKey, size_t& keySize, const JSValue*& pValue ) const { return _pHashMap->Begin( pKey, keySize, pValue ); } - const bool HashMap_Next( const char*& pKey, size_t& keySize, const JSValue*& pValue ) const + bool HashMap_Next( const char*& pKey, size_t& keySize, const JSValue*& pValue ) const { return _pHashMap->Next( pKey, keySize, pValue ); } - const size_t HashMap_Size() const + size_t HashMap_Size() const { return _pHashMap->Size(); } @@ -294,4 +294,3 @@ class JSValue IHashMap* _pHashMap; // Type::HashMap }; }; - diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index 8fdaa09..8f4ff84 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -1,55 +1,6 @@ -# TODO: Update Resources for CEF 95+ -if(WIN32) - set(BINARIES - ${CEF_PATH}/Release/chrome_elf.dll - ${CEF_PATH}/Release/d3dcompiler_47.dll - ${CEF_PATH}/Release/libcef.dll - ${CEF_PATH}/Release/libEGL.dll - ${CEF_PATH}/Release/libGLESv2.dll - ${CEF_PATH}/Release/snapshot_blob.bin - ${CEF_PATH}/Release/v8_context_snapshot.bin - ${CEF_PATH}/Resources/icudtl.dat) -elseif(UNIX AND NOT APPLE) - set(BINARIES - ${CEF_PATH}/Release/chrome-sandbox - ${CEF_PATH}/Release/libcef.so - ${CEF_PATH}/Release/libEGL.so - ${CEF_PATH}/Release/libGLESv2.so - ${CEF_PATH}/Release/snapshot_blob.bin - ${CEF_PATH}/Release/v8_context_snapshot.bin - ${CEF_PATH}/Resources/icudtl.dat) -else() - message(FATAL_ERROR "No IMPORTED_LOCATION for libcef") -endif() - -if(WIN32) - set(RESOURCES - ${CEF_PATH}/Resources/cef.pak - ${CEF_PATH}/Resources/cef_100_percent.pak - ${CEF_PATH}/Resources/cef_200_percent.pak - ${CEF_PATH}/Resources/cef_extensions.pak - ${CEF_PATH}/Resources/devtools_resources.pak) -elseif(UNIX AND NOT APPLE) - set(RESOURCES - ${CEF_PATH}/Resources/chrome_100_percent.pak - ${CEF_PATH}/Resources/chrome_200_percent.pak - ${CEF_PATH}/Resources/resources.pak) -else() - message(FATAL_ERROR "No RESOURCES set") -endif() - -install(FILES ${BINARIES} DESTINATION ${GAME_BIN_DIR}) - -if(WIN32 OR APPLE) - install(FILES ${RESOURCES} DESTINATION chromium) - install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION chromium) -else() - install(FILES ${RESOURCES} DESTINATION linux32/chromium) - install(DIRECTORY ${CEF_PATH}/Resources/locales DESTINATION linux32/chromium) -endif() -# Actual lib -set(SOURCES +set(TARGET html_chromium) +set(SOURCES cef_end.h cef_start.h ChromiumBrowser.cpp @@ -67,14 +18,29 @@ set(SOURCES ResourceHandler.cpp ResourceHandler.h) -# Lame -include_directories(${CEF_PATH}) +if(OS_LINUX OR OS_WINDOWS) + ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") +endif() + +add_library(${TARGET} SHARED ${SOURCES}) +SET_LIBRARY_TARGET_PROPERTIES(${TARGET}) +add_dependencies(${TARGET} html libcef_dll_wrapper) -add_library(html_chromium SHARED ${SOURCES}) -target_link_libraries(html_chromium html libcef_imp libcef_dll_wrapper) -SET_TARGET_PROPERTIES(html_chromium PROPERTIES PREFIX "") +if(OS_MAC) + target_link_libraries(${TARGET} html libcef_dll_wrapper ${CEF_STANDARD_LIBS}) +else() + target_link_libraries(${TARGET} html libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS}) +endif() + +if(OS_WINDOWS AND USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) +endif() -if(UNIX) - set_target_properties(html_chromium PROPERTIES OUTPUT_NAME "html_chromium_client") +set_target_properties(${TARGET} PROPERTIES PREFIX "") +if(OS_LINUX) + set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "html_chromium_client") endif() -install(TARGETS html_chromium LIBRARY DESTINATION ${GAME_BIN_DIR}) +if(OS_MAC) + set_target_properties(${TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") +endif() \ No newline at end of file diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index fa04132..8f75ad1 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -1,4 +1,7 @@ -#include "ChromiumBrowser.h" + +#include + +#include "ChromiumBrowser.h" #include "html/IHtmlClient.h" #include "JSObjects.h" @@ -214,7 +217,11 @@ ChromiumBrowser::ChromiumBrowser() , m_PopupTall( 0 ) , m_PopupData( nullptr ) , m_OpenLinksExternally( false ) -{} +{ + RunOrDeferForInit([this] { + m_BrowserHost->WasResized(); + }); +} ChromiumBrowser::~ChromiumBrowser() { diff --git a/html_chromium/ChromiumClient.cpp b/html_chromium/ChromiumClient.cpp index 27f3e47..cdbbae3 100644 --- a/html_chromium/ChromiumClient.cpp +++ b/html_chromium/ChromiumClient.cpp @@ -8,6 +8,7 @@ #include "include/wrapper/cef_closure_task.h" #include "cef_end.h" +#include #include ChromiumClient::ChromiumClient( CefRefPtr browser, IHtmlClientListener* listener ) @@ -81,7 +82,6 @@ void ChromiumClient::Close() CefPostTask( TID_UI, base::BindOnce( &ChromiumBrowser::Close, m_Browser ) ); g_ChromiumSystem.OnClientClose( this ); - delete this; } void ChromiumClient::SetSize( int wide, int tall ) @@ -186,4 +186,3 @@ const unsigned char* ChromiumClient::GetImageData( int& imageWide, int& imageTal ImageData& imageData = m_Browser->GetImageData(); return imageData.GetData( imageWide, imageTall ); } - diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index feeda85..4062f7e 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -1,4 +1,5 @@ -#include "ChromiumSystem.h" + +#include "ChromiumSystem.h" #include "ChromiumClient.h" #include "ChromiumBrowser.h" #include "ResourceHandler.h" @@ -8,15 +9,16 @@ #include "cef_start.h" #include "include/cef_app.h" #include "include/cef_origin_whitelist.h" -#ifdef OSX +#ifdef OS_MAC #include "include/wrapper/cef_library_loader.h" #endif #include "cef_end.h" +#include #include #include -#ifdef _WIN32 +#ifdef OS_WINDOWS #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include namespace fs = std::experimental::filesystem; @@ -34,17 +36,17 @@ class ChromiumApp command_line->AppendSwitch( "enable-gpu" ); command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); -#ifdef _WIN32 +#ifdef OS_WINDOWS command_line->AppendSwitch( "enable-begin-frame-scheduling" ); #endif command_line->AppendSwitch( "enable-system-flash" ); // This can interfere with posix signals and break Breakpad -#ifdef POSIX +#ifdef OS_LINUX command_line->AppendSwitch( "disable-in-process-stack-traces" ); #endif -#ifdef OSX +#ifdef OS_MAC command_line->AppendSwitch( "use-mock-keychain" ); #endif @@ -79,7 +81,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource { g_pHtmlResourceHandler = pResourceHandler; -#ifdef OSX +#ifdef OS_MAC static CefScopedLibraryLoader library_loader; if ( !library_loader.LoadInMain() ) { @@ -87,7 +89,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource } #endif -#ifdef POSIX +#if defined( OS_LINUX ) || defined( OS_MAC ) // GMOD: GO - Chromium will replace Breakpad's signal handlers if we don't do this early int argc = 2; char arg1[] = "binary"; @@ -103,15 +105,17 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.remote_debugging_port = 0; settings.windowless_rendering_enabled = true; -#if defined( _WIN32 ) && !defined( NDEBUG ) - settings.no_sandbox = true; -#else + +#ifdef CEF_USE_SANDBOX settings.no_sandbox = false; +#else + settings.no_sandbox = true; #endif + settings.command_line_args_disabled = true; settings.log_severity = LOGSEVERITY_DEFAULT; -#ifdef _WIN32 +#ifdef OS_WINDOWS // Chromium will be sad if we don't resolve any NTFS junctions for it // Is this really the only way Windows will let me do that? auto hFile = CreateFile( strBaseDir.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); @@ -170,7 +174,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.locales_dir_path ).FromString( chromiumDir + "/locales" ); settings.multi_threaded_message_loop = true; -#elif LINUX +#elif OS_LINUX CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); #if defined(__x86_64__) || defined(_WIN64) @@ -184,7 +188,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.locales_dir_path ).FromString( strBaseDir + "/bin/linux32/chromium/locales" ); settings.multi_threaded_message_loop = true; -#elif OSX +#elif OS_MAC CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); #else #error @@ -193,7 +197,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); // Grab our Sandbox info from the game exe -#if defined( _WIN32 ) && defined( NDEBUG ) +#if defined(OS_WINDOWS) && defined(CEF_USE_SANDBOX) HMODULE pModule; if ( !GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nullptr, &pModule ) ) @@ -224,7 +228,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource return false; } -#if defined( _WIN32 ) && defined( NDEBUG ) +#if defined(OS_WINDOWS) && defined(CEF_USE_SANDBOX) DestroyCefSandboxInfo( sandbox_info ); #endif @@ -252,7 +256,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefAddCrossOriginWhitelistEntry( "asset://html", "asset", "", true ); } -#ifdef OSX +#ifdef OS_MAC CefDoMessageLoopWork(); #endif @@ -268,17 +272,18 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) { CefWindowInfo windowInfo; windowInfo.SetAsWindowless( 0 ); - // TODO: See ChromiumBrowser::OnAcceleratedPaint +#ifdef OS_WINDOWS //windowInfo.shared_texture_enabled = true; +#endif CefBrowserSettings browserSettings; CefString( &browserSettings.default_encoding ).FromString( "UTF-8" ); browserSettings.windowless_frame_rate = 60; browserSettings.javascript_access_clipboard = STATE_DISABLED; browserSettings.javascript_close_windows = STATE_DISABLED; - //browserSettings.webgl = STATE_DISABLED; + browserSettings.webgl = STATE_ENABLED; - CefRefPtr cefClient( new ChromiumBrowser ); + CefRefPtr cefClient( new ChromiumBrowser() ); // Queue the browser creation. It's async, but ChromiumBrowser will handle it all. CefBrowserHost::CreateBrowser( windowInfo, cefClient, "", browserSettings, nullptr, nullptr ); @@ -301,7 +306,7 @@ void ChromiumSystem::Update() m_RequestsLock.Release(); // macOS will want me -#ifdef OSX +#ifdef OS_MAC CefDoMessageLoopWork(); #endif diff --git a/html_chromium/HtmlResourceHandler.cpp b/html_chromium/HtmlResourceHandler.cpp index 0914ec7..69f0e3c 100644 --- a/html_chromium/HtmlResourceHandler.cpp +++ b/html_chromium/HtmlResourceHandler.cpp @@ -61,4 +61,4 @@ bool HtmlResourceHandler::ReadResponse( void* data_out, int bytes_to_read, int& void HtmlResourceHandler::Cancel() { // ... -} \ No newline at end of file +} diff --git a/html_chromium/ImageData.h b/html_chromium/ImageData.h index cab2790..47deef7 100644 --- a/html_chromium/ImageData.h +++ b/html_chromium/ImageData.h @@ -113,4 +113,4 @@ class ImageData base::Lock m_Lock; unsigned char* m_Data; -}; \ No newline at end of file +}; diff --git a/html_chromium/JSObjects.cpp b/html_chromium/JSObjects.cpp index c9525a0..8ed6061 100644 --- a/html_chromium/JSObjects.cpp +++ b/html_chromium/JSObjects.cpp @@ -92,4 +92,4 @@ JSValue::IHashMap* JSHashMap::Copy() const const std::unordered_map& JSHashMap::GetInternalData() const { return _data; -} \ No newline at end of file +} diff --git a/html_chromium/JSObjects.h b/html_chromium/JSObjects.h index 1bc4420..3e997f1 100644 --- a/html_chromium/JSObjects.h +++ b/html_chromium/JSObjects.h @@ -82,4 +82,4 @@ class JSHashMap : public JSValue::IHashMap private: std::unordered_map _data; std::unordered_map::const_iterator _iterator; -}; \ No newline at end of file +}; diff --git a/html_chromium/MessageQueue.h b/html_chromium/MessageQueue.h index 67deba6..1362341 100644 --- a/html_chromium/MessageQueue.h +++ b/html_chromium/MessageQueue.h @@ -69,4 +69,4 @@ class MessageQueue private: base::Lock m_Lock; std::queue m_Messages; -}; \ No newline at end of file +}; diff --git a/html_chromium/cef_end.h b/html_chromium/cef_end.h index b495497..85b735f 100644 --- a/html_chromium/cef_end.h +++ b/html_chromium/cef_end.h @@ -3,4 +3,4 @@ // #undef int64 -#undef uint64 \ No newline at end of file +#undef uint64 diff --git a/html_chromium/cef_start.h b/html_chromium/cef_start.h index 5a0aa3b..fef98c0 100644 --- a/html_chromium/cef_start.h +++ b/html_chromium/cef_start.h @@ -3,4 +3,4 @@ // #define int64 cef_int64 -#define uint64 cef_uint64 \ No newline at end of file +#define uint64 cef_uint64 diff --git a/html_chromium/chromium_process/CMakeLists.txt b/html_chromium/chromium_process/CMakeLists.txt deleted file mode 100644 index 53df377..0000000 --- a/html_chromium/chromium_process/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -set(SOURCES - ChromiumApp.cpp - ChromiumApp.h) - -if(WIN32) - set(SOURCES ${SOURCES} Windows.cpp) -elseif(UNIX AND NOT APPLE) - set(SOURCES ${SOURCES} Linux.cpp) -else() - set(SOURCES ${SOURCES} macOS.cpp) -endif() - -# Lame -include_directories(${CEF_PATH}) - -add_executable(chromium_process ${SOURCES}) -target_link_libraries(chromium_process libcef_imp libcef_dll_wrapper) -install(TARGETS chromium_process RUNTIME DESTINATION ${GAME_BIN_DIR}) diff --git a/html_chromium/chromium_process/Linux.cpp b/html_chromium/chromium_process/Linux.cpp deleted file mode 100644 index 5667783..0000000 --- a/html_chromium/chromium_process/Linux.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "include/cef_app.h" -#include "ChromiumApp.h" - -int main( int argc, char* argv[] ) -{ - CefRefPtr chromiumApp( new ChromiumApp ); - - // Provide CEF with command-line arguments. - CefMainArgs main_args( argc, argv ); - - // CEF applications have multiple sub-processes (render, plugin, GPU, etc) - // that share the same executable. This function checks the command-line and, - // if this is a sub-process, executes the appropriate logic. - return CefExecuteProcess( main_args, chromiumApp, nullptr ); -} diff --git a/html_chromium/chromium_process/Windows.cpp b/html_chromium/chromium_process/Windows.cpp deleted file mode 100644 index 0a44978..0000000 --- a/html_chromium/chromium_process/Windows.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include "include/cef_app.h" -#include "include/cef_sandbox_win.h" - -#include "ChromiumApp.h" - -int ChromiumMain( HINSTANCE hInstance ) -{ - CefRefPtr chromiumApp( new ChromiumApp ); - - // Provide CEF with command-line arguments. - CefMainArgs main_args( hInstance ); - -#if defined( _WIN32 ) && defined( NDEBUG ) - CefScopedSandboxInfo info; - return CefExecuteProcess( main_args, chromiumApp, info.sandbox_info() ); -#else - return CefExecuteProcess( main_args, chromiumApp, nullptr ); -#endif -} diff --git a/html_chromium/chromium_process/chromium_process.vpc b/html_chromium/chromium_process/chromium_process.vpc deleted file mode 100644 index f7c4db2..0000000 --- a/html_chromium/chromium_process/chromium_process.vpc +++ /dev/null @@ -1,45 +0,0 @@ -$Macro SRCDIR "..\..\.." - -// GMOD: GO - We have to append the PLATSUBDIR manually here because EXEs are unique. -$Include "$SRCDIR\vpc_scripts\platform_dirs.vpc" -$Macro OUTBINDIR "$SRCDIR\..\game\bin" [$WIN32] -$Macro OUTBINDIR "$SRCDIR\..\game\bin\$PLATSUBDIR" [!$WIN32 && !$OSX64] - -// We move this later -$Macro OUTBINDIR "$SRCDIR\..\build_scripts\osx64" [$OSX64] - -$Macro OUTBINNAME "chromium_process" - -$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" - -$Include "$SRCDIR\tier0\tier0_exclude.vpc" -$Include "$SRCDIR\tier1\tier1_exclude.vpc" -$Include "$SRCDIR\vstdlib\vstdlib_exclude.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\linux\CEF" [$LINUX32] - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\linux64\CEF" [$LINUX64] - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\osx64\CEF" [$OSX64] - } - -} - -$Project "chromium_process" -{ - $Folder "Source Files" - { - $File "ChromiumApp.cpp" - $File "Linux.cpp" [$LINUX] - $File "macOS.cpp" [$OSX64] - } - - $Folder "Link Libraries" - { - $ImpLibExternal "$LIBCOMMON\cef" [$LINUX] - $LibExternal "$LIBCOMMON\libcef_dll_wrapper" [$LINUX || $OSX64] - $LibExternal "$LIBCOMMON\cef_sandbox" [$OSX64] - } -} diff --git a/html_chromium/chromium_process/macOS.cpp b/html_chromium/chromium_process/macOS.cpp deleted file mode 100644 index 44ef408..0000000 --- a/html_chromium/chromium_process/macOS.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "include/cef_app.h" -#include "include/wrapper/cef_library_loader.h" -#include "include/cef_sandbox_mac.h" -#include "ChromiumApp.h" - -// Entry point function for sub-processes. -int main( int argc, char* argv[] ) { - // HACK - - // Initialize the macOS sandbox for this helper process. - CefScopedSandboxContext sandbox_context; - if ( !sandbox_context.Initialize( argc, argv ) ) - return 1; - - // Load the CEF framework library at runtime instead of linking directly - // as required by the macOS sandbox implementation. - CefScopedLibraryLoader library_loader; - if ( !library_loader.LoadInHelper() ) - return 1; - - CefRefPtr chromiumApp( new ChromiumApp ); - - // Provide CEF with command-line arguments. - CefMainArgs main_args( argc, argv ); - - // Execute the sub-process. - return CefExecuteProcess( main_args, chromiumApp, nullptr ); -} diff --git a/html_chromium/html_chromium.vpc b/html_chromium/html_chromium.vpc deleted file mode 100644 index f1e2cb5..0000000 --- a/html_chromium/html_chromium.vpc +++ /dev/null @@ -1,74 +0,0 @@ -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -// $Include "$SRCDIR\tier0\tier0_exclude.vpc" -// $Include "$SRCDIR\tier1\tier1_exclude.vpc" -// $Include "$SRCDIR\vstdlib\vstdlib_exclude.vpc" - -$Configuration -{ - $General - { - // Win32: Don't target XP for Chromium - $PlatformToolset "v142" [$CHROMIUM && $WIN32] - } - - $Compiler - { - $AdditionalIncludeDirectories "$BASE;$SRCDIR\garrysmod" - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\win32\CEF" [$WIN32] - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\win64\CEF" [$WIN64] - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\linux64\CEF" [$LINUX64] - $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\osx64\CEF" [$OSX64] - - // Win32: Don't target XP for Chromium - $PreprocessorDefinitions "$BASE;WINVER=0x0601;_WIN32_WINNT=0x0601;NTDDI_VERSION=0x06010000" [$WIN32] - } - -} - -$Project "html_chromium" -{ - $Folder "Interfaces" - { - $File "$SRCDIR\garrysmod\html\IHtmlSystem.h" - $File "$SRCDIR\garrysmod\html\IHtmlClient.h" - $File "$SRCDIR\garrysmod\html\IHtmlClientListener.h" - $File "$SRCDIR\garrysmod\html\IHtmlResourceHandler.h" - $File "$SRCDIR\garrysmod\html\JSValue.h" - } - - $Folder "Source Files" - { - $File "ChromiumSystem.cpp" - $File "ChromiumClient.cpp" - $File "ChromiumBrowser.cpp" - $File "ResourceHandler.cpp" - $File "HtmlResourceHandler.cpp" - $File "JSObjects.cpp" - } - - $Folder "Header Files" - { - $File "cef_start.h" - $File "cef_end.h" - $File "ChromiumSystem.h" - $File "ChromiumClient.h" - $File "ChromiumBrowser.h" - $File "ResourceHandler.h" - $File "HtmlResourceHandler.h" - $File "MessageQueue.h" - $File "ImageData.h" - $File "JSObjects.h" - } - - $Folder "Link Libraries" - { - $LibExternal "$LIBCOMMON\libcef_dll_wrapper" - - // macOS doesn't link against libcef at compile-time - $ImpLibExternal "$LIBCOMMON\libcef" [$WINDOWS] - $ImpLibExternal "$LIBCOMMON\cef" [$LINUX] - } -} diff --git a/html_stub/CMakeLists.txt b/html_stub/CMakeLists.txt index 6be8245..5c53662 100644 --- a/html_stub/CMakeLists.txt +++ b/html_stub/CMakeLists.txt @@ -1,3 +1,4 @@ + set(SOURCES StubClient.cpp StubClient.h @@ -5,10 +6,13 @@ set(SOURCES StubSystem.h) add_library(html_stub SHARED ${SOURCES}) +add_dependencies(html_stub html) target_link_libraries(html_stub html) -SET_TARGET_PROPERTIES(html_stub PROPERTIES PREFIX "") -if(UNIX) +set_target_properties(html_stub PROPERTIES PREFIX "") +if(OS_LINUX) set_target_properties(html_stub PROPERTIES OUTPUT_NAME "html_stub_client") endif() -install(TARGETS html_stub LIBRARY DESTINATION ${GAME_BIN_DIR}) +if(OS_MAC) + set_target_properties(html_stub PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") +endif() diff --git a/html_stub/html_stub.vpc b/html_stub/html_stub.vpc deleted file mode 100644 index 6df12f6..0000000 --- a/html_stub/html_stub.vpc +++ /dev/null @@ -1,35 +0,0 @@ -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Include "$SRCDIR\tier0\tier0_exclude.vpc" -$Include "$SRCDIR\tier1\tier1_exclude.vpc" -$Include "$SRCDIR\vstdlib\vstdlib_exclude.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE;$SRCDIR\garrysmod" - } -} - -$Project "html_stub" -{ - $Folder "Interfaces" - { - $File "$SRCDIR\garrysmod\html\IHtmlSystem.h" - $File "$SRCDIR\garrysmod\html\IHtmlClient.h" - $File "$SRCDIR\garrysmod\html\IHtmlClientListener.h" - $File "$SRCDIR\garrysmod\html\IHtmlResourceHandler.h" - $File "$SRCDIR\garrysmod\html\JSValue.h" - } - - $Folder "Source Files" - { - $File "StubSystem.cpp" - $File "StubSystem.h" - $File "StubClient.cpp" - $File "StubClient.h" - } -} diff --git a/thirdparty/imgui-1.74/CMakeLists.txt b/thirdparty/imgui-1.74/CMakeLists.txt index a3269f4..0cff301 100644 --- a/thirdparty/imgui-1.74/CMakeLists.txt +++ b/thirdparty/imgui-1.74/CMakeLists.txt @@ -1,3 +1,6 @@ + +project(imgui) + set(SOURCES imconfig.h imgui.cpp @@ -13,6 +16,8 @@ set(SOURCES imstb_textedit.h imstb_truetype.h) + add_library(imgui STATIC ${SOURCES}) -target_link_libraries(imgui glfw glad) +target_compile_definitions(imgui PRIVATE IMGUI_IMPL_OPENGL_LOADER_GLAD) +target_link_libraries(imgui glfw glad ${glfw_LIBRARIES}) target_include_directories(imgui PUBLIC ./) From c2f71651612557250ccf098022147a858060f366 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 12:27:29 -0500 Subject: [PATCH 35/87] Reenable HTMLImports (not sure we need this as it has been removed) --- chromium_process/ChromiumApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 98a54f3..a3467b2 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -192,7 +192,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); // Chromium 80 removed this but only sometimes. - // command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); + command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); // Disable site isolation until we implement passing registered Lua functions between processes command_line->AppendSwitch( "disable-site-isolation-trials" ); From b8b1643ef7ba39f23fc8cfa0a17ce5edcf067e15 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 12:29:53 -0500 Subject: [PATCH 36/87] Bumped CEF version to 98.2.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c79860a..8b59760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "98.1.21+g9782362+chromium-98.0.4758.102") +set(CEF_VERSION "98.2.0+g78c653a+chromium-98.0.4758.102") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") From 89b1061b50f21841901097014f65adb2ad8f0978 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 13:14:15 -0500 Subject: [PATCH 37/87] Changed user-agent so it's generated using cef_version.h --- html_chromium/ChromiumSystem.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 4062f7e..e32373b 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -12,6 +12,7 @@ #ifdef OS_MAC #include "include/wrapper/cef_library_loader.h" #endif +#include "include/cef_version.h" #include "cef_end.h" #include @@ -116,6 +117,8 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.log_severity = LOGSEVERITY_DEFAULT; #ifdef OS_WINDOWS + std::string platform = "Windows NT"; + // Chromium will be sad if we don't resolve any NTFS junctions for it // Is this really the only way Windows will let me do that? auto hFile = CreateFile( strBaseDir.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); @@ -167,15 +170,13 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource chromiumDir = targetPath.string(); } - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Windows NT; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); - // GMOD: GO - We use the same resources with 32-bit and 64-bit builds, so always use the 32-bit bin path for them CefString( &settings.resources_dir_path ).FromString( chromiumDir ); CefString( &settings.locales_dir_path ).FromString( chromiumDir + "/locales" ); settings.multi_threaded_message_loop = true; #elif OS_LINUX - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Linux; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); + std::string platform = "Linux"; #if defined(__x86_64__) || defined(_WIN64) CefString( &settings.browser_subprocess_path ).FromString( strBaseDir + "/bin/linux64/chromium_process" ); @@ -189,11 +190,14 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif OS_MAC - CefString( &settings.user_agent ).FromString( "Mozilla/5.0 (Macintosh; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 GMod/13" ); + std::string platform = "Machintosh"; #else #error #endif + std::string chrome_version = std::to_string(CHROME_VERSION_MAJOR) + "." + std::to_string(CHROME_VERSION_MINOR) + "." + std::to_string(CHROME_VERSION_BUILD) + "." + std::to_string(CHROME_VERSION_PATCH); + CefString(&settings.user_agent).FromString("Mozilla/5.0 (" + platform + "; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chrome_version + " Safari/537.36 GMod/13"); + CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); // Grab our Sandbox info from the game exe From c71fe4f46f39c1547091b38259fb8d43c61cfd94 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 22:03:08 -0500 Subject: [PATCH 38/87] Merged gmod_launcher into chromium_process They roughly served the same function. --- .gitignore | 3 + CMakeLists.txt | 19 ++-- chromium_process/CMakeLists.txt | 70 +++++++++++--- chromium_process/Windows.cpp | 73 ++++++++++++--- .../resources/win}/gmod.ico | Bin .../resources/win}/gmod_icon.rc | 0 gmod_launcher/CMakeLists.txt | 39 -------- gmod_launcher/Main.cpp | 88 ------------------ html_chromium/CMakeLists.txt | 13 ++- html_stub/CMakeLists.txt | 13 ++- 10 files changed, 151 insertions(+), 167 deletions(-) rename {gmod_launcher => chromium_process/resources/win}/gmod.ico (100%) rename {gmod_launcher => chromium_process/resources/win}/gmod_icon.rc (100%) delete mode 100644 gmod_launcher/CMakeLists.txt delete mode 100644 gmod_launcher/Main.cpp diff --git a/.gitignore b/.gitignore index 40c04d9..c6cc349 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ /build_x64 /build_x86 +# Distribution directories +/dist + # Dependencies that are downloaded/installed separately /thirdparty/cef3/** !/thirdparty/cef3/README.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b59760..3ebd0f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,11 @@ set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) add_subdirectory(thirdparty/glfw-3.3.2 EXCLUDE_FROM_ALL) if(GEN_NINJA OR GEN_MAKEFILES) - set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/${CMAKE_BUILD_TYPE}") + set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/${CMAKE_BUILD_TYPE}/${CEF_PLATFORM}") + set(INSTALL_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dist/${CEF_PLATFORM}-${CMAKE_BUILD_TYPE}") else() - set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/$") + set(CEF_TARGET_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/out/$/${CEF_PLATFORM}") + set(INSTALL_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dist/${CEF_PLATFORM}-$") endif() set(CEF_TARGET_OUT_DIR ${CEF_TARGET_OUT_DIR}/bin/${CEF_PLATFORM}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CEF_TARGET_OUT_DIR}) @@ -62,16 +64,9 @@ add_subdirectory(${CEF_ROOT}/tests/ceftests EXCLUDE_FROM_ALL) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(html) -# NOTE: This can probably be removed. It would only need rebuilding if the html interface changes (and this is unlikely). -add_subdirectory(html_stub) +add_subdirectory(html_stub EXCLUDE_FROM_ALL) add_subdirectory(html_chromium) - -if(OS_LINUX OR OS_MAC) - add_subdirectory(chromium_process) -elseif(OS_WIN) - add_subdirectory(gmod_launcher) -endif() - +add_subdirectory(chromium_process) add_subdirectory(example_host EXCLUDE_FROM_ALL) -PRINT_CEF_CONFIG() \ No newline at end of file +PRINT_CEF_CONFIG() diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt index b2aeae8..b6a9bbb 100644 --- a/chromium_process/CMakeLists.txt +++ b/chromium_process/CMakeLists.txt @@ -9,32 +9,75 @@ set(SOURCES_LINUX Linux.cpp) set(SOURCES_MAC macOS.cpp) set(SOURCES_WINDOWS Windows.cpp) +set(RESOURCES_WINDOWS resources/win/gmod_icon.rc) + APPEND_PLATFORM_SOURCES(SOURCES) +APPEND_PLATFORM_SOURCES(RESOURCES) if(OS_LINUX OR OS_WINDOWS) ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") - add_executable(${TARGET} ${SOURCES}) + if(OS_LINUX) + add_executable(${TARGET} ${SOURCES}) + else() + add_executable(${TARGET} WIN32 ${SOURCES} ${RESOURCES}) + endif() SET_EXECUTABLE_TARGET_PROPERTIES(${TARGET}) add_dependencies(${TARGET} libcef_dll_wrapper) target_link_libraries(${TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS}) if(OS_LINUX) - set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") - set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) - elseif(USE_SANDBOX) - ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") - target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") + set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + else() + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() + + set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod_launcher") + target_link_libraries(${TARGET} + shlwapi + winmm + wsock32 + WS2_32 + comctl32 + rpcrt4 + version + DbgHelp + Psapi + wbemuuid + OleAut32 + SetupAPI + Propsys + Cfgmgr32 + PowrProf + Delayimp.lib) + + if(USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) + endif() endif() + # FIXME: These should be moved to their own target LIST(REMOVE_ITEM CEF_RESOURCE_FILES "icudtl.dat") - COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}") - COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}") if(OS_LINUX) - COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}/../linux32/chromium") + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/linux32/chromium") else() - COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}/chromium") + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win32") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/chromium") endif() else() set(HELPER_TARGET "gmod_Helper") @@ -50,7 +93,7 @@ else() POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework" - "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks/Chromium Embedded Framework.framework" + "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks/Chromium Embedded Framework.framework" VERBATIM) foreach(_suffix_list ${CEF_HELPER_APP_SUFFIXES}) @@ -75,8 +118,9 @@ else() target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS}) set_target_properties(${_helper_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist} - OUTPUT_NAME ${_helper_output_name} - RUNTIME_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks") + OUTPUT_NAME ${_helper_output_name}) + + install(TARGETS ${_helper_target} DESTINATION ${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks) if(USE_SANDBOX) target_link_libraries(${_helper_target} cef_sandbox_lib) diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp index a88a686..793da8b 100644 --- a/chromium_process/Windows.cpp +++ b/chromium_process/Windows.cpp @@ -1,25 +1,76 @@ + +#include +#include +#include + #include +#include +#include + +#if __x86_64__ || _WIN64 + #define ENVIRONMENT64 +#else + #define ENVIRONMENT32 +#endif + #include "include/cef_app.h" +#include "ChromiumApp.h" #ifdef CEF_USE_SANDBOX #include "include/cef_sandbox_win.h" -#endif -#include "ChromiumApp.h" + extern "C" + { + __declspec( dllexport ) void* CreateCefSandboxInfo() + { + return cef_sandbox_info_create(); + } -int ChromiumMain(HINSTANCE hInstance) -{ - CefEnableHighDPISupport(); + __declspec( dllexport ) void DestroyCefSandboxInfo( void* info ) + { + cef_sandbox_info_destroy( info ); + } + } +#endif + +using FuncLauncherMain = int (*)(HINSTANCE, HINSTANCE, LPSTR, int); - void* sandbox_info = nullptr; +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + // Check if "--type=" is in the command arguments. If it is, we are a chromium subprocess. + if (strstr(lpCmdLine, "--type=")) { + CefEnableHighDPISupport(); + void* sandbox_info = nullptr; #ifdef CEF_USE_SANDBOX - CefScopedSandboxInfo scoped_sandbox; - sandbox_info = scoped_sandbox.sandbox_info(); + CefScopedSandboxInfo scoped_sandbox; + sandbox_info = scoped_sandbox.sandbox_info(); +#endif + + CefMainArgs main_args(hInstance); + CefRefPtr app(new ChromiumApp()); + + int exit_code = CefExecuteProcess(main_args, app, sandbox_info); + if (exit_code >= 0) { + return exit_code; + } + } + + char executable_path[MAX_PATH] = { 0 }; + GetModuleFileNameA(NULL, executable_path, MAX_PATH); + + std::string::size_type last_slash = std::string(executable_path).find_last_of("\\/"); + std::string executable_dir = std::string(executable_path).substr(0, last_slash); + +#ifdef ENVIORNMENT64 + executable_dir += "\\..\\.."; +#else + executable_dir += "\\.."; #endif - CefMainArgs main_args(hInstance); - CefRefPtr app(new ChromiumApp()); + _chdir(executable_dir.c_str()); - return CefExecuteProcess(main_args, app, sandbox_info); + // Launch GarrysMod's main function from this process. We needed this so the "main" process could provide sandbox information above. + HMODULE hLauncher = LoadLibraryA("launcher.dll"); + void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); + return (static_cast(mainFn))(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } diff --git a/gmod_launcher/gmod.ico b/chromium_process/resources/win/gmod.ico similarity index 100% rename from gmod_launcher/gmod.ico rename to chromium_process/resources/win/gmod.ico diff --git a/gmod_launcher/gmod_icon.rc b/chromium_process/resources/win/gmod_icon.rc similarity index 100% rename from gmod_launcher/gmod_icon.rc rename to chromium_process/resources/win/gmod_icon.rc diff --git a/gmod_launcher/CMakeLists.txt b/gmod_launcher/CMakeLists.txt deleted file mode 100644 index 33827a2..0000000 --- a/gmod_launcher/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -set(SOURCES - Main.cpp) - -if(WIN32) - set(SOURCES - ${SOURCES} - ../html_chromium/chromium_process/ChromiumApp.cpp - ../html_chromium/chromium_process/ChromiumApp.h - ../html_chromium/chromium_process/Windows.cpp) -endif() - -# Lame -include_directories(${CEF_PATH}) - -set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/gmod_icon.rc") - -add_executable(gmod_launcher WIN32 ${SOURCES} ${APP_ICON_RESOURCE_WINDOWS}) -target_link_libraries(gmod_launcher html libcef_imp libcef_dll_wrapper) -target_link_libraries(gmod_launcher optimized cef_sandbox) - -if(WIN32) - target_link_libraries(gmod_launcher - shlwapi - winmm - wsock32 - WS2_32 - comctl32 - rpcrt4 - version - DbgHelp - Psapi - wbemuuid - OleAut32 - SetupAPI - Propsys - Cfgmgr32 - PowrProf - Delayimp.lib) -endif() diff --git a/gmod_launcher/Main.cpp b/gmod_launcher/Main.cpp deleted file mode 100644 index 6aad617..0000000 --- a/gmod_launcher/Main.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include - -#include -#include -#include - -#ifdef _WIN32 - #include - #define cd _chdir -#else - #include "unistd.h" - #define cd chdir -#endif - -// Is this exe 32-bit or 64-bit? -#if _WIN32 || _WIN64 - #if _WIN64 - #define ENVIRONMENT64 - #else - #define ENVIRONMENT32 - #endif -#endif -#if __GNUC__ - #if __x86_64__ || __ppc64__ - #define ENVIRONMENT64 - #else - #define ENVIRONMENT32 - #endif -#endif - -#if defined( _WIN32 ) - #include "include/cef_sandbox_win.h" - - extern "C" - { - __declspec( dllexport ) void* CreateCefSandboxInfo() - { - return cef_sandbox_info_create(); - } - - __declspec( dllexport ) void DestroyCefSandboxInfo( void* info ) - { - cef_sandbox_info_destroy( info ); - } - } -#endif - -using FuncLauncherMain = int (*)(HINSTANCE, HINSTANCE, LPSTR, int); - -int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) -{ - // Sub-process -#ifdef _WIN32 - if ( strstr( lpCmdLine, "--type=" ) ) - { - int ChromiumMain( HINSTANCE hInstance ); - - int exit_code = ChromiumMain( hInstance ); - - if ( exit_code >= 0 ) - { - return exit_code; - } - } -#endif - - // Make sure the CWD is always GarrysMod root (where hl2.exe is) - char exePath[MAX_PATH]; - GetModuleFileNameA(NULL, exePath, MAX_PATH); - - std::string::size_type lastSlashPos = std::string(exePath).find_last_of("\\/"); - std::string exeDir = std::string(exePath).substr(0, lastSlashPos); - -#ifdef ENVIRONMENT64 - exeDir += "\\..\\.."; -#else - exeDir += "\\.."; -#endif - - cd(exeDir.c_str()); - - HMODULE hLauncher = LoadLibraryA("launcher.dll"); - void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); - (static_cast(mainFn))(hInstance, hPrevInstance, lpCmdLine, nCmdShow); - - return 0; -} diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index 8f4ff84..eb3af0e 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -41,6 +41,15 @@ set_target_properties(${TARGET} PROPERTIES PREFIX "") if(OS_LINUX) set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "html_chromium_client") endif() -if(OS_MAC) - set_target_properties(${TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") + +if(OS_WINDOWS) + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() +elseif(OS_LINUX) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") +elseif(OS_MAC) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") endif() \ No newline at end of file diff --git a/html_stub/CMakeLists.txt b/html_stub/CMakeLists.txt index 5c53662..0ac031e 100644 --- a/html_stub/CMakeLists.txt +++ b/html_stub/CMakeLists.txt @@ -13,6 +13,15 @@ set_target_properties(html_stub PROPERTIES PREFIX "") if(OS_LINUX) set_target_properties(html_stub PROPERTIES OUTPUT_NAME "html_stub_client") endif() -if(OS_MAC) - set_target_properties(html_stub PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CEF_TARGET_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") + +if(OS_WINDOWS) + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + install(TARGETS html_stub DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + install(TARGETS html_stub DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() +elseif(OS_LINUX) + install(TARGETS html_stub DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") +elseif(OS_MAC) + install(TARGETS html_stub DESTINATION "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") endif() From 99652c018c7eaa67abd1fe62689b202e130c9cc1 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 19 Feb 2022 22:10:14 -0500 Subject: [PATCH 39/87] Added a possibly needed set in CMake config --- CMakeLists.txt | 2 ++ html_chromium/ChromiumSystem.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ebd0f1..170e2dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,8 @@ add_subdirectory(${CEF_ROOT}/tests/ceftests EXCLUDE_FROM_ALL) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + add_subdirectory(html) add_subdirectory(html_stub EXCLUDE_FROM_ALL) add_subdirectory(html_chromium) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index e32373b..53430d4 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -200,7 +200,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); - // Grab our Sandbox info from the game exe + // Grab our Sandbox info from the "game" exe #if defined(OS_WINDOWS) && defined(CEF_USE_SANDBOX) HMODULE pModule; From d4a53673ec6630d3d5387a52611a1d7213f4d12c Mon Sep 17 00:00:00 2001 From: John Peel Date: Sun, 20 Feb 2022 11:42:55 -0500 Subject: [PATCH 40/87] Reverted define changes, CEF isn't consistant between OSes --- html_chromium/ChromiumSystem.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 53430d4..5ae161d 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -9,7 +9,7 @@ #include "cef_start.h" #include "include/cef_app.h" #include "include/cef_origin_whitelist.h" -#ifdef OS_MAC +#ifdef __APPLE__ #include "include/wrapper/cef_library_loader.h" #endif #include "include/cef_version.h" @@ -19,7 +19,7 @@ #include #include -#ifdef OS_WINDOWS +#ifdef _WIN32 #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include namespace fs = std::experimental::filesystem; @@ -37,17 +37,17 @@ class ChromiumApp command_line->AppendSwitch( "enable-gpu" ); command_line->AppendSwitch( "disable-gpu-compositing" ); // NOTE: Enabling GPU Compositing will make OnAcceleratedPaint run instead of OnPaint (CEF must be patched or NOTHING will run!) command_line->AppendSwitch( "disable-smooth-scrolling" ); -#ifdef OS_WINDOWS +#ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); #endif command_line->AppendSwitch( "enable-system-flash" ); // This can interfere with posix signals and break Breakpad -#ifdef OS_LINUX +#ifdef __linux__ command_line->AppendSwitch( "disable-in-process-stack-traces" ); #endif -#ifdef OS_MAC +#ifdef __APPLE__ command_line->AppendSwitch( "use-mock-keychain" ); #endif @@ -82,7 +82,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource { g_pHtmlResourceHandler = pResourceHandler; -#ifdef OS_MAC +#ifdef __APPLE__ static CefScopedLibraryLoader library_loader; if ( !library_loader.LoadInMain() ) { @@ -90,7 +90,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource } #endif -#if defined( OS_LINUX ) || defined( OS_MAC ) +#if defined( __linux__ ) || defined( __APPLE__ ) // GMOD: GO - Chromium will replace Breakpad's signal handlers if we don't do this early int argc = 2; char arg1[] = "binary"; @@ -116,7 +116,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.command_line_args_disabled = true; settings.log_severity = LOGSEVERITY_DEFAULT; -#ifdef OS_WINDOWS +#ifdef _WIN32 std::string platform = "Windows NT"; // Chromium will be sad if we don't resolve any NTFS junctions for it @@ -175,7 +175,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.locales_dir_path ).FromString( chromiumDir + "/locales" ); settings.multi_threaded_message_loop = true; -#elif OS_LINUX +#elif __linux__ std::string platform = "Linux"; #if defined(__x86_64__) || defined(_WIN64) @@ -189,7 +189,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.locales_dir_path ).FromString( strBaseDir + "/bin/linux32/chromium/locales" ); settings.multi_threaded_message_loop = true; -#elif OS_MAC +#elif __APPLE__ std::string platform = "Machintosh"; #else #error @@ -201,7 +201,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); // Grab our Sandbox info from the "game" exe -#if defined(OS_WINDOWS) && defined(CEF_USE_SANDBOX) +#if defined(_WIN32) && defined(CEF_USE_SANDBOX) HMODULE pModule; if ( !GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nullptr, &pModule ) ) @@ -232,7 +232,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource return false; } -#if defined(OS_WINDOWS) && defined(CEF_USE_SANDBOX) +#if defined(_WIN32) && defined(CEF_USE_SANDBOX) DestroyCefSandboxInfo( sandbox_info ); #endif @@ -260,7 +260,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefAddCrossOriginWhitelistEntry( "asset://html", "asset", "", true ); } -#ifdef OS_MAC +#ifdef __APPLE__ CefDoMessageLoopWork(); #endif @@ -276,7 +276,7 @@ IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) { CefWindowInfo windowInfo; windowInfo.SetAsWindowless( 0 ); -#ifdef OS_WINDOWS +#ifdef _WIN32 //windowInfo.shared_texture_enabled = true; #endif @@ -310,7 +310,7 @@ void ChromiumSystem::Update() m_RequestsLock.Release(); // macOS will want me -#ifdef OS_MAC +#ifdef __APPLE__ CefDoMessageLoopWork(); #endif From c1f34b377293f0e7fae0272ded64451b3f883df8 Mon Sep 17 00:00:00 2001 From: John Peel Date: Sun, 20 Feb 2022 12:48:23 -0500 Subject: [PATCH 41/87] Fixed misspelling of macintosh --- html_chromium/ChromiumSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 5ae161d..8106dfc 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -190,7 +190,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif __APPLE__ - std::string platform = "Machintosh"; + std::string platform = "Macintosh"; #else #error #endif From 6ca74298570c1d2967d44406be4ca78b3faaf7fd Mon Sep 17 00:00:00 2001 From: John Peel Date: Sun, 20 Feb 2022 12:53:32 -0500 Subject: [PATCH 42/87] Fixed more defines and implemented opening urls externally on MacOS --- chromium_process/ChromiumApp.cpp | 4 +-- html_chromium/ChromiumBrowser.cpp | 53 +++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index a3467b2..2122da8 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -177,11 +177,11 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitch( "enable-system-flash" ); // This can interfere with posix signals and break Breakpad -#ifdef POSIX +#if defined(__linux__) || defined(__APPLE__) command_line->AppendSwitch( "disable-in-process-stack-traces" ); #endif -#ifdef OSX +#ifdef __APPLE__ command_line->AppendSwitch( "use-mock-keychain" ); #endif diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 8f75ad1..7c9a07b 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -10,6 +10,18 @@ #include "include/cef_parser.h" #include "cef_end.h" +#ifdef _WIN32 + #include +#endif +#ifdef __linux__ + #include + #include +#endif +#ifdef __APPLE__ + #include + #include +#endif + static bool CefValueToJSValue( JSValue& outValue, CefRefPtr inValue, int depth = 0 ) { if ( depth > 16 ) @@ -207,6 +219,9 @@ static int GetModifiers( const IHtmlClient::EventModifiers modifiers ) if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::RightMouse ) ) chromiumModifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON; + if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::OSX_Cmd ) ) + chromiumModifiers |= EVENTFLAG_COMMAND_DOWN; + return chromiumModifiers; } @@ -286,7 +301,7 @@ void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) chromiumKeyEvent.type = KEYEVENT_CHAR; chromiumKeyEvent.character = static_cast( keyEvent.key_char ); chromiumKeyEvent.unmodified_character = static_cast( keyEvent.key_char ); -#ifdef OSX +#ifdef __APPLE__ chromiumKeyEvent.windows_key_code = 0; chromiumKeyEvent.native_key_code = keyEvent.native_key_code; #else @@ -828,23 +843,29 @@ bool ChromiumBrowser::OnBeforeBrowse( CefRefPtr, #ifndef __APPLE__ if ( m_OpenLinksExternally ) { -#if defined( _WIN32 ) - ShellExecute( NULL, "open", request->GetURL().ToString().c_str(), NULL, NULL, SW_SHOWNORMAL ); -#elif defined( __linux__ ) - std::string strUrl = request->GetURL().ToString(); - pid_t pid; - const char* args[3]; - args[0] = "/usr/bin/xdg-open"; - args[1] = strUrl.c_str(); - args[2] = NULL; - pid = fork(); - - if ( pid == 0 ) - { - execvp( args[0], (char* const*) args ); + std::string url = request->GetURL().ToString(); + +#if defined(_WIN32) + ShellExecute(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); +#elif defined(__linux__) + pid_t pid = fork(); + if (pid == 0) { + execlp("xdg-open", "xdg-open", url.c_str(), NULL); + exit(0); + } else if (pid < 0) { + LOG(ERROR) << "Failed to open URL in external browser: " << url; + } else { + // TODO: Should we wait for the child process to finish? + waitpid(pid, NULL, 0); + } +#elif defined(__APPLE__) + CFURLRef urlRef = CFURLCreateWithBytes(NULL, (const UInt8 *)url.c_str(), url.length(), kCFStringEncodingUTF8, NULL); + if (urlRef) { + LSOpenCFURLRef(urlRef, NULL); + CFRelease(urlRef); } #else -#error +#error Unsupported platform #endif return true; } From 92ffa2e0c9dc5f7a91e4a8f9a016cb10f77d7f3d Mon Sep 17 00:00:00 2001 From: John Peel Date: Sat, 5 Mar 2022 11:45:04 -0500 Subject: [PATCH 43/87] Fixed Windows builds by using ANSI versions of functions. --- chromium_process/ChromiumApp.cpp | 5 +++-- html_chromium/ChromiumBrowser.cpp | 2 +- html_chromium/ChromiumSystem.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 2122da8..d84d35d 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -106,9 +106,10 @@ static bool CefValueToV8Value( CefRefPtr& outValue, const CefRefPtr< case VTYPE_LIST: { auto inList = inValue->GetList(); - outValue = CefV8Value::CreateArray( inList->GetSize() ); + int size = static_cast(inList->GetSize()); + outValue = CefV8Value::CreateArray(size); - for ( size_t i = 0; i < inList->GetSize(); i++ ) + for ( int i = 0; i < size; i++ ) { CefRefPtr entry; diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 7c9a07b..4e74906 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -846,7 +846,7 @@ bool ChromiumBrowser::OnBeforeBrowse( CefRefPtr, std::string url = request->GetURL().ToString(); #if defined(_WIN32) - ShellExecute(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); + ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); #elif defined(__linux__) pid_t pid = fork(); if (pid == 0) { diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 8106dfc..3943ad6 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -121,13 +121,13 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource // Chromium will be sad if we don't resolve any NTFS junctions for it // Is this really the only way Windows will let me do that? - auto hFile = CreateFile( strBaseDir.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); + auto hFile = CreateFileA( strBaseDir.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); bool useTempDir = false; if ( hFile != INVALID_HANDLE_VALUE ) { char pathBuf[MAX_PATH] = { 0 }; - if ( GetFinalPathNameByHandle( hFile, pathBuf, sizeof( pathBuf ), VOLUME_NAME_DOS ) ) + if ( GetFinalPathNameByHandleA( hFile, pathBuf, sizeof( pathBuf ), VOLUME_NAME_DOS ) ) { // If it's a network drive, we can't use it if ( strstr( pathBuf, "\\\\?\\UNC" ) == pathBuf ) From 817638f6db20a0723b3d47b735d96c47689037f1 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sun, 6 Mar 2022 22:03:50 -0500 Subject: [PATCH 44/87] Fix typo for `ENVIRONMENT64` --- chromium_process/Windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp index 793da8b..7dfe13c 100644 --- a/chromium_process/Windows.cpp +++ b/chromium_process/Windows.cpp @@ -61,7 +61,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ std::string::size_type last_slash = std::string(executable_path).find_last_of("\\/"); std::string executable_dir = std::string(executable_path).substr(0, last_slash); -#ifdef ENVIORNMENT64 +#ifdef ENVIRONMENT64 executable_dir += "\\..\\.."; #else executable_dir += "\\.."; From 33be81beb8beba249f1f93e70e315aa775032a14 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Mon, 7 Mar 2022 20:55:29 -0500 Subject: [PATCH 45/87] Bump CEF version to 98.2.1+g29d6e22+chromium-98.0.4758.109 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 170e2dc..d0a173a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "98.2.0+g78c653a+chromium-98.0.4758.102") +set(CEF_VERSION "98.2.1+g29d6e22+chromium-98.0.4758.109") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") @@ -16,7 +16,7 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") else() set(CEF_PLATFORM "macosx64") endif() -elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") if(CMAKE_SIZEOF_VOID_P MATCHES 8) set(CEF_PLATFORM "linux64") else() From ec324086ef28e93042469d0b5555ce9e646aec3d Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Mon, 7 Mar 2022 23:15:31 -0500 Subject: [PATCH 46/87] Update README CEF version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7baa101..cbc80bf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **98.1.19+g57be9e2+chromium-98.0.4758.80** +- **98.2.1+g29d6e22+chromium-98.0.4758.109** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From 7b0e4c1e742bc3e6160025ba87d981c791ad5d80 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 8 Mar 2022 15:19:54 -0500 Subject: [PATCH 47/87] Updated macOS platform strings for valid user agent config. --- html_chromium/ChromiumSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 3943ad6..7ab1919 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -190,7 +190,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource settings.multi_threaded_message_loop = true; #elif __APPLE__ - std::string platform = "Macintosh"; + std::string platform = "Macintosh; Intel Mac OS X"; #else #error #endif From 826e65e6c9554154a108689e9de20f5d9fdd40d9 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 8 Mar 2022 15:44:34 -0500 Subject: [PATCH 48/87] Added preliminary macOS build instructions (using 'make' for now). --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cbc80bf..b76ba27 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,18 @@ make && make install This will place a complete build into the `build/out` folder by default. ### macOS -TODO: Still working on this. +#### Requirements +- A version of GCC/G++ or Clang/Clang++ with C++11 support +- CMake 3.19 or newer + +#### Compiling +```mkdir build +cd build +cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. +make && make install +``` + +This will place a complete build into the `dist` folder by default. ## TODO - Improve the CMake files. We don't use them for GMod builds so they're a bit wonky. From e49bf1c9f149b91edf53e819915b8d6f4f517835 Mon Sep 17 00:00:00 2001 From: Sophie-bear Date: Tue, 8 Mar 2022 20:46:43 +0000 Subject: [PATCH 49/87] README.md edited online with Bitbucket --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b76ba27..565478e 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ This will place a complete build into the `build/out` folder by default. - CMake 3.19 or newer #### Compiling -```mkdir build +```sh +mkdir build cd build cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. make && make install From 1029d444c72d244471b59873dc0ca76e44b604c5 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 8 Mar 2022 16:02:31 -0500 Subject: [PATCH 50/87] Updated macOS build instructions to use Ninja for building. Corrected build directory from full distribution release to just built files output. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 565478e..468e881 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,18 @@ This will place a complete build into the `build/out` folder by default. ### macOS #### Requirements -- A version of GCC/G++ or Clang/Clang++ with C++11 support +- Ninja - CMake 3.19 or newer #### Compiling ```sh mkdir build cd build -cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. -make && make install +cmake -G Ninja .. +ninja ``` -This will place a complete build into the `dist` folder by default. +This will place a complete build into the `build/out` folder by default. ## TODO - Improve the CMake files. We don't use them for GMod builds so they're a bit wonky. From a8edf25568b27cc0570c7f1438f9555827ba572e Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 3 Jun 2022 16:25:27 -0400 Subject: [PATCH 51/87] Bump CEF version to 102.0.9+g1c5e658+chromium-102.0.5005.63 --- CMakeLists.txt | 2 +- html_chromium/ChromiumBrowser.cpp | 1 - html_chromium/ChromiumBrowser.h | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a173a..e12122e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "98.2.1+g29d6e22+chromium-98.0.4758.109") +set(CEF_VERSION "102.0.9+g1c5e658+chromium-102.0.5005.63") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 4e74906..66fb68a 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -929,7 +929,6 @@ bool ChromiumBrowser::OnFileDialog( CefRefPtr, const CefString&, const CefString&, const std::vector&, - int, CefRefPtr callback ) { callback->Cancel(); diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index f56c2ec..7d41e1b 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -161,7 +161,6 @@ class ChromiumBrowser const CefString&, const CefString&, const std::vector&, - int, CefRefPtr callback ) override; // From ae6bfff17d07da01042866614ae35c65036d71c8 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sat, 4 Jun 2022 18:59:07 -0400 Subject: [PATCH 52/87] - Update README for new CEF version and correct build output folder - Consistently use tabs for CMakeLists - Fix icudtl.dat not outputting to the correct folder on Windows with INSTALL target --- README.md | 6 +- chromium_process/CMakeLists.txt | 220 ++++++++++++++++---------------- example_host/CMakeLists.txt | 4 +- html_chromium/CMakeLists.txt | 10 +- 4 files changed, 120 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 468e881..8931c71 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **98.2.1+g29d6e22+chromium-98.0.4758.109** +- **102.0.9+g1c5e658+chromium-102.0.5005.63** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. @@ -49,7 +49,7 @@ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release .. make && make install ``` -This will place a complete build into the `build/out` folder by default. +This will place a complete build into the `dist` folder by default. ### macOS #### Requirements @@ -64,7 +64,7 @@ cmake -G Ninja .. ninja ``` -This will place a complete build into the `build/out` folder by default. +This will place a complete build into the `dist` folder by default. ## TODO - Improve the CMake files. We don't use them for GMod builds so they're a bit wonky. diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt index b6a9bbb..ec8f342 100644 --- a/chromium_process/CMakeLists.txt +++ b/chromium_process/CMakeLists.txt @@ -15,115 +15,115 @@ APPEND_PLATFORM_SOURCES(SOURCES) APPEND_PLATFORM_SOURCES(RESOURCES) if(OS_LINUX OR OS_WINDOWS) - ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") - - if(OS_LINUX) - add_executable(${TARGET} ${SOURCES}) - else() - add_executable(${TARGET} WIN32 ${SOURCES} ${RESOURCES}) - endif() - SET_EXECUTABLE_TARGET_PROPERTIES(${TARGET}) - add_dependencies(${TARGET} libcef_dll_wrapper) - - target_link_libraries(${TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS}) - - if(OS_LINUX) - install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") - set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") - set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) - else() - if(CMAKE_SIZEOF_VOID_P MATCHES 8) - install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") - else() - install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") - endif() - - set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod_launcher") - target_link_libraries(${TARGET} - shlwapi - winmm - wsock32 - WS2_32 - comctl32 - rpcrt4 - version - DbgHelp - Psapi - wbemuuid - OleAut32 - SetupAPI - Propsys - Cfgmgr32 - PowrProf - Delayimp.lib) - - if(USE_SANDBOX) - ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") - target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) - endif() - endif() - - # FIXME: These should be moved to their own target - LIST(REMOVE_ITEM CEF_RESOURCE_FILES "icudtl.dat") - if(OS_LINUX) - COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") - COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") - COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/linux32/chromium") - else() - if(CMAKE_SIZEOF_VOID_P MATCHES 8) - COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win32") - COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") - else() - COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") - COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") - endif() - COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/chromium") - endif() + ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") + + if(OS_LINUX) + add_executable(${TARGET} ${SOURCES}) + else() + add_executable(${TARGET} WIN32 ${SOURCES} ${RESOURCES}) + endif() + SET_EXECUTABLE_TARGET_PROPERTIES(${TARGET}) + add_dependencies(${TARGET} libcef_dll_wrapper) + + target_link_libraries(${TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS}) + + if(OS_LINUX) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") + set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + else() + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() + + set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod_launcher") + target_link_libraries(${TARGET} + shlwapi + winmm + wsock32 + WS2_32 + comctl32 + rpcrt4 + version + DbgHelp + Psapi + wbemuuid + OleAut32 + SetupAPI + Propsys + Cfgmgr32 + PowrProf + Delayimp.lib) + + if(USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) + endif() + endif() + + # FIXME: These should be moved to their own target + LIST(REMOVE_ITEM CEF_RESOURCE_FILES "icudtl.dat") + if(OS_LINUX) + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/linux32/chromium") + else() + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/win64") + else() + COPY_FILES(${TARGET} "icudtl.dat" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") + COPY_FILES(${TARGET} "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin") + endif() + COPY_FILES(${TARGET} "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${INSTALL_OUT_DIR}/GarrysMod/bin/chromium") + endif() else() - set(HELPER_TARGET "gmod_Helper") - set(HELPER_OUTPUT_NAME "gmod Helper") - add_custom_target(${TARGET} ALL) - - if(USE_SANDBOX) - ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") - endif() - - add_custom_command( - TARGET ${TARGET} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework" - "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks/Chromium Embedded Framework.framework" - VERBATIM) - - foreach(_suffix_list ${CEF_HELPER_APP_SUFFIXES}) - string(REPLACE ":" ";" _suffix_list ${_suffix_list}) - list(GET _suffix_list 0 _name_suffix) - list(GET _suffix_list 1 _target_suffix) - list(GET _suffix_list 2 _plist_suffix) - - set(_helper_target "${HELPER_TARGET}${_target_suffix}") - set(_helper_output_name "${HELPER_OUTPUT_NAME}${_name_suffix}") - - set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist") - file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/helper-Info.plist" _plist_contents) - string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) - string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) - string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents}) - file(WRITE ${_helper_info_plist} ${_plist_contents}) - - add_executable(${_helper_target} MACOSX_BUNDLE ${SOURCES}) - SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target}) - add_dependencies(${_helper_target} libcef_dll_wrapper) - target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS}) - set_target_properties(${_helper_target} PROPERTIES - MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist} - OUTPUT_NAME ${_helper_output_name}) - - install(TARGETS ${_helper_target} DESTINATION ${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks) - - if(USE_SANDBOX) - target_link_libraries(${_helper_target} cef_sandbox_lib) - endif() - endforeach() + set(HELPER_TARGET "gmod_Helper") + set(HELPER_OUTPUT_NAME "gmod Helper") + add_custom_target(${TARGET} ALL) + + if(USE_SANDBOX) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + endif() + + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework" + "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks/Chromium Embedded Framework.framework" + VERBATIM) + + foreach(_suffix_list ${CEF_HELPER_APP_SUFFIXES}) + string(REPLACE ":" ";" _suffix_list ${_suffix_list}) + list(GET _suffix_list 0 _name_suffix) + list(GET _suffix_list 1 _target_suffix) + list(GET _suffix_list 2 _plist_suffix) + + set(_helper_target "${HELPER_TARGET}${_target_suffix}") + set(_helper_output_name "${HELPER_OUTPUT_NAME}${_name_suffix}") + + set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist") + file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/helper-Info.plist" _plist_contents) + string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) + string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents}) + string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents}) + file(WRITE ${_helper_info_plist} ${_plist_contents}) + + add_executable(${_helper_target} MACOSX_BUNDLE ${SOURCES}) + SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target}) + add_dependencies(${_helper_target} libcef_dll_wrapper) + target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS}) + set_target_properties(${_helper_target} PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist} + OUTPUT_NAME ${_helper_output_name}) + + install(TARGETS ${_helper_target} DESTINATION ${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/Frameworks) + + if(USE_SANDBOX) + target_link_libraries(${_helper_target} cef_sandbox_lib) + endif() + endforeach() endif() diff --git a/example_host/CMakeLists.txt b/example_host/CMakeLists.txt index 0a19732..3d5f6f4 100644 --- a/example_host/CMakeLists.txt +++ b/example_host/CMakeLists.txt @@ -18,6 +18,6 @@ target_compile_definitions(${TARGET} PRIVATE IMGUI_IMPL_OPENGL_LOADER_GLAD) target_link_libraries(${TARGET} glfw glad imgui html) if(OS_LINUX) - set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") - set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") + set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) endif() diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index eb3af0e..3952a2a 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -19,9 +19,9 @@ set(SOURCES ResourceHandler.h) if(OS_LINUX OR OS_WINDOWS) - ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") + ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") endif() - + add_library(${TARGET} SHARED ${SOURCES}) SET_LIBRARY_TARGET_PROPERTIES(${TARGET}) add_dependencies(${TARGET} html libcef_dll_wrapper) @@ -33,8 +33,8 @@ else() endif() if(OS_WINDOWS AND USE_SANDBOX) - ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") - target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) + ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") + target_link_libraries(${TARGET} cef_sandbox_lib ${CEF_SANDBOX_STANDARD_LIBS}) endif() set_target_properties(${TARGET} PROPERTIES PREFIX "") @@ -52,4 +52,4 @@ elseif(OS_LINUX) install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") elseif(OS_MAC) install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod_Signed.app/Contents/MacOS") -endif() \ No newline at end of file +endif() From a6b41f44ddcd39c49da9b4f4ae3241f3e028cd04 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sat, 30 Jul 2022 22:11:23 -0400 Subject: [PATCH 53/87] Update to CEF 104.4.15+g12fbff8+chromium-104.0.5112.65 --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e12122e..486d35c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "102.0.9+g1c5e658+chromium-102.0.5005.63") +set(CEF_VERSION "104.4.15+g12fbff8+chromium-104.0.5112.65") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 8931c71..72c1f70 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **102.0.9+g1c5e658+chromium-102.0.5005.63** +- **104.4.15+g12fbff8+chromium-104.0.5112.65** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From 8d664fdbf9a93939fff5cf6f3e4fa5f3f8041f20 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 14 Dec 2022 18:24:05 -0500 Subject: [PATCH 54/87] Update to CEF 108.4.12+g2e23ced+chromium-108.0.5359.71 --- CMakeLists.txt | 2 +- README.md | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 486d35c..5b8a442 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "104.4.15+g12fbff8+chromium-104.0.5112.65") +set(CEF_VERSION "108.4.12+g2e23ced+chromium-108.0.5359.71") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 72c1f70..40f948b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll probably want the **Minimal Distribution** unless you need to debug with ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **104.4.15+g12fbff8+chromium-104.0.5112.65** +- **108.4.12+g2e23ced+chromium-108.0.5359.71** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. @@ -61,14 +61,10 @@ This will place a complete build into the `dist` folder by default. mkdir build cd build cmake -G Ninja .. -ninja +ninja && ninja install ``` This will place a complete build into the `dist` folder by default. ## TODO -- Improve the CMake files. We don't use them for GMod builds so they're a bit wonky. -- Get the example_host into workable condition. It's disabled at the moment -- Cleanup. Everything is quite messy. -- macOS. These builds require quite a few unique things so they're not handled by the CMake scripts at all at the moment. It's still technically possible to get the builds working, though. - Dynamic loading of the HTML implementation. Atm we just use dylib() or LoadLibrary() in each host which is kind of lame. It'd be nice to simplify it. From 3120026e3fb469c4dac876c124b02da68f2b0fd4 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 11 Jan 2023 01:29:39 -0500 Subject: [PATCH 55/87] Downgrade back to CEF 106.1.1+g5891c70+chromium-106.0.5249.119 (CEF 108 currently has issues with Widevine, crashing on 32-bit Windows, etc) --- CMakeLists.txt | 2 +- README.md | 4 ++-- chromium_process/ChromiumApp.cpp | 2 ++ html_chromium/ChromiumSystem.cpp | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b8a442..0d8693e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "108.4.12+g2e23ced+chromium-108.0.5359.71") +set(CEF_VERSION "106.1.1+g5891c70+chromium-106.0.5249.119") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 40f948b..e9a50c5 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ To work with this project you will need a build of the Chromium Embedded Framewo https://cef-builds.spotifycdn.com/index.html -You'll probably want the **Minimal Distribution** unless you need to debug with symbols or something. These belong in the `./thirdparty/cef3/` directory (after extraction.) The paths are currently hardcoded into the root `CMakelists.txt` file. +Everything you need is in the **Standard Distribution**. If you need to debug the CEF part, grab the Symbols too. The extracted binary folder belongs in the `./thirdparty/cef3/` directory. The paths are currently hardcoded into the root `CMakelists.txt` file. ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **108.4.12+g2e23ced+chromium-108.0.5359.71** +- **106.1.1+g5891c70+chromium-106.0.5249.119** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index d84d35d..86bcce4 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -174,6 +174,8 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); + // TODO: WINE/Proton support? + //command_line->AppendSwitch( "no-sandbox" ); #endif command_line->AppendSwitch( "enable-system-flash" ); diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 7ab1919..b9c8e2c 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -39,6 +39,8 @@ class ChromiumApp command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); + // TODO: WINE/Proton support? + //command_line->AppendSwitch( "no-sandbox" ); #endif command_line->AppendSwitch( "enable-system-flash" ); From 19a5aea4956aecb988dd7b962a6df2ed21e0a950 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 12 May 2023 20:02:16 -0400 Subject: [PATCH 56/87] Fix `asset://` path-traversal --- html_chromium/ResourceHandler.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/html_chromium/ResourceHandler.cpp b/html_chromium/ResourceHandler.cpp index 7c01d5e..3042d01 100644 --- a/html_chromium/ResourceHandler.cpp +++ b/html_chromium/ResourceHandler.cpp @@ -56,6 +56,13 @@ bool ResourceHandler::ProcessRequest( CefRefPtr request, CefRefPtr Date: Tue, 23 May 2023 10:39:05 -0400 Subject: [PATCH 57/87] Re-enable site isolation (fixes some sites, but there might be consequences for the Lua<->JS bridge) --- chromium_process/ChromiumApp.cpp | 2 +- html_chromium/ChromiumSystem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 86bcce4..fecdc9d 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -198,7 +198,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); // Disable site isolation until we implement passing registered Lua functions between processes - command_line->AppendSwitch( "disable-site-isolation-trials" ); + //command_line->AppendSwitch( "disable-site-isolation-trials" ); } void ChromiumApp::OnRegisterCustomSchemes( CefRawPtr registrar ) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index b9c8e2c..7422192 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -63,7 +63,7 @@ class ChromiumApp command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); // Disable site isolation until we implement passing registered Lua functions between processes - command_line->AppendSwitch( "disable-site-isolation-trials" ); + //command_line->AppendSwitch( "disable-site-isolation-trials" ); } void OnRegisterCustomSchemes( CefRawPtr registrar ) override From b67874902f687f4f166a235a34be88976264f850 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 13 Jun 2023 15:13:21 -0400 Subject: [PATCH 58/87] - Update CEF to 114.2.10+g398e3c3+chromium-114.0.5735.110 - Update Visual Studio target from 2019 to 2022 (CEF build requirements update) --- CMakeLists.txt | 2 +- README.md | 10 +++++----- chromium_process/Windows.cpp | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d8693e..d184069 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "106.1.1+g5891c70+chromium-106.0.5249.119") +set(CEF_VERSION "114.2.10+g398e3c3+chromium-114.0.5735.110") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index e9a50c5..f9cb36f 100644 --- a/README.md +++ b/README.md @@ -11,27 +11,27 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **106.1.1+g5891c70+chromium-106.0.5249.119** +- **114.2.10+g398e3c3+chromium-114.0.5735.110** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. ## Getting started ### Windows #### Requirements -- Visual Studio 2019 -- CMake 3.15 or newer +- Visual Studio 2022 +- CMake 3.19 or newer #### Generating Visual Studio Solution ##### x86 ```bat mkdir build_x86 cd build_x86 -cmake -G "Visual Studio 16 2019" -A Win32 .. +cmake -G "Visual Studio 17 2022" -A Win32 .. ``` #### x86-64 ```bat mkdir build_x64 cd build_x64 -cmake -G "Visual Studio 16 2019" -A x64 .. +cmake -G "Visual Studio 17 2022" -A x64 .. ``` After running either of these sets of commands, you can enter your created directory and open the `gmod-html.sln` solution in Visual Studio. Compiling the `INSTALL` project will place a complete build into the `/out` folder by default. diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp index 7dfe13c..c943bb2 100644 --- a/chromium_process/Windows.cpp +++ b/chromium_process/Windows.cpp @@ -38,7 +38,6 @@ using FuncLauncherMain = int (*)(HINSTANCE, HINSTANCE, LPSTR, int); int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { // Check if "--type=" is in the command arguments. If it is, we are a chromium subprocess. if (strstr(lpCmdLine, "--type=")) { - CefEnableHighDPISupport(); void* sandbox_info = nullptr; #ifdef CEF_USE_SANDBOX From 3047114917f1a11a431f549bac160b6190a02979 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 13 Jun 2023 16:15:13 -0400 Subject: [PATCH 59/87] 32-bit Windows gmod.exe now just tells players to launch it in 64-bit mode --- chromium_process/CMakeLists.txt | 7 ++++++- chromium_process/Windows32Bit.cpp | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 chromium_process/Windows32Bit.cpp diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt index ec8f342..ebfb99e 100644 --- a/chromium_process/CMakeLists.txt +++ b/chromium_process/CMakeLists.txt @@ -7,7 +7,12 @@ set(RESOURCES ) set(SOURCES_LINUX Linux.cpp) set(SOURCES_MAC macOS.cpp) -set(SOURCES_WINDOWS Windows.cpp) + +if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(SOURCES_WINDOWS Windows.cpp) +else() + set(SOURCES_WINDOWS Windows32Bit.cpp) +endif() set(RESOURCES_WINDOWS resources/win/gmod_icon.rc) diff --git a/chromium_process/Windows32Bit.cpp b/chromium_process/Windows32Bit.cpp new file mode 100644 index 0000000..15dc40a --- /dev/null +++ b/chromium_process/Windows32Bit.cpp @@ -0,0 +1,7 @@ +#include + +// Disable 32-bit Windows (for now?), since CEF 114 crashes on it while joining a server +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + MessageBoxA(NULL, "GModCEFCodecFix doesn't support 32-bit. Please launch Garry's Mod in 64-bit mode.", "Launcher Error", 0); + return 0; +} From b68d37ab513b1c1e877c5cda2d69934a2406991a Mon Sep 17 00:00:00 2001 From: snow flurry Date: Thu, 20 Jul 2023 21:02:42 -0700 Subject: [PATCH 60/87] html_chromium: Disable DXVA acceleration This allows Linux systems running GMod over Proton/Wine to play back videos, due to the lack of msvproc.dll (MF Video Processor required for DXVA2). --- html_chromium/ChromiumSystem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 7422192..41eecb2 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -41,6 +41,11 @@ class ChromiumApp command_line->AppendSwitch( "enable-begin-frame-scheduling" ); // TODO: WINE/Proton support? //command_line->AppendSwitch( "no-sandbox" ); + + // Disables DXVA acceleration, which should already be disabled but CEF seems to be + // re-enabling (see https://chromium-review.googlesource.com/c/chromium/src/+/4179889) + // This allows Proton/Wine to run video, since msvproc.dll doesn't exist in Wine + command_line->AppendSwitchWithValue( "disable-features", "DXVAVideoDecoding" ); #endif command_line->AppendSwitch( "enable-system-flash" ); From ff4bdbfb5ad70e76750968d70088bfb9285664f2 Mon Sep 17 00:00:00 2001 From: snow flurry Date: Fri, 21 Jul 2023 16:55:36 -0700 Subject: [PATCH 61/87] html_chromium: merge disable-features flags --- html_chromium/ChromiumSystem.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 41eecb2..074747c 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -41,11 +41,6 @@ class ChromiumApp command_line->AppendSwitch( "enable-begin-frame-scheduling" ); // TODO: WINE/Proton support? //command_line->AppendSwitch( "no-sandbox" ); - - // Disables DXVA acceleration, which should already be disabled but CEF seems to be - // re-enabling (see https://chromium-review.googlesource.com/c/chromium/src/+/4179889) - // This allows Proton/Wine to run video, since msvproc.dll doesn't exist in Wine - command_line->AppendSwitchWithValue( "disable-features", "DXVAVideoDecoding" ); #endif command_line->AppendSwitch( "enable-system-flash" ); @@ -59,7 +54,8 @@ class ChromiumApp #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling" ); + // DXVAVideoDecoding must be disabled for Proton/Wine + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); From ab22bcd59ba0a4ea194f05de798bdd780f1b0a6d Mon Sep 17 00:00:00 2001 From: snow flurry Date: Fri, 21 Jul 2023 16:55:58 -0700 Subject: [PATCH 62/87] chromium_process: Disable DXVAVideoDecoding --- chromium_process/ChromiumApp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index fecdc9d..04cec89 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -189,7 +189,8 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling" ); + // DXVAVideoDecoding must be disabled for Proton/Wine + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); From 3ea10a2285d380ce24a110c2d8762e63ef29cff4 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 27 Jul 2023 19:08:01 -0400 Subject: [PATCH 63/87] Add commented remote-allow-origins/remote_debugging_port for devs that want to enable it --- chromium_process/ChromiumApp.cpp | 5 ++++- html_chromium/ChromiumSystem.cpp | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 04cec89..dd15c5f 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -189,7 +189,7 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - // DXVAVideoDecoding must be disabled for Proton/Wine + // DXVAVideoDecoding must be disabled for Proton/Wine command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); // Auto-play media @@ -200,6 +200,9 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, // Disable site isolation until we implement passing registered Lua functions between processes //command_line->AppendSwitch( "disable-site-isolation-trials" ); + + // Enable remote debugging; see also: settings.remote_debugging_port + //command_line->AppendSwitchWithValue( "remote-allow-origins", "http://localhost:9222" ); } void ChromiumApp::OnRegisterCustomSchemes( CefRawPtr registrar ) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 074747c..1d637b4 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -54,7 +54,7 @@ class ChromiumApp #endif // https://bitbucket.org/chromiumembedded/cef/issues/2400 - // DXVAVideoDecoding must be disabled for Proton/Wine + // DXVAVideoDecoding must be disabled for Proton/Wine command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); // Auto-play media @@ -65,6 +65,9 @@ class ChromiumApp // Disable site isolation until we implement passing registered Lua functions between processes //command_line->AppendSwitch( "disable-site-isolation-trials" ); + + // Enable remote debugging; see also: settings.remote_debugging_port + //command_line->AppendSwitchWithValue( "remote-allow-origins", "http://localhost:9222" ); } void OnRegisterCustomSchemes( CefRawPtr registrar ) override @@ -106,7 +109,8 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource CefSettings settings; std::string strBaseDir = pBaseDir; - + + //settings.remote_debugging_port = 9222; settings.remote_debugging_port = 0; settings.windowless_rendering_enabled = true; From fab56b6a492a780d19e2c5af0e10bef48c9c2b41 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 27 Sep 2023 19:49:43 -0400 Subject: [PATCH 64/87] - Update to CEF 116.0.27+gd8c85ac+chromium-116.0.5845.190 - Remove CEF-specific integer and char16 types (see https://github.com/chromiumembedded/cef/issues/3507) --- CMakeLists.txt | 2 +- README.md | 2 +- chromium_process/ChromiumApp.cpp | 2 +- chromium_process/Linux.cpp | 6 +++--- html_chromium/ChromiumBrowser.cpp | 8 ++++---- html_chromium/HtmlResourceHandler.cpp | 4 ++-- html_chromium/HtmlResourceHandler.h | 2 +- html_chromium/ResourceHandler.cpp | 4 ++-- html_chromium/ResourceHandler.h | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d184069..c64d91d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "114.2.10+g398e3c3+chromium-114.0.5735.110") +set(CEF_VERSION "116.0.27+gd8c85ac+chromium-116.0.5845.190") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index f9cb36f..70aa35e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **114.2.10+g398e3c3+chromium-114.0.5735.110** +- **116.0.27+gd8c85ac+chromium-116.0.5845.190** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index dd15c5f..db4c4a0 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -447,7 +447,7 @@ void ChromiumApp::RegisterFunction( CefRefPtr browser, CefRefPtr frames; + std::vector frames; browser->GetFrameIdentifiers( frames ); for ( auto frameId : frames ) diff --git a/chromium_process/Linux.cpp b/chromium_process/Linux.cpp index 780ded4..275cac0 100644 --- a/chromium_process/Linux.cpp +++ b/chromium_process/Linux.cpp @@ -2,7 +2,7 @@ #include "ChromiumApp.h" int main(int argc, char* argv[]) { - CefMainArgs main_args(argc, argv); - CefRefPtr app(new ChromiumApp()); - return CefExecuteProcess(main_args, app, nullptr); + CefMainArgs main_args(argc, argv); + CefRefPtr app(new ChromiumApp()); + return CefExecuteProcess(main_args, app, nullptr); } diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 66fb68a..f5670f6 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -299,14 +299,14 @@ void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) { case IHtmlClient::KeyEvent::Type::KeyChar: chromiumKeyEvent.type = KEYEVENT_CHAR; - chromiumKeyEvent.character = static_cast( keyEvent.key_char ); - chromiumKeyEvent.unmodified_character = static_cast( keyEvent.key_char ); + chromiumKeyEvent.character = static_cast( keyEvent.key_char ); + chromiumKeyEvent.unmodified_character = static_cast( keyEvent.key_char ); #ifdef __APPLE__ chromiumKeyEvent.windows_key_code = 0; chromiumKeyEvent.native_key_code = keyEvent.native_key_code; #else - chromiumKeyEvent.windows_key_code = static_cast( keyEvent.key_char ); - chromiumKeyEvent.native_key_code = static_cast( keyEvent.key_char ); + chromiumKeyEvent.windows_key_code = static_cast( keyEvent.key_char ); + chromiumKeyEvent.native_key_code = static_cast( keyEvent.key_char ); #endif break; case IHtmlClient::KeyEvent::Type::KeyDown: diff --git a/html_chromium/HtmlResourceHandler.cpp b/html_chromium/HtmlResourceHandler.cpp index 69f0e3c..4e3c922 100644 --- a/html_chromium/HtmlResourceHandler.cpp +++ b/html_chromium/HtmlResourceHandler.cpp @@ -33,7 +33,7 @@ bool HtmlResourceHandler::ProcessRequest( CefRefPtr request, CefRefP return true; } -void HtmlResourceHandler::GetResponseHeaders( CefRefPtr response, cef_int64& responseLength, CefString& redirectUrl ) +void HtmlResourceHandler::GetResponseHeaders( CefRefPtr response, int64_t& responseLength, CefString& redirectUrl ) { CefResponse::HeaderMap headerMap; headerMap.emplace( "X-XSS-Protection", "0" ); @@ -49,7 +49,7 @@ bool HtmlResourceHandler::ReadResponse( void* data_out, int bytes_to_read, int& if ( m_Html.size() <= 0 ) return false; - bytes_to_read = std::min( bytes_to_read, m_Html.size() - m_Current ); + bytes_to_read = std::min( bytes_to_read, m_Html.size() - m_Current ); memcpy( data_out, m_Html.data() + m_Current, bytes_to_read ); bytes_read = bytes_to_read; diff --git a/html_chromium/HtmlResourceHandler.h b/html_chromium/HtmlResourceHandler.h index 70b776c..6788674 100644 --- a/html_chromium/HtmlResourceHandler.h +++ b/html_chromium/HtmlResourceHandler.h @@ -28,7 +28,7 @@ class HtmlResourceHandler // CefResourceHandler interface // bool ProcessRequest( CefRefPtr request, CefRefPtr callback ) override; - void GetResponseHeaders( CefRefPtr response, cef_int64& responseLength, CefString& redirectUrl ) override; + void GetResponseHeaders( CefRefPtr response, int64_t& responseLength, CefString& redirectUrl ) override; bool ReadResponse( void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback ) override; void Cancel() override; diff --git a/html_chromium/ResourceHandler.cpp b/html_chromium/ResourceHandler.cpp index 3042d01..040ca2f 100644 --- a/html_chromium/ResourceHandler.cpp +++ b/html_chromium/ResourceHandler.cpp @@ -80,7 +80,7 @@ static std::string GetFileExtension( std::string& path ) return path.substr( i + 1, path.length() ); } -void ResourceHandler::GetResponseHeaders( CefRefPtr response, cef_int64& responseLength, CefString& redirectUrl ) +void ResourceHandler::GetResponseHeaders( CefRefPtr response, int64_t& responseLength, CefString& redirectUrl ) { if ( m_Buffer == nullptr ) { @@ -105,7 +105,7 @@ void ResourceHandler::GetResponseHeaders( CefRefPtr response, cef_i bool ResourceHandler::ReadResponse( void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback ) { - bytes_to_read = std::min( bytes_to_read, m_BufferLength - ( m_BufferPtr - m_Buffer ) ); + bytes_to_read = std::min( bytes_to_read, m_BufferLength - ( m_BufferPtr - m_Buffer ) ); memcpy( data_out, m_BufferPtr, bytes_to_read ); bytes_read = bytes_to_read; diff --git a/html_chromium/ResourceHandler.h b/html_chromium/ResourceHandler.h index 4bc1981..b6fb726 100644 --- a/html_chromium/ResourceHandler.h +++ b/html_chromium/ResourceHandler.h @@ -34,7 +34,7 @@ class ResourceHandler // CefResourceHandler interface // bool ProcessRequest( CefRefPtr request, CefRefPtr callback ) override; - void GetResponseHeaders( CefRefPtr response, cef_int64& responseLength, CefString& redirectUrl ) override; + void GetResponseHeaders( CefRefPtr response, int64_t& responseLength, CefString& redirectUrl ) override; bool ReadResponse( void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback ) override; void Cancel() override; From 336069792fe9d7503c07c03314ab223aa2d47867 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sun, 1 Oct 2023 16:31:19 -0400 Subject: [PATCH 65/87] Allow launching GMod in 32-bit, but warn players it may not work --- chromium_process/CMakeLists.txt | 7 +------ chromium_process/Windows.cpp | 1 + chromium_process/Windows32Bit.cpp | 7 ------- 3 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 chromium_process/Windows32Bit.cpp diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt index ebfb99e..cb738ee 100644 --- a/chromium_process/CMakeLists.txt +++ b/chromium_process/CMakeLists.txt @@ -8,12 +8,7 @@ set(RESOURCES ) set(SOURCES_LINUX Linux.cpp) set(SOURCES_MAC macOS.cpp) -if(CMAKE_SIZEOF_VOID_P MATCHES 8) - set(SOURCES_WINDOWS Windows.cpp) -else() - set(SOURCES_WINDOWS Windows32Bit.cpp) -endif() - +set(SOURCES_WINDOWS Windows.cpp) set(RESOURCES_WINDOWS resources/win/gmod_icon.rc) APPEND_PLATFORM_SOURCES(SOURCES) diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp index c943bb2..7ff37a8 100644 --- a/chromium_process/Windows.cpp +++ b/chromium_process/Windows.cpp @@ -64,6 +64,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ executable_dir += "\\..\\.."; #else executable_dir += "\\.."; + MessageBoxA(NULL, "You may encounter stability issues with GModCEFCodecFix in 32-bit mode. Please launch Garry's Mod in 64-bit mode instead if possible.", "32-bit Warning", 0); #endif _chdir(executable_dir.c_str()); diff --git a/chromium_process/Windows32Bit.cpp b/chromium_process/Windows32Bit.cpp deleted file mode 100644 index 15dc40a..0000000 --- a/chromium_process/Windows32Bit.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -// Disable 32-bit Windows (for now?), since CEF 114 crashes on it while joining a server -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { - MessageBoxA(NULL, "GModCEFCodecFix doesn't support 32-bit. Please launch Garry's Mod in 64-bit mode.", "Launcher Error", 0); - return 0; -} From 60a46b04aaa5cb347eb462ace30d33703d7de807 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 27 Oct 2023 01:15:55 -0400 Subject: [PATCH 66/87] Update to CEF 118.6.8+ge44bee1+chromium-118.0.5993.117 --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c64d91d..b47e67e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "116.0.27+gd8c85ac+chromium-116.0.5845.190") +set(CEF_VERSION "118.6.8+ge44bee1+chromium-118.0.5993.117") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 70aa35e..9ba7bce 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **116.0.27+gd8c85ac+chromium-116.0.5845.190** +- **118.6.8+ge44bee1+chromium-118.0.5993.117** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From 08ac0ca458261d726a1125a3c7af7831ceb96c2f Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Jan 2024 01:17:14 -0500 Subject: [PATCH 67/87] Update to CEF 120.2.7+g4bc6a59+chromium-120.0.6099.234 --- CMakeLists.txt | 2 +- README.md | 2 +- html_chromium/ChromiumBrowser.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b47e67e..6d21d8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "118.6.8+ge44bee1+chromium-118.0.5993.117") +set(CEF_VERSION "120.2.7+g4bc6a59+chromium-120.0.6099.234") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 9ba7bce..313ba37 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **118.6.8+ge44bee1+chromium-118.0.5993.117** +- **120.2.7+g4bc6a59+chromium-120.0.6099.234** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index f5670f6..50af814 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -556,7 +556,7 @@ bool ChromiumBrowser::OnBeforePopup( CefRefPtr, msg.type = MessageQueue::Type::OnCreateChildView; msg.string1 = sourceUrl.ToString(); msg.string2 = targetUrl.ToString(); - msg.integer = static_cast( targetDisposition == WOD_NEW_POPUP ); + msg.integer = static_cast( targetDisposition == CEF_WOD_NEW_POPUP ); QueueMessage( std::move( msg ) ); // Don't create the popup From fdd016edda11c79c4bed682d4cc9027a99a0b6b4 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Jan 2024 22:47:28 -0500 Subject: [PATCH 68/87] Add CMAKE_BUILD_TYPE to macOS instructions too --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 313ba37..d4c5637 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ This will place a complete build into the `dist` folder by default. ```sh mkdir build cd build -cmake -G Ninja .. +cmake -G Ninja -D CMAKE_BUILD_TYPE=Release .. ninja && ninja install ``` From 4b0940783abf03cdf94df1b4a3ad342ef9605c47 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 1 Feb 2024 10:56:14 -0500 Subject: [PATCH 69/87] Chrome only uses the Major version in their User Agent strings after version 100 (reduce-user-agent) --- html_chromium/ChromiumSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 1d637b4..5bb14c8 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -202,7 +202,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource #error #endif - std::string chrome_version = std::to_string(CHROME_VERSION_MAJOR) + "." + std::to_string(CHROME_VERSION_MINOR) + "." + std::to_string(CHROME_VERSION_BUILD) + "." + std::to_string(CHROME_VERSION_PATCH); + std::string chrome_version = std::to_string(CHROME_VERSION_MAJOR) + ".0.0.0"; CefString(&settings.user_agent).FromString("Mozilla/5.0 (" + platform + "; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chrome_version + " Safari/537.36 GMod/13"); CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); From 1f445f685e28912b090c72f660e0ff7d80873d48 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 7 Feb 2024 18:36:16 -0500 Subject: [PATCH 70/87] - Disable FirstPartySets (it causes crashing on Chromium 120, also it's a blatantly anti-privacy feature - Don't enable HTMLImports anymore; I'm pretty sure we don't need it --- chromium_process/ChromiumApp.cpp | 6 ++---- html_chromium/ChromiumSystem.cpp | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index db4c4a0..69d2603 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -190,14 +190,12 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, // https://bitbucket.org/chromiumembedded/cef/issues/2400 // DXVAVideoDecoding must be disabled for Proton/Wine - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); + // FirstPartySets causes crashing on Chromium 120, also it's an anti-privacy feature + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding,FirstPartySets" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); - // Chromium 80 removed this but only sometimes. - command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); - // Disable site isolation until we implement passing registered Lua functions between processes //command_line->AppendSwitch( "disable-site-isolation-trials" ); diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 5bb14c8..a75e712 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -55,14 +55,12 @@ class ChromiumApp // https://bitbucket.org/chromiumembedded/cef/issues/2400 // DXVAVideoDecoding must be disabled for Proton/Wine - command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding" ); + // FirstPartySets causes crashing on Chromium 120, also it's an anti-privacy feature + command_line->AppendSwitchWithValue( "disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents,HardwareMediaKeyHandling,DXVAVideoDecoding,FirstPartySets" ); // Auto-play media command_line->AppendSwitchWithValue( "autoplay-policy", "no-user-gesture-required" ); - // Chromium 80 removed this but only sometimes. - command_line->AppendSwitchWithValue( "enable-blink-features", "HTMLImports" ); - // Disable site isolation until we implement passing registered Lua functions between processes //command_line->AppendSwitch( "disable-site-isolation-trials" ); From 6598618b9b4f07b29142d463cd53e2a316c82f07 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sat, 9 Mar 2024 23:43:40 -0500 Subject: [PATCH 71/87] - Configure root_cache_path to make CEF 120+ happy - Add really basic log rotation for chromium.log - Configure cache_path so everything is in one place --- html_chromium/ChromiumSystem.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index a75e712..c02b1df 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -18,12 +18,8 @@ #include #include #include - -#ifdef _WIN32 -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING -#include -namespace fs = std::experimental::filesystem; -#endif +#include +namespace fs = std::filesystem; class ChromiumApp : public CefApp @@ -203,7 +199,23 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource std::string chrome_version = std::to_string(CHROME_VERSION_MAJOR) + ".0.0.0"; CefString(&settings.user_agent).FromString("Mozilla/5.0 (" + platform + "; Valve Source Client) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chrome_version + " Safari/537.36 GMod/13"); - CefString( &settings.log_file ).FromString( strBaseDir + "/chromium.log" ); + // Rotate log file + // TODO(winter): This is probably not the best place to be doing this + std::string curLogPath = strBaseDir + "/chromium.log"; + std::string lastLogPath = strBaseDir + "/chromium.log.1"; + + try { + fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing); + fs::remove(curLogPath); // TODO(winter): Truncate instead? + } catch (fs::filesystem_error& e) { + LOG(ERROR) << "Couldn't rotate log file: " << e.what(); + } + + CefString( &settings.log_file ).FromString( curLogPath ); + + // CEF 120+ requires this otherwise CEF applications will trample each other + CefString( &settings.root_cache_path ).FromString( strBaseDir + "/ChromiumCache" ); + CefString( &settings.cache_path ).FromString( strBaseDir + "/ChromiumCache" ); // Grab our Sandbox info from the "game" exe #if defined(_WIN32) && defined(CEF_USE_SANDBOX) From d16d07fecc8cda7ee26f2dac7077c8721b737eb1 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Sun, 10 Mar 2024 01:22:04 -0500 Subject: [PATCH 72/87] Use error_code approach for errors rotating chromium.log --- html_chromium/ChromiumSystem.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index c02b1df..fad36c5 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -201,14 +201,21 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource // Rotate log file // TODO(winter): This is probably not the best place to be doing this + std::error_code rotateError; std::string curLogPath = strBaseDir + "/chromium.log"; std::string lastLogPath = strBaseDir + "/chromium.log.1"; - try { - fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing); - fs::remove(curLogPath); // TODO(winter): Truncate instead? - } catch (fs::filesystem_error& e) { - LOG(ERROR) << "Couldn't rotate log file: " << e.what(); + fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing, rotateError); + + if (rotateError) { + LOG(WARNING) << "Couldn't copy log file: " << rotateError.message(); + rotateError.clear(); + } + + if (rotateError) { + fs::remove(curLogPath, rotateError); // TODO(winter): Truncate instead? + LOG(WARNING) << "Couldn't remove log file: " << rotateError.message(); + rotateError.clear(); } CefString( &settings.log_file ).FromString( curLogPath ); From fe463dac795d6599dfb4a6dcfb6eac422d221628 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 21 Mar 2024 00:08:57 -0400 Subject: [PATCH 73/87] Minimum C++ version is now C++17 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d21d8c..46738a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.19) set(CMAKE_CONFIGURATION_TYPES Debug Release) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) project(gmod_html) From cb092df53d069569924db24f485356a4940617b1 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 21 Mar 2024 00:27:19 -0400 Subject: [PATCH 74/87] Force CMAKE_OSX_DEPLOYMENT_TARGET to 10.15 --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46738a3..a19373a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.19) set(CMAKE_CONFIGURATION_TYPES Debug Release) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") project(gmod_html) From f25e08821af733f1fbf2f52e76c7d6e36896d2f1 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 26 Apr 2024 20:13:12 -0400 Subject: [PATCH 75/87] Actually remove the old chromium.log once it's been rotated --- html_chromium/ChromiumSystem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index fad36c5..06115a0 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -212,8 +212,10 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource rotateError.clear(); } + // TODO(winter): Truncate instead? + fs::remove(curLogPath, rotateError); + if (rotateError) { - fs::remove(curLogPath, rotateError); // TODO(winter): Truncate instead? LOG(WARNING) << "Couldn't remove log file: " << rotateError.message(); rotateError.clear(); } From de980b88d12bb9ea216fd27ef1a69533b82756e4 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Fri, 26 Apr 2024 20:22:33 -0400 Subject: [PATCH 76/87] Give error messages related to chromium.log and CefInitialize to GMod instead of trying to write to chromium.log before it's initialized --- html_chromium/ChromiumSystem.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 06115a0..388a75e 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -208,7 +208,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing, rotateError); if (rotateError) { - LOG(WARNING) << "Couldn't copy log file: " << rotateError.message(); + pResourceHandler->Message("Couldn't rotate chromium.log (copy): " + rotateError.message() + "\n"); rotateError.clear(); } @@ -216,7 +216,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource fs::remove(curLogPath, rotateError); if (rotateError) { - LOG(WARNING) << "Couldn't remove log file: " << rotateError.message(); + pResourceHandler->Message("Couldn't rotate chromium.log (remove): " + rotateError.message() + "\n"); rotateError.clear(); } @@ -255,6 +255,7 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource if ( !CefInitialize( main_args, settings, new ChromiumApp, sandbox_info ) ) { + pResourceHandler->Message("CefInitialize failed!\n"); return false; } From 92ebc223fa572ff664a9d376b4e5bc7cee087a5c Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Apr 2024 02:21:05 -0400 Subject: [PATCH 77/87] - Update to CEF 124.3.0+g77c1e82+chromium-124.0.6367.60 - Implement OnAlreadyRunningAppRelaunch and Fix -multirun support - Fix rotate log errors --- CMakeLists.txt | 2 +- README.md | 2 +- chromium_process/ChromiumApp.cpp | 20 ++++++--- chromium_process/ChromiumApp.h | 74 +++++++++++++++++-------------- html_chromium/ChromiumBrowser.cpp | 2 +- html_chromium/ChromiumBrowser.h | 2 +- html_chromium/ChromiumSystem.cpp | 70 +++++++++++++++++++++++------ html_chromium/ChromiumSystem.h | 2 + 8 files changed, 117 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a19373a..b0bef2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "120.2.7+g4bc6a59+chromium-120.0.6099.234") +set(CEF_VERSION "124.3.0+g77c1e82+chromium-124.0.6367.60") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index d4c5637..08a1d8e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **120.2.7+g4bc6a59+chromium-120.0.6099.234** +- **124.3.0+g77c1e82+chromium-124.0.6367.60** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 69d2603..d1d70bb 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -174,7 +174,8 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); - // TODO: WINE/Proton support? + + // TODO: WINE/Proton/Flatpak support? //command_line->AppendSwitch( "no-sandbox" ); #endif command_line->AppendSwitch( "enable-system-flash" ); @@ -214,6 +215,15 @@ CefRefPtr ChromiumApp::GetRenderProcessHandler() return this; } +// +// CefBrowserProcessHandler interface +// +bool ChromiumApp::OnAlreadyRunningAppRelaunch( CefRefPtr command_line, const CefString ¤t_directory ) +{ + // See ChromiumSystem::Init, we detect lockfile and handle things there + return true; +} + // // CefRenderProcessHandler interface // @@ -226,7 +236,7 @@ void ChromiumApp::OnContextCreated( CefRefPtr browser, CefRefPtrGetGlobal()->DeleteValue( "print" ); - // Removing WebSQL for now - we can add it back after CEF3 has been updated + // TODO: Removing WebSQL for now - we can add it back after CEF3 has been updated context->GetGlobal()->DeleteValue( "openDatabase" ); } @@ -244,7 +254,7 @@ void ChromiumApp::OnContextCreated( CefRefPtr browser, CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) { - + // Do nothing } bool ChromiumApp::OnProcessMessageReceived( CefRefPtr browser, CefRefPtr frame, CefProcessId source_process, CefRefPtr message ) @@ -445,12 +455,12 @@ void ChromiumApp::RegisterFunction( CefRefPtr browser, CefRefPtr frames; + std::vector frames; browser->GetFrameIdentifiers( frames ); for ( auto frameId : frames ) { - RegisterFunctionInFrame( browser->GetFrame( frameId ), objName, funcName ); + RegisterFunctionInFrame( browser->GetFrameByIdentifier( frameId ), objName, funcName ); } } diff --git a/chromium_process/ChromiumApp.h b/chromium_process/ChromiumApp.h index d50e18d..dcc3e52 100644 --- a/chromium_process/ChromiumApp.h +++ b/chromium_process/ChromiumApp.h @@ -4,53 +4,59 @@ #include class ChromiumApp - : public CefApp - , public CefRenderProcessHandler - , public CefV8Handler + : public CefApp + , public CefBrowserProcessHandler + , public CefRenderProcessHandler + , public CefV8Handler { public: - // - // CefApp interface - // - void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override; - void OnRegisterCustomSchemes( CefRawPtr registrar ) override; - CefRefPtr GetRenderProcessHandler() override; - - // - // CefRenderProcessHandler interface - // - void OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override; - void OnContextReleased( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override; + // + // CefApp interface + // + void OnBeforeCommandLineProcessing( const CefString& process_type, CefRefPtr command_line ) override; + void OnRegisterCustomSchemes( CefRawPtr registrar ) override; + CefRefPtr GetRenderProcessHandler() override; + + // + // CefBrowserProcessHandler interface + // + bool OnAlreadyRunningAppRelaunch( CefRefPtr command_line, const CefString ¤t_directory ) override; + + // + // CefRenderProcessHandler interface + // + void OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override; + void OnContextReleased( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) override; bool OnProcessMessageReceived( CefRefPtr browser, CefRefPtr frame, CefProcessId source_process, CefRefPtr message ) override; - // - // CefV8Handler interface - // - bool Execute( const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception ) override; + // + // CefV8Handler interface + // + bool Execute( const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception ) override; private: - int CreateCallback( CefRefPtr context, CefRefPtr func ); - void RegisterFunctionInFrame( CefRefPtr frame, const CefString& objName, const CefString& funcName ); + int CreateCallback( CefRefPtr context, CefRefPtr func ); + void RegisterFunctionInFrame( CefRefPtr frame, const CefString& objName, const CefString& funcName ); - // Messages from the game process + // Messages from the game process void ExecuteJavaScript( CefRefPtr browser, CefRefPtr args ); - void RegisterFunction( CefRefPtr browser, CefRefPtr args ); - void ExecuteCallback( CefRefPtr browser, CefRefPtr args ); - void ForgetCallback( CefRefPtr browser, CefRefPtr args ); + void RegisterFunction( CefRefPtr browser, CefRefPtr args ); + void ExecuteCallback( CefRefPtr browser, CefRefPtr args ); + void ForgetCallback( CefRefPtr browser, CefRefPtr args ); private: - std::vector> m_RegisteredFunctions; + std::vector> m_RegisteredFunctions; - struct Callback - { - CefRefPtr Context; - CefRefPtr Function; - }; + struct Callback + { + CefRefPtr Context; + CefRefPtr Function; + }; - std::unordered_map m_Callbacks; - int m_NextCallbackId = 0; + std::unordered_map m_Callbacks; + int m_NextCallbackId = 0; private: - IMPLEMENT_REFCOUNTING( ChromiumApp ); + IMPLEMENT_REFCOUNTING( ChromiumApp ); }; diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 50af814..06fb65b 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -804,7 +804,7 @@ void ChromiumBrowser::OnPaint( CefRefPtr, CefRenderHandler::PaintEle } } -void ChromiumBrowser::OnAcceleratedPaint(CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, void* shared_handle) +void ChromiumBrowser::OnAcceleratedPaint(CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const CefAcceleratedPaintInfo& info) { // TODO: Implement once fixed for OSR on Viz // TODO: See ChromiumSystem::CreateClient diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 7d41e1b..4c68ea8 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -111,7 +111,7 @@ class ChromiumBrowser void OnPopupShow( CefRefPtr, bool show ) override; void OnPopupSize( CefRefPtr, const CefRect& rect ) override; void OnPaint( CefRefPtr, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const void* buffer, int width, int height ) override; - void OnAcceleratedPaint( CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, void* shared_handle ) override; + void OnAcceleratedPaint( CefRefPtr browser, CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& dirtyRects, const CefAcceleratedPaintInfo& info) override; // // CefRequestHandler interface diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 388a75e..0a8639e 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -35,7 +35,7 @@ class ChromiumApp command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); - // TODO: WINE/Proton support? + // TODO: WINE/Proton/Flatpak support? //command_line->AppendSwitch( "no-sandbox" ); #endif command_line->AppendSwitch( "enable-system-flash" ); @@ -77,7 +77,7 @@ class ChromiumApp typedef void* ( *CreateCefSandboxInfoFn )( ); typedef void ( *DestroyCefSandboxInfoFn )( void* ); -// Needs cleaning up. There's too much Windows shit. +// TODO: Needs cleaning up. There's too much Windows shit. bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResourceHandler ) { g_pHtmlResourceHandler = pResourceHandler; @@ -205,26 +205,50 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource std::string curLogPath = strBaseDir + "/chromium.log"; std::string lastLogPath = strBaseDir + "/chromium.log.1"; - fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing, rotateError); + std::string cefCachePath = strBaseDir + "/ChromiumCache"; + std::string cefLockFilePath = cefCachePath + "/lockfile"; - if (rotateError) { - pResourceHandler->Message("Couldn't rotate chromium.log (copy): " + rotateError.message() + "\n"); - rotateError.clear(); - } + // TODO(winter): What if GMod/CEF crashes? Will the lockfile still be there? + if (fs::exists(cefLockFilePath)) { + pResourceHandler->Message("Skipping Chromium log rotation (lockfile exists)...\n"); + + // TODO(winter): See also ChromiumSystem::Shutdown; we should be clearing these multirun caches instead of keeping them around/reusing them (they can be >500MB EACH) + unsigned int multirunInstanceID = 0; + //while (fs::exists(cefCachePath)) { + while (fs::exists(cefCachePath) && fs::exists(cefLockFilePath)) { + multirunInstanceID++; + cefCachePath = strBaseDir + "/ChromiumCacheMultirun/" + std::to_string(multirunInstanceID); + cefLockFilePath = cefCachePath + "/lockfile"; + } + + m_MultirunCacheDir = cefCachePath; + + std::string tmpCacheMsg = "Using temporary Chromium cache to support multirun: " + m_MultirunCacheDir + "\n"; + pResourceHandler->Message(tmpCacheMsg.c_str()); + } else { + fs::copy_file(curLogPath, lastLogPath, fs::copy_options::overwrite_existing, rotateError); + + if (rotateError) { + const std::string rotateErrorMsg = "Couldn't rotate chromium.log (copy): " + rotateError.message() + "\n"; + pResourceHandler->Message(rotateErrorMsg.c_str()); + rotateError.clear(); + } - // TODO(winter): Truncate instead? - fs::remove(curLogPath, rotateError); + // TODO(winter): Truncate instead? + fs::remove(curLogPath, rotateError); - if (rotateError) { - pResourceHandler->Message("Couldn't rotate chromium.log (remove): " + rotateError.message() + "\n"); - rotateError.clear(); + if (rotateError) { + const std::string rotateErrorMsg = "Couldn't rotate chromium.log (remove): " + rotateError.message() + "\n"; + pResourceHandler->Message(rotateErrorMsg.c_str()); + rotateError.clear(); + } } CefString( &settings.log_file ).FromString( curLogPath ); // CEF 120+ requires this otherwise CEF applications will trample each other - CefString( &settings.root_cache_path ).FromString( strBaseDir + "/ChromiumCache" ); - CefString( &settings.cache_path ).FromString( strBaseDir + "/ChromiumCache" ); + CefString( &settings.root_cache_path ).FromString( cefCachePath ); + CefString( &settings.cache_path ).FromString( cefCachePath ); // Grab our Sandbox info from the "game" exe #if defined(_WIN32) && defined(CEF_USE_SANDBOX) @@ -297,6 +321,24 @@ bool ChromiumSystem::Init( const char* pBaseDir, IHtmlResourceHandler* pResource void ChromiumSystem::Shutdown() { CefShutdown(); + + // Delete temporary ChromiumCacheMultirun if it exists + // TODO(winter): For some reason CEF still hasn't released the handles it has for these files even though CefShutdown has finished and the lockfile is gone... + /* + if (!m_MultirunCacheDir.empty()) { + while (fs::exists(m_MultirunCacheDir + "/lockfile")) { + // Spin until the lockfile is released by CEF + } + + std::error_code removeTempError; + fs::remove_all(m_MultirunCacheDir, removeTempError); + + if (removeTempError) { + const std::string removeTempErrorMsg = "Couldn't remove temporary Chromium cache: " + removeTempError.message() + "\n"; + g_pHtmlResourceHandler->Message(removeTempErrorMsg.c_str()); + } + } + */ } IHtmlClient* ChromiumSystem::CreateClient( IHtmlClientListener* listener ) diff --git a/html_chromium/ChromiumSystem.h b/html_chromium/ChromiumSystem.h index 87fd0c6..b75e684 100644 --- a/html_chromium/ChromiumSystem.h +++ b/html_chromium/ChromiumSystem.h @@ -36,6 +36,8 @@ class ChromiumSystem : public IHtmlSystem base::Lock m_RequestsLock; std::vector> m_Requests; + + std::string m_MultirunCacheDir; }; // We've got a few bits of code that need to access this directly From 3e7d15f914691f409696b2d874c2703610ae8f06 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Apr 2024 02:42:07 -0400 Subject: [PATCH 78/87] - Disable sandboxing if we seem to be running in Flatpak, AppImage, or Snap on Linux - Remove enable-system-flash; Flash is dead and it doesn't do anything anymore --- chromium_process/ChromiumApp.cpp | 10 ++++++---- html_chromium/ChromiumSystem.cpp | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index d1d70bb..549addd 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -174,15 +174,17 @@ void ChromiumApp::OnBeforeCommandLineProcessing( const CefString& process_type, command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); - - // TODO: WINE/Proton/Flatpak support? - //command_line->AppendSwitch( "no-sandbox" ); #endif - command_line->AppendSwitch( "enable-system-flash" ); // This can interfere with posix signals and break Breakpad #if defined(__linux__) || defined(__APPLE__) command_line->AppendSwitch( "disable-in-process-stack-traces" ); + + // Flatpak, AppImage, and Snap break sandboxing + // TODO(winter): It's not ideal to just outright turn off sandboxing...but Steam does it too, so + if (getenv("container") || getenv("APPIMAGE") || getenv("SNAP")) { + command_line->AppendSwitch("no-sandbox"); + } #endif #ifdef __APPLE__ diff --git a/html_chromium/ChromiumSystem.cpp b/html_chromium/ChromiumSystem.cpp index 0a8639e..e160cc7 100644 --- a/html_chromium/ChromiumSystem.cpp +++ b/html_chromium/ChromiumSystem.cpp @@ -35,14 +35,16 @@ class ChromiumApp command_line->AppendSwitch( "disable-smooth-scrolling" ); #ifdef _WIN32 command_line->AppendSwitch( "enable-begin-frame-scheduling" ); - // TODO: WINE/Proton/Flatpak support? - //command_line->AppendSwitch( "no-sandbox" ); #endif - command_line->AppendSwitch( "enable-system-flash" ); - // This can interfere with posix signals and break Breakpad #ifdef __linux__ command_line->AppendSwitch( "disable-in-process-stack-traces" ); + + // Flatpak, AppImage, and Snap break sandboxing + // TODO(winter): It's not ideal to just outright turn off sandboxing...but Steam does it too, so + if (getenv("container") || getenv("APPIMAGE") || getenv("SNAP")) { + command_line->AppendSwitch("no-sandbox"); + } #endif #ifdef __APPLE__ From 4f8412d8a9498aa4427c346294d981bce2299745 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 30 Apr 2024 20:32:59 -0400 Subject: [PATCH 79/87] Remove WebSQL stuff; it doesn't exist anymore --- chromium_process/ChromiumApp.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chromium_process/ChromiumApp.cpp b/chromium_process/ChromiumApp.cpp index 549addd..f8e0b7f 100644 --- a/chromium_process/ChromiumApp.cpp +++ b/chromium_process/ChromiumApp.cpp @@ -232,15 +232,11 @@ bool ChromiumApp::OnAlreadyRunningAppRelaunch( CefRefPtr command void ChromiumApp::OnContextCreated( CefRefPtr browser, CefRefPtr frame, CefRefPtr context ) { // - // CEF3 doesn't support implementing the print dialog, so we've gotta just remove window.print. + // CEF3 only supports implementing CefPrintHandler on Linux, so we've gotta just remove window.print. // context->Enter(); { context->GetGlobal()->DeleteValue( "print" ); - - // TODO: Removing WebSQL for now - we can add it back after CEF3 has been updated - context->GetGlobal()->DeleteValue( "openDatabase" ); - } context->Exit(); From de7d49bc5be9a70a4a1ae58cc245e3676b8957ef Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 1 May 2024 17:40:35 -0400 Subject: [PATCH 80/87] Slight update to CEF 124.3.1+g6d871a1+chromium-124.0.6367.60 --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0bef2d..da69ae1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "124.3.0+g77c1e82+chromium-124.0.6367.60") +set(CEF_VERSION "124.3.1+g6d871a1+chromium-124.0.6367.60") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 08a1d8e..9bf76ee 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **124.3.0+g77c1e82+chromium-124.0.6367.60** +- **124.3.1+g6d871a1+chromium-124.0.6367.60** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From 5d238b355a334962ec0fe30dca91f101c40eb302 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Tue, 7 May 2024 20:24:37 -0400 Subject: [PATCH 81/87] Update to 124.3.5+gff7dcd8+chromium-124.0.6367.119 to fix dangling raw_ptr crashes --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da69ae1..94dad98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project(gmod_html) set_property(GLOBAL PROPERTY OS_FOLDERS ON) -set(CEF_VERSION "124.3.1+g6d871a1+chromium-124.0.6367.60") +set(CEF_VERSION "124.3.5+gff7dcd8+chromium-124.0.6367.119") if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") if("${PROJECT_ARCH}" STREQUAL "arm64") diff --git a/README.md b/README.md index 9bf76ee..2e6e667 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Everything you need is in the **Standard Distribution**. If you need to debug th ## Currently supported CEF version The current version of CEF that's supported by this library is: -- **124.3.1+g6d871a1+chromium-124.0.6367.60** +- **124.3.5+gff7dcd8+chromium-124.0.6367.119** This is not the only version that could be supported, but it's the version that's currently configured and tested to work. From 333505b8c188834f4136845099b7e134965fb218 Mon Sep 17 00:00:00 2001 From: Davilarek Date: Wed, 19 Jun 2024 23:48:47 +0000 Subject: [PATCH 82/87] Add gmod_launcher for Linux which can override namespace detection for Flatpak/Snap/AppImage (#3) * Add own gmod launcher executable with container detection * Cleanup gmod/Main.c --- CMakeLists.txt | 3 ++ gmod/CMakeLists.txt | 7 +++ gmod/Main.c | 105 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 gmod/CMakeLists.txt create mode 100644 gmod/Main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 94dad98..cf6f1f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,5 +71,8 @@ add_subdirectory(html_stub EXCLUDE_FROM_ALL) add_subdirectory(html_chromium) add_subdirectory(chromium_process) add_subdirectory(example_host EXCLUDE_FROM_ALL) +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + add_subdirectory(gmod) +endif() PRINT_CEF_CONFIG() diff --git a/gmod/CMakeLists.txt b/gmod/CMakeLists.txt new file mode 100644 index 0000000..6467f10 --- /dev/null +++ b/gmod/CMakeLists.txt @@ -0,0 +1,7 @@ +set(TARGET gmod) +set(SOURCES + Main.c) + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-dynamic") # used for exposing the "has_namespace_support" variable for dlsym +add_executable(${TARGET} ${SOURCES}) +target_link_libraries(${TARGET}) diff --git a/gmod/Main.c b/gmod/Main.c new file mode 100644 index 0000000..70fdaf9 --- /dev/null +++ b/gmod/Main.c @@ -0,0 +1,105 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#define REALPATH_BUF_SIZE 4096 +#define LIBRARY_FILENAME "launcher_client.so" +#define LIBRARY_LAUNCHER_FN "LauncherMain" +typedef int (*arbitrary)(); + +char has_namespace_support = 0x0; + +void calc_has_namespace_support() +{ + __pid_t clonedProcessPid; + __pid_t stoppedProcessPid; + int unshareSuccessOrFail; + int statusCode; + + clonedProcessPid = fork(); + if (clonedProcessPid == -1) + { + puts("fork failed... assuming unprivileged usernamespaces are disabled"); + } + else + { + if (clonedProcessPid == 0) + { + unshareSuccessOrFail = unshare(0x10000000); + if (unshareSuccessOrFail != -1) + { + exit(0); + } + fprintf(stderr, "unshare(CLONE_NEWUSER) failed, unprivileged usernamespaces are probably disabled\n"); + exit(1); + } + stoppedProcessPid = waitpid(clonedProcessPid, &statusCode, 0); + if (clonedProcessPid == stoppedProcessPid) + { + has_namespace_support = statusCode == '\0'; + return; + } + puts("waitpid failed... assuming unprivileged usernamespaces disabled"); + } + return; +} + +int main(int argc, char **argv) +{ + char realPathOut[4104]; + char *unused; + int returnValue = 0; + if (getenv("container") || getenv("APPIMAGE") || getenv("SNAP")) + { + printf("[GModCefPatch] Overriding \"has_namespace_support\"...\n"); + has_namespace_support = 1; + } + else + calc_has_namespace_support(); + memset(realPathOut, 0, REALPATH_BUF_SIZE); + realpath("/proc/self/exe", realPathOut); + unused = strrchr(realPathOut, '/'); + if (unused != (char *)0x0) + { + *unused = '\0'; + unused = strrchr(realPathOut, '/'); + if (unused != (char *)0x0) + { + *unused = '\0'; + unused = strrchr(realPathOut, '/'); + if (unused != (char *)0x0) + { + *unused = '\0'; + } + } + } + chdir(realPathOut); + void *launcherHandle = dlopen(LIBRARY_FILENAME, RTLD_NOW); + if (launcherHandle == 0) + { + char *errorMsg = dlerror(); + fprintf(stderr, "Failed to load the launcher (%s)\n", errorMsg); + returnValue = 1; + } + else + { + arbitrary launcherMainFn; + *(void **)(&launcherMainFn) = dlsym(launcherHandle, LIBRARY_LAUNCHER_FN); + if (launcherMainFn == 0x0) + { + puts("Failed to load the launcher entry proc"); + returnValue = 1; + } + else + { + returnValue = ((*launcherMainFn)(argc, argv)); + } + dlclose(launcherHandle); + } + return returnValue; +} From 1ea8fb8cc7d28beb0c8081d1cc49836bc2d8cc19 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 19 Jun 2024 20:53:26 -0400 Subject: [PATCH 83/87] - Use LauncherMain_t struct instead of writing wildly unreadable code - Fix Linux gmod_launcher build on (missing -ldl) - Fix Linux gmod_launcher not being output in INSTALL target - Fix Linux gmod_launcher code formatting - Rename CMake project gmod -> gmod_launcher - Rename Windows gmod_launcher.exe output to gmod.exe --- CMakeLists.txt | 2 +- chromium_process/CMakeLists.txt | 2 +- chromium_process/Windows.cpp | 6 +- gmod/CMakeLists.txt | 7 --- gmod/Main.c | 105 -------------------------------- gmod_launcher/CMakeLists.txt | 13 ++++ gmod_launcher/Linux.cpp | 102 +++++++++++++++++++++++++++++++ gmod_launcher/Windows.cpp | 4 ++ 8 files changed, 124 insertions(+), 117 deletions(-) delete mode 100644 gmod/CMakeLists.txt delete mode 100644 gmod/Main.c create mode 100644 gmod_launcher/CMakeLists.txt create mode 100644 gmod_launcher/Linux.cpp create mode 100644 gmod_launcher/Windows.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6f1f9..9e751f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ add_subdirectory(html_chromium) add_subdirectory(chromium_process) add_subdirectory(example_host EXCLUDE_FROM_ALL) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") - add_subdirectory(gmod) + add_subdirectory(gmod_launcher) endif() PRINT_CEF_CONFIG() diff --git a/chromium_process/CMakeLists.txt b/chromium_process/CMakeLists.txt index cb738ee..a52ab7b 100644 --- a/chromium_process/CMakeLists.txt +++ b/chromium_process/CMakeLists.txt @@ -38,7 +38,7 @@ if(OS_LINUX OR OS_WINDOWS) install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin") endif() - set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod_launcher") + set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod") target_link_libraries(${TARGET} shlwapi winmm diff --git a/chromium_process/Windows.cpp b/chromium_process/Windows.cpp index 7ff37a8..4b4d718 100644 --- a/chromium_process/Windows.cpp +++ b/chromium_process/Windows.cpp @@ -33,7 +33,7 @@ } #endif -using FuncLauncherMain = int (*)(HINSTANCE, HINSTANCE, LPSTR, int); +typedef int (*LauncherMain_t)(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow); int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { // Check if "--type=" is in the command arguments. If it is, we are a chromium subprocess. @@ -71,6 +71,6 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ // Launch GarrysMod's main function from this process. We needed this so the "main" process could provide sandbox information above. HMODULE hLauncher = LoadLibraryA("launcher.dll"); - void* mainFn = static_cast(GetProcAddress(hLauncher, "LauncherMain")); - return (static_cast(mainFn))(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + LauncherMain_t mainFn = (LauncherMain_t)(GetProcAddress(hLauncher, "LauncherMain")); + return mainFn(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } diff --git a/gmod/CMakeLists.txt b/gmod/CMakeLists.txt deleted file mode 100644 index 6467f10..0000000 --- a/gmod/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(TARGET gmod) -set(SOURCES - Main.c) - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-dynamic") # used for exposing the "has_namespace_support" variable for dlsym -add_executable(${TARGET} ${SOURCES}) -target_link_libraries(${TARGET}) diff --git a/gmod/Main.c b/gmod/Main.c deleted file mode 100644 index 70fdaf9..0000000 --- a/gmod/Main.c +++ /dev/null @@ -1,105 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#define REALPATH_BUF_SIZE 4096 -#define LIBRARY_FILENAME "launcher_client.so" -#define LIBRARY_LAUNCHER_FN "LauncherMain" -typedef int (*arbitrary)(); - -char has_namespace_support = 0x0; - -void calc_has_namespace_support() -{ - __pid_t clonedProcessPid; - __pid_t stoppedProcessPid; - int unshareSuccessOrFail; - int statusCode; - - clonedProcessPid = fork(); - if (clonedProcessPid == -1) - { - puts("fork failed... assuming unprivileged usernamespaces are disabled"); - } - else - { - if (clonedProcessPid == 0) - { - unshareSuccessOrFail = unshare(0x10000000); - if (unshareSuccessOrFail != -1) - { - exit(0); - } - fprintf(stderr, "unshare(CLONE_NEWUSER) failed, unprivileged usernamespaces are probably disabled\n"); - exit(1); - } - stoppedProcessPid = waitpid(clonedProcessPid, &statusCode, 0); - if (clonedProcessPid == stoppedProcessPid) - { - has_namespace_support = statusCode == '\0'; - return; - } - puts("waitpid failed... assuming unprivileged usernamespaces disabled"); - } - return; -} - -int main(int argc, char **argv) -{ - char realPathOut[4104]; - char *unused; - int returnValue = 0; - if (getenv("container") || getenv("APPIMAGE") || getenv("SNAP")) - { - printf("[GModCefPatch] Overriding \"has_namespace_support\"...\n"); - has_namespace_support = 1; - } - else - calc_has_namespace_support(); - memset(realPathOut, 0, REALPATH_BUF_SIZE); - realpath("/proc/self/exe", realPathOut); - unused = strrchr(realPathOut, '/'); - if (unused != (char *)0x0) - { - *unused = '\0'; - unused = strrchr(realPathOut, '/'); - if (unused != (char *)0x0) - { - *unused = '\0'; - unused = strrchr(realPathOut, '/'); - if (unused != (char *)0x0) - { - *unused = '\0'; - } - } - } - chdir(realPathOut); - void *launcherHandle = dlopen(LIBRARY_FILENAME, RTLD_NOW); - if (launcherHandle == 0) - { - char *errorMsg = dlerror(); - fprintf(stderr, "Failed to load the launcher (%s)\n", errorMsg); - returnValue = 1; - } - else - { - arbitrary launcherMainFn; - *(void **)(&launcherMainFn) = dlsym(launcherHandle, LIBRARY_LAUNCHER_FN); - if (launcherMainFn == 0x0) - { - puts("Failed to load the launcher entry proc"); - returnValue = 1; - } - else - { - returnValue = ((*launcherMainFn)(argc, argv)); - } - dlclose(launcherHandle); - } - return returnValue; -} diff --git a/gmod_launcher/CMakeLists.txt b/gmod_launcher/CMakeLists.txt new file mode 100644 index 0000000..c09d0dd --- /dev/null +++ b/gmod_launcher/CMakeLists.txt @@ -0,0 +1,13 @@ + +set(TARGET gmod_launcher) +set(SOURCES + Linux.cpp) + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-dynamic") # used for exposing the "has_namespace_support" variable for dlsym +add_executable(${TARGET} ${SOURCES}) +target_link_libraries(${TARGET} ${CMAKE_DL_LIBS}) + +install(TARGETS ${TARGET} DESTINATION "${INSTALL_OUT_DIR}/GarrysMod/bin/${CEF_PLATFORM}") +set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "$ORIGIN") +set_target_properties(${TARGET} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) +set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "gmod") diff --git a/gmod_launcher/Linux.cpp b/gmod_launcher/Linux.cpp new file mode 100644 index 0000000..d568caa --- /dev/null +++ b/gmod_launcher/Linux.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define REALPATH_BUF_SIZE 4096 + +typedef int (*LauncherMain_t)(int argc, char **argv); + +char has_namespace_support = 0x0; + +void calc_has_namespace_support() +{ + __pid_t clonedProcessPid; + __pid_t stoppedProcessPid; + int unshareSuccessOrFail; + int statusCode; + + clonedProcessPid = fork(); + if (clonedProcessPid == -1) { + puts("fork failed... assuming unprivileged usernamespaces are disabled"); + } else { + if (clonedProcessPid == 0) { + unshareSuccessOrFail = unshare(0x10000000); + + if (unshareSuccessOrFail != -1) { + exit(0); + } + + fprintf(stderr, "unshare(CLONE_NEWUSER) failed, unprivileged usernamespaces are probably disabled\n"); + exit(1); + } + + stoppedProcessPid = waitpid(clonedProcessPid, &statusCode, 0); + if (clonedProcessPid == stoppedProcessPid) { + has_namespace_support = statusCode == '\0'; + return; + } + + puts("waitpid failed... assuming unprivileged usernamespaces disabled"); + } + + return; +} + +int main(int argc, char *argv[]) +{ + char realPathOut[4104]; + char *unused; + + if (getenv("container") || getenv("APPIMAGE") || getenv("SNAP")) { + printf("Container detected, overriding \"has_namespace_support\"...\n"); + has_namespace_support = 1; + } else { + calc_has_namespace_support(); + } + + memset(realPathOut, 0, REALPATH_BUF_SIZE); + realpath("/proc/self/exe", realPathOut); + unused = strrchr(realPathOut, '/'); + + if (unused != (char *)0x0) { + *unused = '\0'; + unused = strrchr(realPathOut, '/'); + + if (unused != (char *)0x0) { + *unused = '\0'; + unused = strrchr(realPathOut, '/'); + + if (unused != (char *)0x0) { + *unused = '\0'; + } + } + } + + chdir(realPathOut); + + void *launcherHandle = dlopen("launcher_client.so", RTLD_NOW); + if (!launcherHandle) { + char *errorMsg = dlerror(); + fprintf(stderr, "Failed to load the launcher (%s)\n", errorMsg); + return 1; + } else { + LauncherMain_t launcherMainFn = (LauncherMain_t)dlsym(launcherHandle, "LauncherMain"); + + if (!launcherMainFn) { + fprintf(stderr, "Failed to load the launcher entry proc\n"); + return 1; + } else { + return launcherMainFn(argc, argv); + } + + //dlclose(launcherHandle); + } +} diff --git a/gmod_launcher/Windows.cpp b/gmod_launcher/Windows.cpp new file mode 100644 index 0000000..d23603e --- /dev/null +++ b/gmod_launcher/Windows.cpp @@ -0,0 +1,4 @@ +// +// You're looking for chromium_process/Windows.cpp +// It's there because on Windows, gmod_launcher and chromium_process are combined +// From 7e2cd773f4fa73268878feb1254780c4c1f30f5e Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 19 Jun 2024 22:10:43 -0400 Subject: [PATCH 84/87] Linux gmod_launcher: Remove unused fstream stuff --- gmod_launcher/Linux.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gmod_launcher/Linux.cpp b/gmod_launcher/Linux.cpp index d568caa..13c13c2 100644 --- a/gmod_launcher/Linux.cpp +++ b/gmod_launcher/Linux.cpp @@ -7,11 +7,7 @@ #include #include -#include -#include - #define REALPATH_BUF_SIZE 4096 - typedef int (*LauncherMain_t)(int argc, char **argv); char has_namespace_support = 0x0; From 287d4b0ecacebc297faab37aa407c03be58375ba Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 7 Aug 2024 13:58:10 -0400 Subject: [PATCH 85/87] - Fix key native_key_code being WRONG on Windows and Linux (with a super hacky workaround) - Fix key modifiers on Windows - Fix modifer key presses having the wrong windows_key_code on Windows - Add m_LastKeyEvent to ChromiumBrowser so we can correctly detect modifer up/down state changes - Added a stripped-down version of KeycodeConverter from Chromium (which is used for the native_key_code hack) --- html/html/IHtmlClient.h | 17 +- html_chromium/CMakeLists.txt | 5 + html_chromium/ChromiumBrowser.cpp | 149 +++-- html_chromium/ChromiumBrowser.h | 2 + html_chromium/DomCodeConverter.h | 178 ++++++ html_chromium/chromium/dom_code.h | 24 + html_chromium/chromium/dom_code_data.inc | 604 ++++++++++++++++++++ html_chromium/chromium/keycode_converter.cc | 73 +++ html_chromium/chromium/keycode_converter.h | 70 +++ 9 files changed, 1075 insertions(+), 47 deletions(-) create mode 100644 html_chromium/DomCodeConverter.h create mode 100644 html_chromium/chromium/dom_code.h create mode 100644 html_chromium/chromium/dom_code_data.inc create mode 100644 html_chromium/chromium/keycode_converter.cc create mode 100644 html_chromium/chromium/keycode_converter.h diff --git a/html/html/IHtmlClient.h b/html/html/IHtmlClient.h index d96527a..759a0b6 100644 --- a/html/html/IHtmlClient.h +++ b/html/html/IHtmlClient.h @@ -11,13 +11,13 @@ class IHtmlClient enum class EventModifiers : int { None = 0, - Shift = 1 << 0, - Control = 1 << 1, - Alt = 1 << 2, - LeftMouse = 1 << 3, - MiddleMouse = 1 << 4, - RightMouse = 1 << 5, - OSX_Cmd = 1 << 6, + Shift = (1 << 0), + Control = (1 << 1), + Alt = (1 << 2), + LeftMouse = (1 << 3), + MiddleMouse = (1 << 4), + RightMouse = (1 << 5), + OSX_Cmd = (1 << 6), }; struct KeyEvent @@ -34,9 +34,10 @@ class IHtmlClient union { unsigned short key_char; // Type::KeyChar - int windows_key_code; // Type::KeyDown / Type::KeyUp; + int windows_key_code; // Type::KeyDown / Type::KeyUp }; + // TODO: Fix this Facepunch! See ChromiumBrowser::SendKeyEvent #ifndef _WIN32 int native_key_code; #endif diff --git a/html_chromium/CMakeLists.txt b/html_chromium/CMakeLists.txt index 3952a2a..27dd8c5 100644 --- a/html_chromium/CMakeLists.txt +++ b/html_chromium/CMakeLists.txt @@ -3,6 +3,11 @@ set(TARGET html_chromium) set(SOURCES cef_end.h cef_start.h + chromium/dom_code_data.inc + chromium/dom_code.h + chromium/keycode_converter.cc + chromium/keycode_converter.h + DomCodeConverter.h ChromiumBrowser.cpp ChromiumBrowser.h ChromiumClient.cpp diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index 06fb65b..cc71abe 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -1,5 +1,7 @@  #include +//#include +//#include #include "ChromiumBrowser.h" @@ -9,6 +11,9 @@ #include "cef_start.h" #include "include/cef_parser.h" #include "cef_end.h" +#include "chromium/dom_code.h" +#include "chromium/keycode_converter.h" +#include "DomCodeConverter.h" #ifdef _WIN32 #include @@ -198,29 +203,36 @@ static bool JSValuesToCefList( CefRefPtr outList, const std::vecto static int GetModifiers( const IHtmlClient::EventModifiers modifiers ) { - int gameModifiers = static_cast( modifiers ); + int gameModifiers = static_cast(modifiers); int chromiumModifiers = 0; - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::Shift ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::Shift)) { chromiumModifiers |= EVENTFLAG_SHIFT_DOWN; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::Control ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::Control)) { chromiumModifiers |= EVENTFLAG_CONTROL_DOWN; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::Alt ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::Alt)) { chromiumModifiers |= EVENTFLAG_ALT_DOWN; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::LeftMouse ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::LeftMouse)) { chromiumModifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::MiddleMouse ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::MiddleMouse)) { chromiumModifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::RightMouse ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::RightMouse)) { chromiumModifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON; + } - if ( gameModifiers & static_cast( IHtmlClient::EventModifiers::OSX_Cmd ) ) + if (gameModifiers & static_cast(IHtmlClient::EventModifiers::OSX_Cmd)) { chromiumModifiers |= EVENTFLAG_COMMAND_DOWN; + } return chromiumModifiers; } @@ -286,50 +298,109 @@ void ChromiumBrowser::SetFocused( bool hasFocus ) }); } -void ChromiumBrowser::SendKeyEvent( IHtmlClient::KeyEvent keyEvent ) +// Validate against https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html +void ChromiumBrowser::SendKeyEvent(IHtmlClient::KeyEvent keyEvent) { if (m_BrowserHost == nullptr) { return; } - CefKeyEvent chromiumKeyEvent; - chromiumKeyEvent.modifiers = GetModifiers( keyEvent.modifiers ); + // TODO/BUG/HACK(winter): Getting wrong windows_key_code for modifiers for some reason... + int modifiers = GetModifiers(keyEvent.modifiers); - switch ( keyEvent.eventType ) - { - case IHtmlClient::KeyEvent::Type::KeyChar: - chromiumKeyEvent.type = KEYEVENT_CHAR; - chromiumKeyEvent.character = static_cast( keyEvent.key_char ); - chromiumKeyEvent.unmodified_character = static_cast( keyEvent.key_char ); -#ifdef __APPLE__ - chromiumKeyEvent.windows_key_code = 0; - chromiumKeyEvent.native_key_code = keyEvent.native_key_code; -#else - chromiumKeyEvent.windows_key_code = static_cast( keyEvent.key_char ); - chromiumKeyEvent.native_key_code = static_cast( keyEvent.key_char ); -#endif - break; - case IHtmlClient::KeyEvent::Type::KeyDown: - chromiumKeyEvent.type = KEYEVENT_KEYDOWN; - chromiumKeyEvent.windows_key_code = keyEvent.windows_key_code; + if ((modifiers & EVENTFLAG_SHIFT_DOWN || m_LastKeyEvent.modifiers & EVENTFLAG_SHIFT_DOWN) && keyEvent.windows_key_code == 0xA0) { + keyEvent.windows_key_code = 0x10; + } + if ((modifiers & EVENTFLAG_CONTROL_DOWN || m_LastKeyEvent.modifiers & EVENTFLAG_CONTROL_DOWN) && keyEvent.windows_key_code == 0xA2) { + keyEvent.windows_key_code = 0x11; + } + if ((modifiers & EVENTFLAG_ALT_DOWN || m_LastKeyEvent.modifiers & EVENTFLAG_ALT_DOWN) && keyEvent.windows_key_code == 0x0) { + keyEvent.windows_key_code = 0x12; + } + + // TODO/BUG(winter): IHtmlClient doesn't give us native_key_code on Windows. Facepunch needs to PROPERLY fix this! #ifndef _WIN32 - chromiumKeyEvent.native_key_code = keyEvent.native_key_code; + int native_key_code = keyEvent.native_key_code; #else - chromiumKeyEvent.native_key_code = keyEvent.windows_key_code; + int native_key_code = 0; #endif - break; - case IHtmlClient::KeyEvent::Type::KeyUp: - chromiumKeyEvent.type = KEYEVENT_KEYUP; - chromiumKeyEvent.windows_key_code = keyEvent.windows_key_code; + + // TODO/BUG(winter): IHtmlClient gives us the wrong native_key_code on Linux. Facepunch needs to PROPERLY fix this! + // HACK: Use Chromium's `DomCodeToNativeKeycode` to "generate" the native_key_code + // Will be converted back, in Chromium, with `NativeKeycodeToDomCode`. + bool lastkeydown_null = m_LastKeyEvent.character == 0x0 && m_LastKeyEvent.windows_key_code == 0x0 && m_LastKeyEvent.native_key_code == 0x0; + +#if defined(_WIN32) || defined(__linux__) + ui::DomCode domCode = WindowsKeyCodeToDomCode(keyEvent.windows_key_code, lastkeydown_null); + native_key_code = ui::KeycodeConverter::DomCodeToNativeKeycode(domCode); +#endif + + //LOG(ERROR) << "WINDOWS: " << keyEvent.windows_key_code; + //LOG(ERROR) << "NATIVE: " << native_key_code; + //LOG(ERROR) << "CHAR: " << keyEvent.key_char; + + CefKeyEvent chromiumKeyEvent; + chromiumKeyEvent.modifiers = modifiers; + + switch (keyEvent.eventType) + { + case IHtmlClient::KeyEvent::Type::KeyChar: + //LOG(ERROR) << "KEYEVENT_CHAR"; + + // BUG/HACK: If the last KeyDown was literally nothing, we'll fire a WORKING version of it before continuing + /* + if (lastkeydown_null) { + IHtmlClient::KeyEvent fakeKeyDownEvent{}; + fakeKeyDownEvent.eventType = IHtmlClient::KeyEvent::Type::KeyDown; + fakeKeyDownEvent.modifiers = keyEvent.modifiers; + fakeKeyDownEvent.key_char = keyEvent.key_char; + fakeKeyDownEvent.windows_key_code = keyEvent.windows_key_code; #ifndef _WIN32 - chromiumKeyEvent.native_key_code = keyEvent.native_key_code; + fakeKeyDownEvent.native_key_code = native_key_code; +#endif + + // TODO(winter): Key events trample each other if we effectively send them to CEF at the same time (sleep halts TID_UI!) + //SendKeyEvent(fakeKeyDownEvent); + //this_thread::sleep_for(chrono::milliseconds(100)); + } + */ + + chromiumKeyEvent.type = KEYEVENT_CHAR; + chromiumKeyEvent.character = static_cast(keyEvent.key_char); + chromiumKeyEvent.unmodified_character = static_cast(keyEvent.key_char); +#ifdef __APPLE__ + chromiumKeyEvent.windows_key_code = 0; + chromiumKeyEvent.native_key_code = native_key_code; #else - chromiumKeyEvent.native_key_code = keyEvent.windows_key_code; + chromiumKeyEvent.windows_key_code = keyEvent.windows_key_code; + chromiumKeyEvent.native_key_code = native_key_code; #endif - break; + break; + case IHtmlClient::KeyEvent::Type::KeyDown: + //LOG(ERROR) << "KEYEVENT_RAWKEYDOWN"; + chromiumKeyEvent.type = KEYEVENT_RAWKEYDOWN; // TODO: Fix repeating detection with KEYEVENT_KEYDOWN(?) + chromiumKeyEvent.windows_key_code = keyEvent.windows_key_code; + chromiumKeyEvent.native_key_code = native_key_code; + break; + case IHtmlClient::KeyEvent::Type::KeyUp: + //LOG(ERROR) << "KEYEVENT_KEYUP"; + chromiumKeyEvent.type = KEYEVENT_KEYUP; + chromiumKeyEvent.windows_key_code = keyEvent.windows_key_code; + chromiumKeyEvent.native_key_code = native_key_code; + break; } - m_BrowserHost->SendKeyEvent( chromiumKeyEvent ); + //chromiumKeyEvent.character = 0x0; + //chromiumKeyEvent.unmodified_character = 0x0; + //chromiumKeyEvent.windows_key_code = 0x0; + //chromiumKeyEvent.native_key_code = 0x0; + + m_LastKeyEvent = chromiumKeyEvent; + + // TODO: There has to be *something* to send to CEF for this to make any sense + //if (chromiumKeyEvent.character != 0x0 || chromiumKeyEvent.windows_key_code != 0x0 || chromiumKeyEvent.native_key_code != 0x0) { + m_BrowserHost->SendKeyEvent(chromiumKeyEvent); + //} } void ChromiumBrowser::SendMouseMoveEvent( IHtmlClient::MouseEvent gmodMouseEvent, bool mouseLeave ) @@ -352,7 +423,7 @@ void ChromiumBrowser::SendMouseWheelEvent( IHtmlClient::MouseEvent gmodMouseEven return; } - // Some CEF bug is fucking this up. I don't care much for worrying about it yet + // TODO(willox): Some CEF bug is fucking this up. I don't care much for worrying about it yet CefMouseEvent mouseEvent; mouseEvent.x = gmodMouseEvent.x; mouseEvent.y = gmodMouseEvent.y; diff --git a/html_chromium/ChromiumBrowser.h b/html_chromium/ChromiumBrowser.h index 4c68ea8..79dfa80 100644 --- a/html_chromium/ChromiumBrowser.h +++ b/html_chromium/ChromiumBrowser.h @@ -200,6 +200,8 @@ class ChromiumBrowser bool m_OpenLinksExternally; + CefKeyEvent m_LastKeyEvent; + private: IMPLEMENT_REFCOUNTING( ChromiumBrowser ); diff --git a/html_chromium/DomCodeConverter.h b/html_chromium/DomCodeConverter.h new file mode 100644 index 0000000..01aa98f --- /dev/null +++ b/html_chromium/DomCodeConverter.h @@ -0,0 +1,178 @@ +#ifndef DOMCODECONVERTER_H_ +#define DOMCODECONVERTER_H_ + +#include + +#include "chromium/dom_code.h" + +using namespace std; +using namespace ui; + +// WARN(winter): This might not be right all the time! Tested on Windows 10 with US Keyboard Layout with English (United States) locale and UTF-8 enabled +// TODO(winter): Incomplete! Populate with https://github.com/chromium/chromium/blob/master/ui/events/keycodes/keyboard_defines_win.h +// TODO(winter): Make LinuxToDomMap with https://github.com/chromium/chromium/blob/master/ui/events/keycodes/keyboard_codes_posix.h +map WindowsToDomMap = { + // Control (1) + {0x08, DomCode::BACKSPACE}, + {0x09, DomCode::TAB}, + {0x0D, DomCode::ENTER}, + {0x10, DomCode::SHIFT_LEFT}, + //{0x10, DomCode::SHIFT_RIGHT}, // TODO: Duplicate! + {0x11, DomCode::CONTROL_LEFT}, + //{0x11, DomCode::CONTROL_RIGHT}, // TODO: Duplicate! + {0x12, DomCode::ALT_LEFT}, + //{0x12, DomCode::ALT_RIGHT}, // TODO: Duplicate! + {0x13, DomCode::PAUSE}, + {0x14, DomCode::CAPS_LOCK}, + {0x1B, DomCode::ESCAPE}, + {0x20, DomCode::SPACE}, + + // Control (2) + {0x21, DomCode::PAGE_UP}, + {0x22, DomCode::PAGE_DOWN}, + {0x23, DomCode::END}, + {0x24, DomCode::HOME}, + {0x25, DomCode::ARROW_LEFT}, + {0x26, DomCode::ARROW_UP}, + {0x27, DomCode::ARROW_RIGHT}, + {0x28, DomCode::ARROW_DOWN}, + {0x29, DomCode::SELECT}, + {0x2A, DomCode::PRINT}, + {0x2C, DomCode::PRINT_SCREEN}, + {0x2D, DomCode::INSERT}, + {0x2E, DomCode::DEL}, + + // Digits + {0x30, DomCode::DIGIT0}, + {0x31, DomCode::DIGIT1}, + {0x32, DomCode::DIGIT2}, + {0x33, DomCode::DIGIT3}, + {0x34, DomCode::DIGIT4}, + {0x35, DomCode::DIGIT5}, + {0x36, DomCode::DIGIT6}, + {0x37, DomCode::DIGIT7}, + {0x38, DomCode::DIGIT8}, + {0x39, DomCode::DIGIT9}, + + // Symbols (2) + {0x3A, DomCode::SEMICOLON}, // : + {0x3B, DomCode::SEMICOLON}, // ; + {0x3C, DomCode::COMMA}, // < + {0x3D, DomCode::EQUAL}, // = + {0x3E, DomCode::PERIOD}, // > + {0x3F, DomCode::SLASH}, // ? + {0x40, DomCode::DIGIT2}, // @ + + // Uppercase + {0x41, DomCode::US_A}, + {0x42, DomCode::US_B}, + {0x43, DomCode::US_C}, + {0x44, DomCode::US_D}, + {0x45, DomCode::US_E}, + {0x46, DomCode::US_F}, + {0x47, DomCode::US_G}, + {0x48, DomCode::US_H}, + {0x49, DomCode::US_I}, + {0x4A, DomCode::US_J}, + {0x4B, DomCode::US_K}, + {0x4C, DomCode::US_L}, + {0x4D, DomCode::US_M}, + {0x4E, DomCode::US_N}, + {0x4F, DomCode::US_O}, + {0x50, DomCode::US_P}, + {0x51, DomCode::US_Q}, + {0x52, DomCode::US_R}, + {0x53, DomCode::US_S}, + {0x54, DomCode::US_T}, + {0x55, DomCode::US_U}, + {0x56, DomCode::US_V}, + {0x57, DomCode::US_W}, + {0x58, DomCode::US_X}, + {0x59, DomCode::US_Y}, + {0x5A, DomCode::US_Z}, + + // Control (3) + // TODO: Conflict! + //{0x5B, DomCode::META_LEFT}, + //{0x5C, DomCode::META_RIGHT}, + + // Symbols (3) + {0x5B, DomCode::BRACKET_LEFT}, // [ + {0x5C, DomCode::BACKSLASH}, // \ (backslash) + {0x5D, DomCode::BRACKET_RIGHT}, // ] + {0x5E, DomCode::DIGIT6}, // ^ + {0x5F, DomCode::MINUS}, // _ + {0x60, DomCode::BACKQUOTE}, // ` + + // Lowercase + // TODO: This makes sense according to UTF, but not Windows' VK_* defines... + {0x61, DomCode::US_A}, + {0x62, DomCode::US_B}, + {0x63, DomCode::US_C}, + {0x64, DomCode::US_D}, + {0x65, DomCode::US_E}, + {0x66, DomCode::US_F}, + {0x67, DomCode::US_G}, + {0x68, DomCode::US_H}, + {0x69, DomCode::US_I}, + {0x6A, DomCode::US_J}, + {0x6B, DomCode::US_K}, + {0x6C, DomCode::US_L}, + {0x6D, DomCode::US_M}, + {0x6E, DomCode::US_N}, + {0x6F, DomCode::US_O}, + {0x70, DomCode::US_P}, + {0x71, DomCode::US_Q}, + {0x72, DomCode::US_R}, + {0x73, DomCode::US_S}, + {0x74, DomCode::US_T}, + {0x75, DomCode::US_U}, + {0x76, DomCode::US_V}, + {0x77, DomCode::US_W}, + {0x78, DomCode::US_X}, + {0x79, DomCode::US_Y}, + {0x7A, DomCode::US_Z}, + + // Symbols (4) + {0x7B, DomCode::BRACKET_LEFT}, // { + {0x7C, DomCode::BACKSLASH}, // | + {0x7D, DomCode::BRACKET_RIGHT}, // } + {0x7E, DomCode::BACKQUOTE}, // ~ +}; + +// Conflicts with the normal map...but symbols seem to be broken in a way we can detect! +map NullKeyDownWindowsToDomMap = { + // Symbols (1) + {0x21, DomCode::DIGIT1}, // ! + {0x22, DomCode::QUOTE}, // " + {0x23, DomCode::DIGIT3}, // # + {0x24, DomCode::DIGIT4}, // $ + {0x25, DomCode::DIGIT5}, // % + {0x26, DomCode::DIGIT7}, // & + {0x27, DomCode::QUOTE}, // ' + {0x28, DomCode::DIGIT9}, // ( + {0x29, DomCode::DIGIT0}, // ) + {0x2A, DomCode::DIGIT8}, // * + {0x2B, DomCode::EQUAL}, // + + {0x2C, DomCode::COMMA}, // , + {0x2D, DomCode::MINUS}, // - + {0x2E, DomCode::PERIOD}, // . + {0x2F, DomCode::SLASH}, // / +}; + +// static? +DomCode WindowsKeyCodeToDomCode(int windows_key_code, bool lastkeydown_null) { + if (lastkeydown_null) { + if (windows_key_code > 0 && NullKeyDownWindowsToDomMap.count(windows_key_code) == 1) { + return NullKeyDownWindowsToDomMap.at(windows_key_code); + } + } else { + if (windows_key_code > 0 && WindowsToDomMap.count(windows_key_code) == 1) { + return WindowsToDomMap.at(windows_key_code); + } + } + + return DomCode::NONE; +} + +#endif // DOMCODECONVERTER_H_ diff --git a/html_chromium/chromium/dom_code.h b/html_chromium/chromium/dom_code.h new file mode 100644 index 0000000..5b95d2e --- /dev/null +++ b/html_chromium/chromium/dom_code.h @@ -0,0 +1,24 @@ +// Copied from https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/dom_code.h +// Last Updated - August 7, 2024: https://github.com/chromium/chromium/commit/c0a18161acdf82d65faf66f964154cc2aeaee6fe + +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_EVENTS_KEYCODES_DOM_DOM_CODE_H_ +#define UI_EVENTS_KEYCODES_DOM_DOM_CODE_H_ + +#include + +namespace ui { + +// Declares named values for each of the recognized DOM Code values. +#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) id = usb +#define DOM_CODE_DECLARATION enum class DomCode : uint32_t +#include "dom_code_data.inc" +#undef DOM_CODE +#undef DOM_CODE_DECLARATION + +} // namespace ui + +#endif // UI_EVENTS_KEYCODES_DOM_DOM_CODE_H_ diff --git a/html_chromium/chromium/dom_code_data.inc b/html_chromium/chromium/dom_code_data.inc new file mode 100644 index 0000000..bdf54ae --- /dev/null +++ b/html_chromium/chromium/dom_code_data.inc @@ -0,0 +1,604 @@ +// Copied from https://github.com/chromium/chromium/blob/master/ui/events/keycodes/dom/dom_code_data.inc +// Last Updated - August 7, 2024: https://github.com/chromium/chromium/commit/35bc8ada48cd36cd115d6146c48b25a0a6fbebdc + +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file has no header guard because it is explicitly intended +// to be included more than once with different definitions of the +// macros DOM_CODE and DOM_CODE_DECLARATION. + +// Data in this file was created by referencing: +// [0] USB HID Usage Tables, +// http://www.usb.org/developers/hidpage/Hut1_12v2.pdf +// [1] DOM Level 3 KeyboardEvent code Values, +// http://www.w3.org/TR/uievents-code/ +// [2] OS X +// [3] Linux and hid-input.c +// [4] USB HID to PS/2 Scan Code Translation Table +// http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf +// [5] Keyboard Scan Code Specification +// http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc + +// General notes: +// +// This table provides the definition of ui::DomCode (UI Events |code|) values +// as well as mapping between scan codes and DomCode. Some entries have no +// defined scan codes; these are present only to allow those UI Events |code| +// strings to be represented by DomCode. A few have a null code; these define +// mappings with a DomCode:: value but no |code| string, typically because +// they end up used in shortcuts but not standardized in UI Events; e.g. +// DomCode::BRIGHTNESS_UP. Commented-out entries document USB codes that are +// potentially interesting but not currently used. + +// Linux notes: +// +// All USB codes that are listed here and that are supported by the kernel +// (as of 4.2) have their evdev/xkb scan codes recorded; if an evdev/xkb +// code is 0, it is because the kernel USB driver does not handle that key. +// +// Some Linux kernel mappings for USB keys may seem counterintuitive: +// +// [L1] Although evdev 0x163 KEY_CLEAR exists, Linux does not use it +// for any USB keys. Linux maps USB 0x07009c [Keyboard Clear] and +// 0x0700d8 [Keypad Clear] to KEY_DELETE "Delete", so those codes are +// not distinguishable by applications, and UI Events "NumpadClear" +// is therefore not supported. USB 0x0700A2 [Keyboard Clear/Again] +// is not mapped by the kernel at all. +// +// [L2] 'Menu' and 'Props' naming differs between evdev and USB / UI Events. +// USB 0x010085 [System Main Menu] and USB 0x0C0040 [Menu Mode] both +// map to evdev 0x8B KEY_MENU (which has no corresponding UI Events +// |code|). USB 0x070076 [Keyboard Menu] does not map to KEY_MENU; +// it maps to evdev 0x82 KEY_PROPS, which is not the same as USB and +// UI Events "Props". USB 0x0700A3 [Props], which does correspond to +// UI Events "Props", is not mapped by the kernel. (And all of these +// are distinct from UI Events' "ContextMenu", which corresponds to +// USB 0x070065 [Keyboard Application] via evdev 0x7F KEY_COMPOSE, +// following Windows convention.) + +// Windows notes: +// +// The set of scan codes supported here may not be complete. +// +// [W1] Windows maps both USB 0x070094 [Lang5] and USB 0x070073 [F24] to the +// same scan code, 0x76. (Microsoft's defined scan codes for F13 - F24 +// appear to be the result of accidentally mapping an IBM Set 3 terminal +// keyboard, rather than an IBM Set 2 PC keyboard, through the BIOS +// 2-to-1 table.) We map 0x76 to F24 here, since Lang5 appears unused +// in practice (its declared function, Zenkaku/Hankaku switch, is +// conventionally placed on Backquote by Japanese keyboards). + +// Macintosh notes: +// +// The set of scan codes supported here may not be complete. +// +// [M1] OS X maps USB 0x070049 [Insert] as well as USB 0x070075 [Help] to +// scan code 0x72 kVK_Help. We map this to UI Events 'Insert', since +// Apple keyboards with USB 0x070049 [Insert] labelled "Help" have not +// been made since 2007. + +DOM_CODE_DECLARATION { + + // USB evdev XKB Win Mac Code + DOM_CODE(0x000000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NONE), // Invalid + + // ========================================= + // Non-USB codes + // ========================================= + + // USB evdev XKB Win Mac Code + DOM_CODE(0x000010, 0x0000, 0x0000, 0x0000, 0xffff, "Hyper", HYPER), + DOM_CODE(0x000011, 0x0000, 0x0000, 0x0000, 0xffff, "Super", SUPER), + DOM_CODE(0x000012, 0x0000, 0x0000, 0x0000, 0xffff, "Fn", FN), + DOM_CODE(0x000013, 0x0000, 0x0000, 0x0000, 0xffff, "FnLock", FN_LOCK), + DOM_CODE(0x000014, 0x0000, 0x0000, 0x0000, 0xffff, "Suspend", SUSPEND), + DOM_CODE(0x000015, 0x0000, 0x0000, 0x0000, 0xffff, "Resume", RESUME), + DOM_CODE(0x000016, 0x0000, 0x0000, 0x0000, 0xffff, "Turbo", TURBO), + // TODO(b/146683484): HID usage for privacy screen control is not yet + // approved. Once it is update the code here. + // TODO(https://crbug.com/952051): Privacy screen will not have a DOM |code| + // name defined as it is not exposed to web content. + DOM_CODE(0x000017, 0x0279, 0x0281, 0x0000, 0xffff, "PrivacyScreenToggle", + PRIVACY_SCREEN_TOGGLE), // Privacy Screen Toggle + // As with privacy screen, microphone mute toggle will not be exposed to web + // content. + DOM_CODE(0x000018, 0x00f8, 0x0100, 0x0000, 0xffff, "MicrophoneMuteToggle", + MICROPHONE_MUTE_TOGGLE), // Microphone Mute Toggle + + // ========================================= + // USB Usage Page 0x01: Generic Desktop Page + // ========================================= + + // Sleep could be encoded as USB#0c0032, but there's no corresponding WakeUp + // in the 0x0c USB page. + // USB evdev XKB Win Mac + DOM_CODE(0x010082, 0x008e, 0x0096, 0xe05f, 0xffff, "Sleep", SLEEP), // SystemSleep + DOM_CODE(0x010083, 0x008f, 0x0097, 0xe063, 0xffff, "WakeUp", WAKE_UP), + DOM_CODE(0x0100b5, 0x00e3, 0x00eb, 0x0000, 0xffff, "DisplayToggleIntExt", + DISPLAY_TOGGLE_INT_EXT), // System Display Toggle Int/Ext + + + // ========================================= + // USB Usage Page 0x07: Keyboard/Keypad Page + // ========================================= + + // TODO(garykac): + // XKB#005c ISO Level3 Shift (AltGr) + // XKB#005e <>|| + // XKB#006d Linefeed + // XKB#008a SunProps cf. USB#0700a3 CrSel/Props + // XKB#008e SunOpen + // Mac#003f kVK_Function + // Mac#000a kVK_ISO_Section (ISO keyboards only) + // Mac#0066 kVK_JIS_Eisu (USB#07008a Henkan?) + + // USB evdev XKB Win Mac + DOM_CODE(0x070000, 0x0000, 0x0000, 0x0000, 0xffff, NULL, USB_RESERVED), + DOM_CODE(0x070001, 0x0000, 0x0000, 0x00ff, 0xffff, NULL, USB_ERROR_ROLL_OVER), + DOM_CODE(0x070002, 0x0000, 0x0000, 0x00fc, 0xffff, NULL, USB_POST_FAIL), + DOM_CODE(0x070003, 0x0000, 0x0000, 0x0000, 0xffff, NULL, USB_ERROR_UNDEFINED), + DOM_CODE(0x070004, 0x001e, 0x0026, 0x001e, 0x0000, "KeyA", US_A), // aA + DOM_CODE(0x070005, 0x0030, 0x0038, 0x0030, 0x000b, "KeyB", US_B), // bB + DOM_CODE(0x070006, 0x002e, 0x0036, 0x002e, 0x0008, "KeyC", US_C), // cC + DOM_CODE(0x070007, 0x0020, 0x0028, 0x0020, 0x0002, "KeyD", US_D), // dD + + DOM_CODE(0x070008, 0x0012, 0x001a, 0x0012, 0x000e, "KeyE", US_E), // eE + DOM_CODE(0x070009, 0x0021, 0x0029, 0x0021, 0x0003, "KeyF", US_F), // fF + DOM_CODE(0x07000a, 0x0022, 0x002a, 0x0022, 0x0005, "KeyG", US_G), // gG + DOM_CODE(0x07000b, 0x0023, 0x002b, 0x0023, 0x0004, "KeyH", US_H), // hH + DOM_CODE(0x07000c, 0x0017, 0x001f, 0x0017, 0x0022, "KeyI", US_I), // iI + DOM_CODE(0x07000d, 0x0024, 0x002c, 0x0024, 0x0026, "KeyJ", US_J), // jJ + DOM_CODE(0x07000e, 0x0025, 0x002d, 0x0025, 0x0028, "KeyK", US_K), // kK + DOM_CODE(0x07000f, 0x0026, 0x002e, 0x0026, 0x0025, "KeyL", US_L), // lL + + DOM_CODE(0x070010, 0x0032, 0x003a, 0x0032, 0x002e, "KeyM", US_M), // mM + DOM_CODE(0x070011, 0x0031, 0x0039, 0x0031, 0x002d, "KeyN", US_N), // nN + DOM_CODE(0x070012, 0x0018, 0x0020, 0x0018, 0x001f, "KeyO", US_O), // oO + DOM_CODE(0x070013, 0x0019, 0x0021, 0x0019, 0x0023, "KeyP", US_P), // pP + DOM_CODE(0x070014, 0x0010, 0x0018, 0x0010, 0x000c, "KeyQ", US_Q), // qQ + DOM_CODE(0x070015, 0x0013, 0x001b, 0x0013, 0x000f, "KeyR", US_R), // rR + DOM_CODE(0x070016, 0x001f, 0x0027, 0x001f, 0x0001, "KeyS", US_S), // sS + DOM_CODE(0x070017, 0x0014, 0x001c, 0x0014, 0x0011, "KeyT", US_T), // tT + + DOM_CODE(0x070018, 0x0016, 0x001e, 0x0016, 0x0020, "KeyU", US_U), // uU + DOM_CODE(0x070019, 0x002f, 0x0037, 0x002f, 0x0009, "KeyV", US_V), // vV + DOM_CODE(0x07001a, 0x0011, 0x0019, 0x0011, 0x000d, "KeyW", US_W), // wW + DOM_CODE(0x07001b, 0x002d, 0x0035, 0x002d, 0x0007, "KeyX", US_X), // xX + DOM_CODE(0x07001c, 0x0015, 0x001d, 0x0015, 0x0010, "KeyY", US_Y), // yY + DOM_CODE(0x07001d, 0x002c, 0x0034, 0x002c, 0x0006, "KeyZ", US_Z), // zZ + DOM_CODE(0x07001e, 0x0002, 0x000a, 0x0002, 0x0012, "Digit1", DIGIT1), // 1! + DOM_CODE(0x07001f, 0x0003, 0x000b, 0x0003, 0x0013, "Digit2", DIGIT2), // 2@ + + DOM_CODE(0x070020, 0x0004, 0x000c, 0x0004, 0x0014, "Digit3", DIGIT3), // 3# + DOM_CODE(0x070021, 0x0005, 0x000d, 0x0005, 0x0015, "Digit4", DIGIT4), // 4$ + DOM_CODE(0x070022, 0x0006, 0x000e, 0x0006, 0x0017, "Digit5", DIGIT5), // 5% + DOM_CODE(0x070023, 0x0007, 0x000f, 0x0007, 0x0016, "Digit6", DIGIT6), // 6^ + DOM_CODE(0x070024, 0x0008, 0x0010, 0x0008, 0x001a, "Digit7", DIGIT7), // 7& + DOM_CODE(0x070025, 0x0009, 0x0011, 0x0009, 0x001c, "Digit8", DIGIT8), // 8* + DOM_CODE(0x070026, 0x000a, 0x0012, 0x000a, 0x0019, "Digit9", DIGIT9), // 9( + DOM_CODE(0x070027, 0x000b, 0x0013, 0x000b, 0x001d, "Digit0", DIGIT0), // 0) + + DOM_CODE(0x070028, 0x001c, 0x0024, 0x001c, 0x0024, "Enter", ENTER), + DOM_CODE(0x070029, 0x0001, 0x0009, 0x0001, 0x0035, "Escape", ESCAPE), + DOM_CODE(0x07002a, 0x000e, 0x0016, 0x000e, 0x0033, "Backspace", BACKSPACE), + DOM_CODE(0x07002b, 0x000f, 0x0017, 0x000f, 0x0030, "Tab", TAB), + DOM_CODE(0x07002c, 0x0039, 0x0041, 0x0039, 0x0031, "Space", SPACE), // Spacebar + DOM_CODE(0x07002d, 0x000c, 0x0014, 0x000c, 0x001b, "Minus", MINUS), // -_ + DOM_CODE(0x07002e, 0x000d, 0x0015, 0x000d, 0x0018, "Equal", EQUAL), // =+ + DOM_CODE(0x07002f, 0x001a, 0x0022, 0x001a, 0x0021, "BracketLeft", BRACKET_LEFT), + + DOM_CODE(0x070030, 0x001b, 0x0023, 0x001b, 0x001e, "BracketRight", BRACKET_RIGHT), + DOM_CODE(0x070031, 0x002b, 0x0033, 0x002b, 0x002a, "Backslash", BACKSLASH), // \| + // USB#070032 never appears on keyboards that have USB#070031. + // Platforms use the same scancode as for the two keys. + // Hence this code can only be generated synthetically + // (e.g. in a DOM Level 3 KeyboardEvent). + // The keycap varies on international keyboards: + // Dan: '* Dutch: <> Ger: #' UK: #~ + // TODO(garykac): Verify Mac intl keyboard. + //DOM_CODE(0x070032, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTL_HASH), + DOM_CODE(0x070033, 0x0027, 0x002f, 0x0027, 0x0029, "Semicolon", SEMICOLON), // ;: + DOM_CODE(0x070034, 0x0028, 0x0030, 0x0028, 0x0027, "Quote", QUOTE), // '" + DOM_CODE(0x070035, 0x0029, 0x0031, 0x0029, 0x0032, "Backquote", BACKQUOTE), // `~ + DOM_CODE(0x070036, 0x0033, 0x003b, 0x0033, 0x002b, "Comma", COMMA), // ,< + DOM_CODE(0x070037, 0x0034, 0x003c, 0x0034, 0x002f, "Period", PERIOD), // .> + + DOM_CODE(0x070038, 0x0035, 0x003d, 0x0035, 0x002c, "Slash", SLASH), // /? + // TODO(garykac): CapsLock requires special handling for each platform. + DOM_CODE(0x070039, 0x003a, 0x0042, 0x003a, 0x0039, "CapsLock", CAPS_LOCK), + DOM_CODE(0x07003a, 0x003b, 0x0043, 0x003b, 0x007a, "F1", F1), + DOM_CODE(0x07003b, 0x003c, 0x0044, 0x003c, 0x0078, "F2", F2), + DOM_CODE(0x07003c, 0x003d, 0x0045, 0x003d, 0x0063, "F3", F3), + DOM_CODE(0x07003d, 0x003e, 0x0046, 0x003e, 0x0076, "F4", F4), + DOM_CODE(0x07003e, 0x003f, 0x0047, 0x003f, 0x0060, "F5", F5), + DOM_CODE(0x07003f, 0x0040, 0x0048, 0x0040, 0x0061, "F6", F6), + + DOM_CODE(0x070040, 0x0041, 0x0049, 0x0041, 0x0062, "F7", F7), + DOM_CODE(0x070041, 0x0042, 0x004a, 0x0042, 0x0064, "F8", F8), + DOM_CODE(0x070042, 0x0043, 0x004b, 0x0043, 0x0065, "F9", F9), + DOM_CODE(0x070043, 0x0044, 0x004c, 0x0044, 0x006d, "F10", F10), + DOM_CODE(0x070044, 0x0057, 0x005f, 0x0057, 0x0067, "F11", F11), + DOM_CODE(0x070045, 0x0058, 0x0060, 0x0058, 0x006f, "F12", F12), + // PrintScreen is effectively F13 on Mac OS X. + DOM_CODE(0x070046, 0x0063, 0x006b, 0xe037, 0xffff, "PrintScreen", PRINT_SCREEN), + DOM_CODE(0x070047, 0x0046, 0x004e, 0x0046, 0xffff, "ScrollLock", SCROLL_LOCK), + + DOM_CODE(0x070048, 0x0077, 0x007f, 0x0045, 0xffff, "Pause", PAUSE), + // USB#0x070049 Insert, labeled "Help/Insert" on Mac -- see note M1 at top. + DOM_CODE(0x070049, 0x006e, 0x0076, 0xe052, 0x0072, "Insert", INSERT), + DOM_CODE(0x07004a, 0x0066, 0x006e, 0xe047, 0x0073, "Home", HOME), + DOM_CODE(0x07004b, 0x0068, 0x0070, 0xe049, 0x0074, "PageUp", PAGE_UP), + // Delete (Forward Delete) named DEL because DELETE conflicts with + DOM_CODE(0x07004c, 0x006f, 0x0077, 0xe053, 0x0075, "Delete", DEL), + DOM_CODE(0x07004d, 0x006b, 0x0073, 0xe04f, 0x0077, "End", END), + DOM_CODE(0x07004e, 0x006d, 0x0075, 0xe051, 0x0079, "PageDown", PAGE_DOWN), + DOM_CODE(0x07004f, 0x006a, 0x0072, 0xe04d, 0x007c, "ArrowRight", ARROW_RIGHT), + + DOM_CODE(0x070050, 0x0069, 0x0071, 0xe04b, 0x007b, "ArrowLeft", ARROW_LEFT), + DOM_CODE(0x070051, 0x006c, 0x0074, 0xe050, 0x007d, "ArrowDown", ARROW_DOWN), + DOM_CODE(0x070052, 0x0067, 0x006f, 0xe048, 0x007e, "ArrowUp", ARROW_UP), + DOM_CODE(0x070053, 0x0045, 0x004d, 0xe045, 0x0047, "NumLock", NUM_LOCK), + DOM_CODE(0x070054, 0x0062, 0x006a, 0xe035, 0x004b, "NumpadDivide", NUMPAD_DIVIDE), + DOM_CODE(0x070055, 0x0037, 0x003f, 0x0037, 0x0043, "NumpadMultiply", + NUMPAD_MULTIPLY), // Keypad_* + DOM_CODE(0x070056, 0x004a, 0x0052, 0x004a, 0x004e, "NumpadSubtract", + NUMPAD_SUBTRACT), // Keypad_- + DOM_CODE(0x070057, 0x004e, 0x0056, 0x004e, 0x0045, "NumpadAdd", NUMPAD_ADD), + + DOM_CODE(0x070058, 0x0060, 0x0068, 0xe01c, 0x004c, "NumpadEnter", NUMPAD_ENTER), + DOM_CODE(0x070059, 0x004f, 0x0057, 0x004f, 0x0053, "Numpad1", NUMPAD1), // +End + DOM_CODE(0x07005a, 0x0050, 0x0058, 0x0050, 0x0054, "Numpad2", NUMPAD2), // +Down + DOM_CODE(0x07005b, 0x0051, 0x0059, 0x0051, 0x0055, "Numpad3", NUMPAD3), // +PageDn + DOM_CODE(0x07005c, 0x004b, 0x0053, 0x004b, 0x0056, "Numpad4", NUMPAD4), // +Left + DOM_CODE(0x07005d, 0x004c, 0x0054, 0x004c, 0x0057, "Numpad5", NUMPAD5), // + DOM_CODE(0x07005e, 0x004d, 0x0055, 0x004d, 0x0058, "Numpad6", NUMPAD6), // +Right + DOM_CODE(0x07005f, 0x0047, 0x004f, 0x0047, 0x0059, "Numpad7", NUMPAD7), // +Home + + DOM_CODE(0x070060, 0x0048, 0x0050, 0x0048, 0x005b, "Numpad8", NUMPAD8), // +Up + DOM_CODE(0x070061, 0x0049, 0x0051, 0x0049, 0x005c, "Numpad9", NUMPAD9), // +PageUp + DOM_CODE(0x070062, 0x0052, 0x005a, 0x0052, 0x0052, "Numpad0", NUMPAD0), // +Insert + DOM_CODE(0x070063, 0x0053, 0x005b, 0x0053, 0x0041, "NumpadDecimal", + NUMPAD_DECIMAL), // Keypad_. Delete + // USB#070064 is not present on US keyboard. + // This key is typically located near LeftShift key. + // The keycap varies on international keyboards: + // Dan: <> Dutch: ][ Ger: <> UK: \| + DOM_CODE(0x070064, 0x0056, 0x005e, 0x0056, 0x000a, "IntlBackslash", INTL_BACKSLASH), + // USB#0x070065 Application Menu (next to RWin key) -- see note L2 at top. + DOM_CODE(0x070065, 0x007f, 0x0087, 0xe05d, 0x006e, "ContextMenu", CONTEXT_MENU), + DOM_CODE(0x070066, 0x0074, 0x007c, 0xe05e, 0xffff, "Power", POWER), + DOM_CODE(0x070067, 0x0075, 0x007d, 0x0059, 0x0051, "NumpadEqual", NUMPAD_EQUAL), + + DOM_CODE(0x070068, 0x00b7, 0x00bf, 0x0064, 0x0069, "F13", F13), + DOM_CODE(0x070069, 0x00b8, 0x00c0, 0x0065, 0x006b, "F14", F14), + DOM_CODE(0x07006a, 0x00b9, 0x00c1, 0x0066, 0x0071, "F15", F15), + DOM_CODE(0x07006b, 0x00ba, 0x00c2, 0x0067, 0x006a, "F16", F16), + DOM_CODE(0x07006c, 0x00bb, 0x00c3, 0x0068, 0x0040, "F17", F17), + DOM_CODE(0x07006d, 0x00bc, 0x00c4, 0x0069, 0x004f, "F18", F18), + DOM_CODE(0x07006e, 0x00bd, 0x00c5, 0x006a, 0x0050, "F19", F19), + DOM_CODE(0x07006f, 0x00be, 0x00c6, 0x006b, 0x005a, "F20", F20), + + DOM_CODE(0x070070, 0x00bf, 0x00c7, 0x006c, 0xffff, "F21", F21), + DOM_CODE(0x070071, 0x00c0, 0x00c8, 0x006d, 0xffff, "F22", F22), + DOM_CODE(0x070072, 0x00c1, 0x00c9, 0x006e, 0xffff, "F23", F23), + // USB#0x070073 -- see note W1 at top. + DOM_CODE(0x070073, 0x00c2, 0x00ca, 0x0076, 0xffff, "F24", F24), + DOM_CODE(0x070074, 0x0086, 0x008e, 0x0000, 0xffff, "Open", OPEN), // Execute + // USB#0x070075 Help -- see note M1 at top. + DOM_CODE(0x070075, 0x008a, 0x0092, 0xe03b, 0xffff, "Help", HELP), + // USB#0x070076 Keyboard Menu -- see note L2 at top. + //DOM_CODE(0x070076, 0x0000, 0x0000, 0x0000, 0xffff, NULL, MENU), // Menu + DOM_CODE(0x070077, 0x0084, 0x008c, 0x0000, 0xffff, "Select", SELECT), // Select + + //DOM_CODE(0x070078, 0x0080, 0x0088, 0x0000, 0xffff, NULL, STOP), // Stop + DOM_CODE(0x070079, 0x0081, 0x0089, 0x0000, 0xffff, "Again", AGAIN), // Again + DOM_CODE(0x07007a, 0x0083, 0x008b, 0xe008, 0xffff, "Undo", UNDO), + DOM_CODE(0x07007b, 0x0089, 0x0091, 0xe017, 0xffff, "Cut", CUT), + DOM_CODE(0x07007c, 0x0085, 0x008d, 0xe018, 0xffff, "Copy", COPY), + DOM_CODE(0x07007d, 0x0087, 0x008f, 0xe00a, 0xffff, "Paste", PASTE), + DOM_CODE(0x07007e, 0x0088, 0x0090, 0x0000, 0xffff, "Find", FIND), // Find + DOM_CODE(0x07007f, 0x0071, 0x0079, 0xe020, 0x004a, "AudioVolumeMute", VOLUME_MUTE), + + DOM_CODE(0x070080, 0x0073, 0x007b, 0xe030, 0x0048, "AudioVolumeUp", VOLUME_UP), + DOM_CODE(0x070081, 0x0072, 0x007a, 0xe02e, 0x0049, "AudioVolumeDown", VOLUME_DOWN), + //DOM_CODE(0x070082, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_CAPS_LOCK), + //DOM_CODE(0x070083, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_NUM_LOCK), + //DOM_CODE(0x070084, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LOCKING_SCROLL_LOCK), + DOM_CODE(0x070085, 0x0079, 0x0081, 0x007e, 0x005f, "NumpadComma", NUMPAD_COMMA), + + // International1 + // USB#070086 is used on AS/400 keyboards. Standard Keypad_= is USB#070067. + //DOM_CODE(0x070086, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_EQUAL), + // USB#070087 is used for Brazilian /? and Japanese _ 'ro'. + DOM_CODE(0x070087, 0x0059, 0x0061, 0x0073, 0x005e, "IntlRo", INTL_RO), + // International2 + // USB#070088 is used as Japanese Hiragana/Katakana key. + DOM_CODE(0x070088, 0x005d, 0x0065, 0x0070, 0xffff, "KanaMode", KANA_MODE), + // International3 + // USB#070089 is used as Japanese Yen key. + DOM_CODE(0x070089, 0x007c, 0x0084, 0x007d, 0x005d, "IntlYen", INTL_YEN), + // International4 + // USB#07008a is used as Japanese Henkan (Convert) key. + DOM_CODE(0x07008a, 0x005c, 0x0064, 0x0079, 0xffff, "Convert", CONVERT), + // International5 + // USB#07008b is used as Japanese Muhenkan (No-convert) key. + DOM_CODE(0x07008b, 0x005e, 0x0066, 0x007b, 0xffff, "NonConvert", NON_CONVERT), + //DOM_CODE(0x07008c, 0x005f, 0x0067, 0x005c, 0xffff, NULL, INTERNATIONAL6), + //DOM_CODE(0x07008d, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL7), + //DOM_CODE(0x07008e, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL8), + //DOM_CODE(0x07008f, 0x0000, 0x0000, 0x0000, 0xffff, NULL, INTERNATIONAL9), + + // LANG1 + // USB#070090 is used as Korean Hangul/English toggle key, and as the Kana key + // on the Apple Japanese keyboard. + DOM_CODE(0x070090, 0x007a, 0x0082, 0x0072, 0x0068, "Lang1", LANG1), + // LANG2 + // USB#070091 is used as Korean Hanja conversion key, and as the Eisu key on + // the Apple Japanese keyboard. + DOM_CODE(0x070091, 0x007b, 0x0083, 0x0071, 0x0066, "Lang2", LANG2), + // LANG3 + // USB#070092 is used as Japanese Katakana key. + DOM_CODE(0x070092, 0x005a, 0x0062, 0x0078, 0xffff, "Lang3", LANG3), + // LANG4 + // USB#070093 is used as Japanese Hiragana key. + DOM_CODE(0x070093, 0x005b, 0x0063, 0x0077, 0xffff, "Lang4", LANG4), + // LANG5 + // USB#070094 is used as Japanese Zenkaku/Hankaku (Fullwidth/halfwidth) key. + // Not mapped on Windows -- see note W1 at top. + DOM_CODE(0x070094, 0x0055, 0x005d, 0x0000, 0xffff, "Lang5", LANG5), + //DOM_CODE(0x070095, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG6), // LANG6 + //DOM_CODE(0x070096, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG7), // LANG7 + //DOM_CODE(0x070097, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG8), // LANG8 + //DOM_CODE(0x070098, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LANG9), // LANG9 + + //DOM_CODE(0x070099, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ALTERNATE_ERASE), + //DOM_CODE(0x07009a, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SYS_REQ), // /Attention + DOM_CODE(0x07009b, 0x0000, 0x0000, 0x0000, 0xffff, "Abort", ABORT), // Cancel + // USB#0x07009c Keyboard Clear -- see note L1 at top. + //DOM_CODE(0x07009c, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CLEAR), // Clear + //DOM_CODE(0x07009d, 0x0000, 0x0000, 0x0000, 0xffff, NULL, PRIOR), // Prior + //DOM_CODE(0x07009e, 0x0000, 0x0000, 0x0000, 0xffff, NULL, RETURN), // Return + //DOM_CODE(0x07009f, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SEPARATOR), // Separator + + //DOM_CODE(0x0700a0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, OUT), // Out + //DOM_CODE(0x0700a1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, OPER), // Oper + //DOM_CODE(0x0700a2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CLEAR_AGAIN), + // USB#0x0700a3 Props -- see note L2 at top. + DOM_CODE(0x0700a3, 0x0000, 0x0000, 0x0000, 0xffff, "Props", PROPS), // CrSel/Props + //DOM_CODE(0x0700a4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, EX_SEL), // ExSel + + //DOM_CODE(0x0700b0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_00), + //DOM_CODE(0x0700b1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_000), + //DOM_CODE(0x0700b2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, THOUSANDS_SEPARATOR), + //DOM_CODE(0x0700b3, 0x0000, 0x0000, 0x0000, 0xffff, NULL, DECIMAL_SEPARATOR), + //DOM_CODE(0x0700b4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CURRENCY_UNIT), + //DOM_CODE(0x0700b5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, CURRENCY_SUBUNIT), + DOM_CODE(0x0700b6, 0x00b3, 0x00bb, 0x0000, 0xffff, "NumpadParenLeft", + NUMPAD_PAREN_LEFT), // Keypad_( + DOM_CODE(0x0700b7, 0x00b4, 0x00bc, 0x0000, 0xffff, "NumpadParenRight", + NUMPAD_PAREN_RIGHT), // Keypad_) + + //DOM_CODE(0x0700b8, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BRACE_LEFT), + //DOM_CODE(0x0700b9, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BRACE_RIGHT), + //DOM_CODE(0x0700ba, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_TAB), + DOM_CODE(0x0700bb, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadBackspace", + NUMPAD_BACKSPACE), // Keypad_Backspace + //DOM_CODE(0x0700bc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_A), + //DOM_CODE(0x0700bd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_B), + //DOM_CODE(0x0700be, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_C), + //DOM_CODE(0x0700bf, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_D), + + //DOM_CODE(0x0700c0, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_E), + //DOM_CODE(0x0700c1, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_F), + //DOM_CODE(0x0700c2, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_XOR), + //DOM_CODE(0x0700c3, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_CARAT), + //DOM_CODE(0x0700c4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_PERCENT), + //DOM_CODE(0x0700c5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_LESS_THAN), + //DOM_CODE(0x0700c6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_GREATER_THAN), + //DOM_CODE(0x0700c7, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_AMERSAND), + + //DOM_CODE(0x0700c8, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_DOUBLE_AMPERSAND), + //DOM_CODE(0x0700c9, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_VERTICAL_BAR), + //DOM_CODE(0x0700ca, 0x0000, 0x0000, 0x0000, 0xffff, NULL, + // NUMPAD_DOUBLE_VERTICAL_BAR), // Keypad_|| + //DOM_CODE(0x0700cb, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_COLON), + //DOM_CODE(0x0700cc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_NUMBER), + //DOM_CODE(0x0700cd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_SPACE), + //DOM_CODE(0x0700ce, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_AT), + //DOM_CODE(0x0700cf, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_EXCLAMATION), + + DOM_CODE(0x0700d0, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryStore", + NUMPAD_MEMORY_STORE), // Keypad_MemoryStore + DOM_CODE(0x0700d1, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryRecall", + NUMPAD_MEMORY_RECALL), // Keypad_MemoryRecall + DOM_CODE(0x0700d2, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryClear", + NUMPAD_MEMORY_CLEAR), // Keypad_MemoryClear + DOM_CODE(0x0700d3, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemoryAdd", + NUMPAD_MEMORY_ADD), // Keypad_MemoryAdd + DOM_CODE(0x0700d4, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadMemorySubtract", + NUMPAD_MEMORY_SUBTRACT), // Keypad_MemorySubtract + //DOM_CODE(0x0700d5, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_MEMORY_MULTIPLE), + //DOM_CODE(0x0700d6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_MEMORY_DIVIDE), + DOM_CODE(0x0700d7, 0x0076, 0x007e, 0x0000, 0xffff, NULL, NUMPAD_SIGN_CHANGE), // +/- + // USB#0x0700d8 Keypad Clear -- see note L1 at top. + DOM_CODE(0x0700d8, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadClear", NUMPAD_CLEAR), + DOM_CODE(0x0700d9, 0x0000, 0x0000, 0x0000, 0xffff, "NumpadClearEntry", + NUMPAD_CLEAR_ENTRY), // Keypad_ClearEntry + //DOM_CODE(0x0700da, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_BINARY), + //DOM_CODE(0x0700db, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_OCTAL), + //DOM_CODE(0x0700dc, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_DECIMAL), + //DOM_CODE(0x0700dd, 0x0000, 0x0000, 0x0000, 0xffff, NULL, NUMPAD_HEXADECIMAL), + + // USB#0700de - #0700df are reserved. + DOM_CODE(0x0700e0, 0x001d, 0x0025, 0x001d, 0x003b, "ControlLeft", CONTROL_LEFT), + DOM_CODE(0x0700e1, 0x002a, 0x0032, 0x002a, 0x0038, "ShiftLeft", SHIFT_LEFT), + // USB#0700e2: left Alt key (Mac left Option key). + DOM_CODE(0x0700e2, 0x0038, 0x0040, 0x0038, 0x003a, "AltLeft", ALT_LEFT), + // USB#0700e3: left GUI key, e.g. Windows, Mac Command, ChromeOS Search. + DOM_CODE(0x0700e3, 0x007d, 0x0085, 0xe05b, 0x0037, "MetaLeft", META_LEFT), + DOM_CODE(0x0700e4, 0x0061, 0x0069, 0xe01d, 0x003e, "ControlRight", CONTROL_RIGHT), + DOM_CODE(0x0700e5, 0x0036, 0x003e, 0x0036, 0x003c, "ShiftRight", SHIFT_RIGHT), + // USB#0700e6: right Alt key (Mac right Option key). + DOM_CODE(0x0700e6, 0x0064, 0x006c, 0xe038, 0x003d, "AltRight", ALT_RIGHT), + // USB#0700e7: right GUI key, e.g. Windows, Mac Command, ChromeOS Search. + DOM_CODE(0x0700e7, 0x007e, 0x0086, 0xe05c, 0x0036, "MetaRight", META_RIGHT), + + // USB#0700e8 - #07ffff are reserved + + // ================================== + // USB Usage Page 0x0c: Consumer Page + // ================================== + // AL = Application Launch + // AC = Application Control + + // TODO(garykac): Many XF86 keys have multiple scancodes mapping to them. + // We need to map all of these into a canonical USB scancode without + // confusing the reverse-lookup - most likely by simply returning the first + // found match. + + // TODO(garykac): Find appropriate mappings for: + // Win#e03c Music - USB#0c0193 is AL_AVCapturePlayback + // Win#e064 Pictures + // XKB#0080 XF86LaunchA + // XKB#0099 XF86Send + // XKB#009b XF86Xfer + // XKB#009c XF86Launch1 + // XKB#009d XF86Launch2 + // XKB... remaining XF86 keys + + // KEY_BRIGHTNESS* added in Linux 3.16 + // http://www.usb.org/developers/hidpage/HUTRR41.pdf + // + // Keyboard backlight/illumination spec update. + // https://www.usb.org/sites/default/files/hutrr73_-_fn_key_and_keyboard_backlight_brightness_0.pdf + // USB evdev XKB Win Mac Code + DOM_CODE(0x0c0060, 0x0166, 0x016e, 0x0000, 0xffff, NULL, INFO), + DOM_CODE(0x0c0061, 0x0172, 0x017a, 0x0000, 0xffff, NULL, CLOSED_CAPTION_TOGGLE), + DOM_CODE(0x0c006f, 0x00e1, 0x00e9, 0x0000, 0xffff, "BrightnessUp", BRIGHTNESS_UP), + DOM_CODE(0x0c0070, 0x00e0, 0x00e8, 0x0000, 0xffff, "BrightnessDown", + BRIGHTNESS_DOWN), // Display Brightness Decrement + DOM_CODE(0x0c0072, 0x01af, 0x01b7, 0x0000, 0xffff, NULL, BRIGHTNESS_TOGGLE), + DOM_CODE(0x0c0073, 0x0250, 0x0258, 0x0000, 0xffff, NULL, BRIGHTNESS_MINIMIUM), + DOM_CODE(0x0c0074, 0x0251, 0x0259, 0x0000, 0xffff, NULL, BRIGHTNESS_MAXIMUM), + DOM_CODE(0x0c0075, 0x00f4, 0x00fc, 0x0000, 0xffff, NULL, BRIGHTNESS_AUTO), + DOM_CODE(0x0c0079, 0x00e6, 0x00ee, 0x0000, 0xffff, NULL, KBD_ILLUM_UP), + DOM_CODE(0x0c007a, 0x00e5, 0x00ed, 0x0000, 0xffff, NULL, KBD_ILLUM_DOWN), + DOM_CODE(0x0c0083, 0x0195, 0x019d, 0x0000, 0xffff, NULL, MEDIA_LAST), + DOM_CODE(0x0c008c, 0x00a9, 0x00b1, 0x0000, 0xffff, NULL, LAUNCH_PHONE), + DOM_CODE(0x0c008d, 0x016a, 0x0172, 0x0000, 0xffff, NULL, PROGRAM_GUIDE), + DOM_CODE(0x0c0094, 0x00ae, 0x00b6, 0x0000, 0xffff, NULL, EXIT), + DOM_CODE(0x0c009c, 0x019a, 0x01a2, 0x0000, 0xffff, NULL, CHANNEL_UP), + DOM_CODE(0x0c009d, 0x019b, 0x01a3, 0x0000, 0xffff, NULL, CHANNEL_DOWN), + + // USB evdev XKB Win Mac + DOM_CODE(0x0c00b0, 0x00cf, 0x00d7, 0x0000, 0xffff, "MediaPlay", MEDIA_PLAY), + DOM_CODE(0x0c00b1, 0x00c9, 0x00d1, 0x0000, 0xffff, "MediaPause", MEDIA_PAUSE), + DOM_CODE(0x0c00b2, 0x00a7, 0x00af, 0x0000, 0xffff, "MediaRecord", MEDIA_RECORD), + DOM_CODE(0x0c00b3, 0x00d0, 0x00d8, 0x0000, 0xffff, "MediaFastForward", MEDIA_FAST_FORWARD), + DOM_CODE(0x0c00b4, 0x00a8, 0x00b0, 0x0000, 0xffff, "MediaRewind", MEDIA_REWIND), + DOM_CODE(0x0c00b5, 0x00a3, 0x00ab, 0xe019, 0xffff, "MediaTrackNext", + MEDIA_TRACK_NEXT), + DOM_CODE(0x0c00b6, 0x00a5, 0x00ad, 0xe010, 0xffff, "MediaTrackPrevious", + MEDIA_TRACK_PREVIOUS), + DOM_CODE(0x0c00b7, 0x00a6, 0x00ae, 0xe024, 0xffff, "MediaStop", MEDIA_STOP), + DOM_CODE(0x0c00b8, 0x00a1, 0x00a9, 0xe02c, 0xffff, "Eject", EJECT), + DOM_CODE(0x0c00cd, 0x00a4, 0x00ac, 0xe022, 0xffff, "MediaPlayPause", + MEDIA_PLAY_PAUSE), + DOM_CODE(0x0c00cf, 0x0246, 0x024e, 0x0000, 0xffff, NULL, SPEECH_INPUT_TOGGLE), + DOM_CODE(0x0c00e5, 0x00d1, 0x00d9, 0x0000, 0xffff, NULL, BASS_BOOST), + //DOM_CODE(0x0c00e6, 0x0000, 0x0000, 0x0000, 0xffff, NULL, SURROUND_MODE), + //DOM_CODE(0x0c0150, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BALANCE_RIGHT), + //DOM_CODE(0x0c0151, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BALANCE_LEFT ), + //DOM_CODE(0x0c0152, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BASS_INCREMENT), + //DOM_CODE(0x0c0153, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_BASS_DECREMENT), + //DOM_CODE(0x0c0154, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_TREBLE_INCREMENT), + //DOM_CODE(0x0c0155, 0x0000, 0x0000, 0x0000, 0xffff, NULL, AUDIO_TREBLE_DECREMENT), + // USB#0c0183: AL Consumer Control Configuration + DOM_CODE(0x0c0183, 0x00ab, 0x00b3, 0xe06d, 0xffff, "MediaSelect", MEDIA_SELECT), + DOM_CODE(0x0c0184, 0x01a5, 0x01ad, 0x0000, 0xffff, NULL, LAUNCH_WORD_PROCESSOR), + DOM_CODE(0x0c0186, 0x01a7, 0x01af, 0x0000, 0xffff, NULL, LAUNCH_SPREADSHEET), + // USB#0x0c018a AL_EmailReader + DOM_CODE(0x0c018a, 0x009b, 0x00a3, 0xe06c, 0xffff, "LaunchMail", LAUNCH_MAIL), + // USB#0x0c018d: AL Contacts/Address Book + DOM_CODE(0x0c018d, 0x01ad, 0x01b5, 0x0000, 0xffff, NULL, LAUNCH_CONTACTS), + // USB#0x0c018e: AL Calendar/Schedule + DOM_CODE(0x0c018e, 0x018d, 0x0195, 0x0000, 0xffff, NULL, LAUNCH_CALENDAR), + // USB#0x0c018f AL Task/Project Manager + //DOM_CODE(0x0c018f, 0x0241, 0x0249, 0x0000, 0xffff, NULL, LAUNCH_TASK_MANAGER), + // USB#0x0c0190: AL Log/Journal/Timecard + //DOM_CODE(0x0c0190, 0x0242, 0x024a, 0x0000, 0xffff, NULL, LAUNCH_LOG), + // USB#0x0c0192: AL_Calculator + DOM_CODE(0x0c0192, 0x008c, 0x0094, 0xe021, 0xffff, "LaunchApp2", LAUNCH_APP2), + // USB#0c0194: My Computer (AL_LocalMachineBrowser) + DOM_CODE(0x0c0194, 0x0090, 0x0098, 0xe06b, 0xffff, "LaunchApp1", LAUNCH_APP1), + DOM_CODE(0x0c0196, 0x0096, 0x009e, 0x0000, 0xffff, NULL, LAUNCH_INTERNET_BROWSER), + DOM_CODE(0x0c019C, 0x01b1, 0x01b9, 0x0000, 0xffff, NULL, LOG_OFF), + // USB#0x0c019e: AL Terminal Lock/Screensaver + DOM_CODE(0x0c019e, 0x0098, 0x00a0, 0x0000, 0xffff, NULL, LOCK_SCREEN), + // USB#0x0c019f AL Control Panel + DOM_CODE(0x0c019f, 0x0243, 0x024b, 0x0000, 0xffff, "LaunchControlPanel", + LAUNCH_CONTROL_PANEL), + // USB#0x0c01a2: AL Select Task/Application + DOM_CODE(0x0c01a2, 0x0244, 0x024c, 0x0000, 0xffff, "SelectTask", SELECT_TASK), + // USB#0x0c01a7: AL_Documents + DOM_CODE(0x0c01a7, 0x00eb, 0x00f3, 0x0000, 0xffff, NULL, LAUNCH_DOCUMENTS), + DOM_CODE(0x0c01ab, 0x01b0, 0x01b8, 0x0000, 0xffff, NULL, SPELL_CHECK), + // USB#0x0c01ae: AL Keyboard Layout + DOM_CODE(0x0c01ae, 0x0176, 0x017e, 0x0000, 0xffff, NULL, LAUNCH_KEYBOARD_LAYOUT), + DOM_CODE(0x0c01b1, 0x0245, 0x024d, 0x0000, 0xffff, "LaunchScreenSaver", + LAUNCH_SCREEN_SAVER), // AL Screen Saver + DOM_CODE(0x0c01cb, 0x0247, 0x024f, 0x0000, 0xffff, "LaunchAssistant", + LAUNCH_ASSISTANT), // AL Context-aware desktop assistant + // USB#0c01b4: Home Directory (AL_FileBrowser) (Explorer) + //DOM_CODE(0x0c01b4, 0x0000, 0x0000, 0x0000, 0xffff, NULL, LAUNCH_FILE_BROWSER), + // USB#0x0c01b7: AL Audio Browser + DOM_CODE(0x0c01b7, 0x0188, 0x0190, 0x0000, 0xffff, NULL, LAUNCH_AUDIO_BROWSER), + // USB#0x0c0201: AC New + DOM_CODE(0x0c0201, 0x00b5, 0x00bd, 0x0000, 0xffff, NULL, NEW), + // USB#0x0c0203: AC Close + DOM_CODE(0x0c0203, 0x00ce, 0x00d6, 0x0000, 0xffff, NULL, CLOSE), + // USB#0x0c0207: AC Close + DOM_CODE(0x0c0207, 0x00ea, 0x00f2, 0x0000, 0xffff, NULL, SAVE), + // USB#0x0c0208: AC Print + DOM_CODE(0x0c0208, 0x00d2, 0x00da, 0x0000, 0xffff, NULL, PRINT), + // USB#0x0c0221: AC_Search + DOM_CODE(0x0c0221, 0x00d9, 0x00e1, 0xe065, 0xffff, "BrowserSearch", BROWSER_SEARCH), + // USB#0x0c0223: AC_Home + DOM_CODE(0x0c0223, 0x00ac, 0x00b4, 0xe032, 0xffff, "BrowserHome", BROWSER_HOME), + // USB#0x0c0224: AC_Back + DOM_CODE(0x0c0224, 0x009e, 0x00a6, 0xe06a, 0xffff, "BrowserBack", BROWSER_BACK), + // USB#0x0c0225: AC_Forward + DOM_CODE(0x0c0225, 0x009f, 0x00a7, 0xe069, 0xffff, "BrowserForward", + BROWSER_FORWARD), + // USB#0x0c0226: AC_Stop + DOM_CODE(0x0c0226, 0x0080, 0x0088, 0xe068, 0xffff, "BrowserStop", BROWSER_STOP), + // USB#0x0c0227: AC_Refresh (Reload) + DOM_CODE(0x0c0227, 0x00ad, 0x00b5, 0xe067, 0xffff, "BrowserRefresh", + BROWSER_REFRESH), + // USB#0x0c022a: AC_Bookmarks (Favorites) + DOM_CODE(0x0c022a, 0x009c, 0x00a4, 0xe066, 0xffff, "BrowserFavorites", + BROWSER_FAVORITES), + DOM_CODE(0x0c022d, 0x01a2, 0x01aa, 0x0000, 0xffff, NULL, ZOOM_IN), + DOM_CODE(0x0c022e, 0x01a3, 0x01ab, 0x0000, 0xffff, NULL, ZOOM_OUT), + // USB#0x0c0230: AC Full Screen View + //DOM_CODE(0x0c0230, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ZOOM_FULL), + // USB#0x0c0231: AC Normal View + //DOM_CODE(0x0c0231, 0x0000, 0x0000, 0x0000, 0xffff, NULL, ZOOM_NORMAL), + DOM_CODE(0x0c0232, 0x0174, 0x017c, 0x0000, 0xffff, "ZoomToggle", + ZOOM_TOGGLE), // AC View Toggle + // USB#0x0c0279: AC Redo/Repeat + DOM_CODE(0x0c0279, 0x00b6, 0x00be, 0x0000, 0xffff, NULL, REDO), + // USB#0x0c0289: AC_Reply + DOM_CODE(0x0c0289, 0x00e8, 0x00f0, 0x0000, 0xffff, "MailReply", MAIL_REPLY), + // USB#0x0c028b: AC_ForwardMsg (MailForward) + DOM_CODE(0x0c028b, 0x00e9, 0x00f1, 0x0000, 0xffff, "MailForward", MAIL_FORWARD), + // USB#0x0c028c: AC_Send + DOM_CODE(0x0c028c, 0x00e7, 0x00ef, 0x0000, 0xffff, "MailSend", MAIL_SEND), + // USB#0x0c029d: AC Next Keyboard Layout Select + DOM_CODE(0x0c029d, 0x0248, 0x0250, 0x0000, 0xffff, "KeyboardLayoutSelect", + KEYBOARD_LAYOUT_SELECT), + DOM_CODE(0x0c029f, 0x0078, 0x0080, 0x0000, 0xffff, "ShowAllWindows", + SHOW_ALL_WINDOWS), // AC Desktop Show All Windows +}; diff --git a/html_chromium/chromium/keycode_converter.cc b/html_chromium/chromium/keycode_converter.cc new file mode 100644 index 0000000..97b9d2d --- /dev/null +++ b/html_chromium/chromium/keycode_converter.cc @@ -0,0 +1,73 @@ +// A stripped down version of https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/keycode_converter.cc +// Last Updated - August 7, 2024: https://github.com/chromium/chromium/commit/24d53e3e9a861c76d0e1626522c93e371f5d9223 + +// Copyright 2013 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "keycode_converter.h" + +#include + +#include "dom_code.h" + +namespace ui { + +namespace { + +// Table of USB codes (equivalent to DomCode values), native scan codes, +// and DOM Level 3 |code| strings. +#if defined(_WIN32) +#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \ + { usb, win, code } +#elif defined(__linux__) +#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \ + { usb, xkb, code } +#elif defined(__APPLE__) +#define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \ + { usb, mac, code } +#else +#error Unsupported platform +#endif +#define DOM_CODE_DECLARATION constexpr KeycodeMapEntry kDomCodeMappings[] = +#include "dom_code_data.inc" +#undef DOM_CODE +#undef DOM_CODE_DECLARATION + +} // namespace + +// static +int KeycodeConverter::InvalidNativeKeycode() { + return kDomCodeMappings[0].native_keycode; +} + +// TODO(zijiehe): Most of the following functions can be optimized by using +// either multiple arrays or unordered_map. + +// static +int KeycodeConverter::DomCodeToNativeKeycode(DomCode code) { + return UsbKeycodeToNativeKeycode(static_cast(code)); +} + +// USB keycodes +// Note that USB keycodes are not part of any web standard. +// Please don't use USB keycodes in new code. + +// static +int KeycodeConverter::UsbKeycodeToNativeKeycode(uint32_t usb_keycode) { + // Deal with some special-cases that don't fit the 1:1 mapping. + if (usb_keycode == 0x070032) // non-US hash. + usb_keycode = 0x070031; // US backslash. +#ifdef __APPLE__ + if (usb_keycode == 0x070046) // PrintScreen. + usb_keycode = 0x070068; // F13. +#endif + + for (auto& mapping : kDomCodeMappings) { + if (mapping.usb_keycode == usb_keycode) + return mapping.native_keycode; + } + return InvalidNativeKeycode(); +} + +} // namespace ui diff --git a/html_chromium/chromium/keycode_converter.h b/html_chromium/chromium/keycode_converter.h new file mode 100644 index 0000000..103bcaf --- /dev/null +++ b/html_chromium/chromium/keycode_converter.h @@ -0,0 +1,70 @@ +// A stripped down version of https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/keycode_converter.h +// Last Updated - August 7, 2024: https://github.com/chromium/chromium/commit/36619c19f1c497cb673f1a5d1b82e20da90663df + +// Copyright 2013 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_EVENTS_KEYCODES_DOM_KEYCODE_CONVERTER_H_ +#define UI_EVENTS_KEYCODES_DOM_KEYCODE_CONVERTER_H_ + +#include +#include + +#include + +#include "dom_code.h" + +// For reference, the W3C UI Event spec is located at: +// http://www.w3.org/TR/uievents/ + +namespace ui { + +// This structure is used to define the keycode mapping table. +// It is defined here because the unittests need access to it. +typedef struct { + // USB keycode: + // Upper 16-bits: USB Usage Page. + // Lower 16-bits: USB Usage Id: Assigned ID within this usage page. + uint32_t usb_keycode; + + // Contains one of the following: + // On Linux: XKB scancode + // On Windows: Windows OEM scancode + // On Mac: Mac keycode + // On Fuchsia: 16-bit Code from the USB Keyboard Usage Page. + int native_keycode; + + // The UIEvents (aka: DOM4Events) |code| value as defined in: + // http://www.w3.org/TR/DOM-Level-3-Events-code/ + const char* code; +} KeycodeMapEntry; + +// A class to convert between the current platform's native keycode (scancode) +// and platform-neutral |code| values (as defined in the W3C UI Events +// spec (http://www.w3.org/TR/uievents/). +class KeycodeConverter { + public: + KeycodeConverter() = delete; + KeycodeConverter(const KeycodeConverter&) = delete; + KeycodeConverter& operator=(const KeycodeConverter&) = delete; + + // Return the value that identifies an invalid native keycode. + static int InvalidNativeKeycode(); + + // Convert a DomCode into a native keycode. + static int DomCodeToNativeKeycode(DomCode code); + + // The following methods relate to USB keycodes. + // Note that USB keycodes are not part of any web standard. + // Please don't use USB keycodes in new code. + + // Conversion between USB keycode and native keycode values. + // Returns the invalid value if the supplied code is not recognized, + // or has no mapping. + static int UsbKeycodeToNativeKeycode(uint32_t usb_keycode); +}; + +} // namespace ui + +#endif // UI_EVENTS_KEYCODES_DOM_KEYCODE_CONVERTER_H_ From 12c1082325eb0d129f689e3900cb07af78eab417 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Wed, 27 Nov 2024 22:51:51 -0500 Subject: [PATCH 86/87] Fix gmod_launcher icon --- chromium_process/resources/win/gmod.ico | Bin 90567 -> 370070 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chromium_process/resources/win/gmod.ico b/chromium_process/resources/win/gmod.ico index 609cf76068c3a3e87f1939f3a612cbb81242ffc7..adc36d927bd5449f2506ee5d74cdf69c50c212b4 100644 GIT binary patch literal 370070 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 owV zsysVv(|OiO;+&0pLe{9pUU?{E@DKehFS}Q^z0Vb}{^Ok*%-xy9;|D9E0tz<5c$l zO!!Yq|6lmWXBPhD2Z>8ltxFDZwYJ=U2mf9dh1?qiz`SlrtJ!7WD=&5!9jMxwF>-uL zp8l#ky5UB2Kk*5C=O)FW@!c-Ke-!rhy}#N$G}nJ0>;TQ(Zh<>joYKb!SKtd=<&LJm z)ivdRi=G1Nt%4me&)0hDFVf@xjAStQA7KxSkYia({*q?8D-TFD5t6@M=-hvVhp|HAyAq{Tlzoa|Edda6aXtG+FB%UfM71 zmDq{W7ai1zhgm_3UH{L-|0O5>Jr~-7J=e$@$3OGp2YO1ty?j8~fq0|ytdFGctPdaY zaQU9nJ?H7SQ=9c5{b-mwKz&Uk{b+>$Hh46PKSlC+0_LNSuNJR!{rBTBf(S3D^VfB6 zp_WK>R>hmwxU7xWyV{rcfPdND@v;Aff2#oo|MvZ>j+vaR{KN;{}jc-}0`tC{k|4fDdMa}=in<*9&jsFTbsoZ;h+l>ykvJdEW1ul&NU_Z?F zP48uSz7gFsMt;o6hi`QS^c=6Yo|M@A zTFm%g^89}k{#C!?^*{Z+AN&;Gxq9E>|H%(TCrB5}_f7a0?`8AC@qvfWR=QU*egT_v z8TseTsAAtWQo|<>tqVTQX4S{|{Xqu2YP`-dhoG3Cj|)cP0{8*SQEWl})~nY7GC=)B zvNo-AO*!NRSw5hy`271_(ci1=Fw3`w_4W8yE)Y42=ek|a{tvohIG8H*zwobmNX0ml zLI2Oh|4qgJFJ}EO{pq=X>^bl+ZdWybg+Km#zI#wFU!47*%s=tM^L>-AaZWKX^p50B zd*vhK`dv?sFLgcOUpTScGxnT%6)C4T(QrQ1`2@LSN8JWMS6IE! zu?PN~T*o$Wr}(@2lqx4s*G8C}=Wl`6*L&F8=zsQuWWrgO_t2kW8)GALuC<)A^2IeH zX0hUbHu`@m`+qUV|Al{Xf6Ac|{w3E7H?MV7&;1);JKR4ppkZNA`9BMUe#^_Suab{^9rJ%P@JHTeTm}rF7yj0jsNRZwQiCjR#un=&ma$mzztsEx82+(A zqw#-=UYAXoyT-8?s|D1D(TXgMPh9A9>yjt~IruK^7`N z{cq6a|MY*;CzT!t_$lfUBmAolC>ij-6#0MEZz+Z*{98{ejfhIIC=1YIsE^k zw*Q5H?CEBfdfQbI*UjI&-qjaA1pcxA*?YbQ#2@Ef02uy>H^VoN4b{7XJ3nD{=Z(9F z_p{$rR~vfIDDPyt-cvy*=@_lkqaVMN`_X^Z_rg<{E0R%hKr` zcJR097gT9^keWRvFAVF*{y*>EL$r{w`}*Gyv%N_e7Un<~K5Y0`K8~+NQ5KN&fAq$9HM>x+Guh&O*z3yu9X?a;P9OM)_16^s z74sAJJ6YYtoO{8)`1Hvz8H?-i9K&EFRvr7CkRztY!+q1wbL3x4hW}39s~sMr4LRIS zFEQ0Jsy3(u8~gB%_c3Sj7`DIsAzv3DK2Vhb^L~qd3%jrX4ZEr%`&=F~6DsLt&_o`F z@ZZLFq0gb~7Or!=kFnP}`un`x{NH55zpjyTQdPq#pIW?n9WkAPt<3m4{axTm?T^jA z(ri3&a?FiK2h8U>@8~n8UIVOw?rfKT`v=I873aSKKa*S&&QrDZR&&m|_i7;nE?CF~ z!!SqXruq72_9*u|Y#~R`x8rL+{p?un;^LxK_*vITT!UX;Huzk=S3d_!-+?$x@foT| zRo^hp-Y$a!%uKt{b=E#*`1f--{I=%wNZEMPFH{I4p(>dpsPI= zwKBPXx$D@03tx-Ztz$JE-{%@Ld9UMlyOvkJ>dN-K)n-t(1pG&^KhdMlV_o~smhcX? z6;~VY@rlgP>>4fQ4M^6tVnd8Cp4+0uW0NiGBSgGGuQz@lyy`uo=i!BOupFOpeK@mD zlq?aSq5cJ?|G|GbIat}u7U?KqjsRRpu)$0YFEjyY(m?nhaZM-hb2-WZM*k~b?&kuj z4j_epvrXBb(Y8jc9E!JH6#M?_Ut@ZW^R`?R<)kYg4KAvbKDLF}0tNJaDBOHC$8|0z zScW|J?rZe z$u*nM#U3}(oqp*vV2|=V=~DfTdVy)F-p+Du6|=@>y>O|grF!_v&hWi_!%@!*eZlp5 z)@MYo3+Ju((KS;Iylkj&9y-4upL($Etw%PGO~qAAh5uae-+&yH%^&KoLLR^*3VNO& znhyV}158HzFM;|WbdTAx9JPlP3s4VW^|jXQXU%@p?3ad}%gIks4xVP-%++GP2=jBQ zt!J-t`^fL%eQMy9>h~;nU3;%U=Q4vDUZQyqefA^z*JIA-&Z(ZZl@)sUD<4fbRyRWB2zrjc3o}CeegE2$1$sZ54}k5Yb7+MNmmH8j_i`$d zqo_P3u5qmV)z25;pYzuHs>UWijauhd_QS!-|APBd48XX6csKwa|IH`wN1jLPe`ZD@ zqr*(l#ccm8{x zX`ueq)mmU{rlt8I_8gvHrRVeadL1qGztRftr}qx45nID%WN{tM|A@$f>G}>&j(_4X z^a?J0Zj0I7)_*O6|M{-Nn%t0x#{ae6{+E8hUo`wje4)jR|I5w)UCjJ{-@%pOAKzI# z6gH~#mSzC9STAt;0jnnNh^Vz>>|F0{`Ncg(q)wBZJVfwvY$PS5UpObi4BA)VDxAfW@5$Yzw!Q@KoZ`G|yMP zcTZ=m#_w2*|FO<|5cwe&uXV(@^!s(hy4Tq}A^oj-4VNqTU2}z4>fK@Tz+{4#UG@4b zoR|84t2Rb{h}l!f1M!c6BZ$VyWcXK~&;s7S_}O&yzN#<8|10-LzCgV7e+vKFZ(;uL zV#fbOe>`u8YA4SP z_CB}d4>mb+q}F7M*Y$N^y_UIKRq9Cv#;cfDQcmu25q0#1^w=(7c4ZDdcJucAn#;TI zlkViV|J)Tm_K&U3JtQq$W z!fYXN1nLzgSs>d>@SriQ|9if5 z+y{_P6Iqk_I}7nxc>i#W=HxCn{+De0|8(EOc+8A_sp~ZtOZ;e9;?6ti9bqvz9#LsW-0*Y;_x0THeN4F}^aa4In^0)b& z<2g2Ofa556c*AF_-9Sf<>#cl>xW_%N>?i-|jvx3P`Vs%4JHd?9VrJyjQKwJ?cVDNO zqs&j$95v0%YQ>%rzi&MYWDCF*bg_DgH>!V@u1hrj<^OALaq;ut;2h|K5W&CBkyDMs zjW>n=Z>C>)PTJKG{PP+4ek9NB`{Z+ke2IX&>Gs%s{w}7zR~O8*SYSs-@?Jp0)p*wEHjOI(^38dZ69*wgAWrhXf`Z;x?5 z)V+_A2RX_ttneE4dc?wqsfQb74pDFGao3P@hb#KQt?u})TZoTbhfG>)bv!ljo8nFN z8N{Wu!tVteNS|A|46?1HLltYV8f-pu3jed)ud(|$;65BL5C6%}|Hodp@N@73E;?e@ zh38z#G0ew^J{r$}Z0XQ~YY>00{A0}?QJhXoeMrTzNsl&czXE;pMwf@reR#{;T;@H0 z=*pkp;ri+hkssRx&V_X_4&Fyu%9T^zqH5SRBiL%$!L#xTwS0}Sd}--Y;oPt2`;r6d zL8w0bs(sf|3_kSb*K24V(h%<@`$2sY)TeN;r@}Sle#_;3`&O5|{cXlMl+rU(y?x{x zv|tak?p}r+K<=E`JIec$OaT9UZq3#S{C_U~h4jDbe<$?6^l%*1zut68{qOC6TTVH&79d^EI;52)<6dyB>T7;v_ zo;$Mh*WBsH{?+x=JWVg^O0d|Cy*^}l!t#lQP2p8KTs_1_g-K+B`WHzaD2GP2v+C$Z z*~->uNLyO^p1tjtpS@|=2V#1Tp08PWTA>G$w;hlNypP^*6d9wV*&E0r%b6T*b7vY} zab?eZ#T~;Y$i*HgL23mUNnns?Fz9Gd09{%iRB74U!ho8tfYhT&iF zf25F|PvYT!Y1seCmj64Q{`P(CSJL19!qsE(0>unsm+!gnj z;A8dj@xi^~@^GqEV7@|g^5EGsGj4RR-1+;i;^;kYu(tv%s~?eiQG+{r(w)~lFl>A2 zboD7wuYxi9hK^|Q*v0?EpsR*KwT0?s6sczb<5pM8BYKAZ?|;X8SnpG0iTHOgDD3+f zJTk>(jO2`ZB#W1i$Qbo692s!~%+MY^*YAdU@?7$u_y3>jItG0odz@P5PhdXC zfbIk2g&4o>c?8+}s&i3}t@=hbqxY1jqq_b&@_wq}&Pvdc$J1|iC+_`y*OGT1eelY_ z)_~z&xRXxSlCDP#*b-a~wT5H)^@^1VKmk{7nRk^V~ddY>DFbJxsY=~}b9%~qceTrS*ej?zPW zSe)Uxvu=pbS^j()v;W>sZ)45>Scc6n`)4h+5bIqbc}gcYM&n<$L<;{??Z4Q^gWVsF zb7B8arT-Uq{?Fz4R~KFHkT-;lZ?Qvu_P)OFFCu%tjhz0r-Rj|w&cnwqWv}O9>zDrU zuU%i$3;67PU_-Med^Vq-gw12}Z@_iezvN!p^?K_H&(dAU!yO=)877%wuSul znA>xk`PHVwe{$IWGwJ`S{QsrI|BuhR7sp3TUrRNE9sAc%PTOe}S?E1>)&$$*iOKG#a$wU;-&%N_gLO84?t;R2t&&HR7M zIf4rrk2fy(wVMwAi&y`r`2X4Y{1@V1T%X5(&jI@L5$98VZavtqp$@iU`)XIZWgR)N z)YIPmo35|pCQ=Xvm1A1Bu+=&;z z3-0~=1#ti-E8=qPrqln)VgFw)|G$*{|4wZFc4R<1_*d^9#XTF){pz<_L(Q6Ud9&~T z12@o>gAT)P1^cPnf=5>)D|mxGSk?U+oU>{3X{_@r=dy`l!KIEucl{53D`fe z`DRy_bqBtFpY2tPWt-lLhptKv(E-)Zv_JX~u_Q(Pm5kT*S3#X{p#6MO09YhTz}Ey|LVp@n&^8o6q!{o4of-f$$`aqdi_#5vzrT$M2`#;71N8A6QpO^CdeJ#Ic?P(T}^uB6(D(O*` z#e9v9(#P43(p19H#LkSmJMmz=@9Ct3CC=4kl;O(XAAxThCO2`|{BFhC^m%;Fy~XAD z+5ZjkOrE1$Cckt)s}Q-7a4tMkmy2;7mVWjzH}xNx z_$U4Db+O{~)bg{2_`4u8Mv);2Y4g15uVyLY|3d$Nsqz2v`<3HseLJ!H{d}4$=vAe8 zdaGUj{$F>4#QBHt`4Cx`u?;{PY?f8|RkzfXE!J$u7k?Ydo8 z!1t5imv)0|IdPD>)m}G5tWSBVi{a+=x352t9#=k%)!xEe43e)fGD?qediM{VEoYuk zt~=ZKitEC^=_+~L{G8sJ=iNYCrW@!zMGu2!IFB>f0L&p!-mc+a9SbB4Wj_cr;i~A) z_z&U1MRe3>nX3P9a@haEe+_dSE1CUVM!kA*`i1&`D*i7S`G3{*N$(5)s_WCdUG?i# zf7(KF{Ic%+q#GTovpuhTRW|z~*~{|$J^rQJN0{w*ZnV)b-BR$dJN?90={^3(?%2-v z(WmH5?nK)4^y9gfUcA@3tWESMM((|mb~CdE-|4as{;oUqy}xmFM|Zo9@JGn$Lmm&jt$( zxes9L4xu~c4|}^fu{N{!-^G`Edp~e|=zH|D%@ja>wG@8G<$d>a?!@-@@wu*H?l&`c zZPqs&2l>op%=9dyo~c}Yx}_Jam&sMu$4s*diqfxg`TR{LJZR=UzwatvN_T^uIlR8| zAbh=!WTW{E;NPSG|C5E_Ed@QY7;c>Bl=x2$`~PzJ|76ntKGv_=e%b%T`-t~9kmskF zbk*2K#Ti%9yZ#TDyH^LUyuYixop3k#^T?%P&T$C4-1_Ws9H8gs5a(W#eV@y`<9FQg zjn^?NoIWLBUihvA=T&^p8hRDhYM!?iaxW~+_?}qw?dSOw{9Osxr4U(=w`q+#vF$ys z_^~h3^B|WT=pK9o{GV}tiuKF( z2mh_NYkvJo@DDE7>nAg=cg;EXf-Poquz#(0688U$wnWdzec@j(-Vz`^15dx}m-@{9N#)*?Z{MLGfOb?wR;KC~i-& zJoCkMp8fT%jCmV}x4w(n+?uZ?E`!=7X6`hQ%T%w$JkAE2$w`hAd`Anti#3~5vx&!- z`kC`PTl~IpTt;YipSKfg>3>qe%;M6FRoDacLAd9STxabeu9ap4WA#UTChLVDS+FR$ zlj-oE9QJ>T|5vUbF+cQwEB!oMZl{+Iy1$9O{pI+*+4S=%e<2N@|15U2{AuZb*|F%g z_(6TrcUJE4Ae{a%_I`g?F7vqm5}kalD+l{ETajn7@v&)y`BrKe+gX}TB;2<$k6Ss; z9bs`DIwDK84-?BjuZ8F9Z*8709I57(SJL}XITBTx=dtk``X0a6)n(srInpB|^gQSM z6gAbn1K9+LF9W8-|Khd(qxJvg^8d+H{~z$*!9EubgpGQ1e*s+Ap^SI9GcCu_W9(lr zui7+mTsE&`QWVDLL19UC$796i`Wjz$$L{)FmxnH{+Coo4YL(QZNEmGd`z^>f**IA@Qb2nocLank^@{TWQXKO?XI=f3#ksjKpa9jb3RLm zB^^$Gn=5(l8^jmuxQ5IDvTG>p$LBiD?p>1~|1NMY9v#-Ug$$vf1F@86J$1k6+D?EXHZ0(-J!QV05gYRxu534t_dgyUH+3F=X%4VJMJnMso%v60u=l*5bPP`8`K?C#7 z>UJ%2#WvS-nLE1aCYS%jCVG9h5ry<|$RGgpDa2cNc@REV{I2YU@2nG>D*l%o_Wx4E z{~7xE_1?jLzeBzJeGh-d{M+}ffcIO)ocgQa$F8D}@71n3_a2ZotX*nbLpwCyH+dc= zgQQRKt%vBJsXCl%(74nDbjh9r9B$HW01mzEmj7o-us=s(& z;qd;`;XgU-|C#WgjQszZJFbkvKYlK@u5_5wS zhbX5&w(A&0AJS(f7fQXSO2mr$#Igt#->M?Uk>iW z-w6L&`dKnSuiYlQLOqi33F@&qDt4^4{DD$9h$9LPO&9Ak5UtwQ7H~O-9#}(GoFYn*~!krswVUJ4pCxYhpdP(|UdAFJ! zs{H#AbZY+hKg;awYly`WuS4%Q!(VD9f&BBfy%TnR@Z){H6E;5Bor@K0fpHlid1|}> z+#~(b>$Wa;wOg)m$I{>I>T@1|KRII@YN$(4Y|wHl66Z4e?>x!v|C#u|q{KhHe67we zGRwpEtm!Oj{ADkL|6F+g%0s)&r}nmg;%rRS>!?TGpyp=KGrQ(Lz6SO+OM5kWI*Q|q z*I!|^i{kc-)1!x>>1)+DMwYI@Twve7M&xx>H;rx49`>s`93o-KJ zo8RiXYF;4Tk6ubN{SVi7jy#&t(LUE!{1bP4%iHkx$>Rh2>Q5#PL-DzGVtF3>;QYW! z*LzS4?jy2#>^jVi{?}`InW|Xogbe6J{g36f}T6yp(?I=`xS7JR1v_qSR|>R&X+$DQxD+%=E?cx+7R#q-qZgLjtk{$OAI zH%8C(xZ)?jOnn|cKlwJ*#QGYEyXkXSJTBP%@Nzv`bL8jG=5@p|bRYxNLrL~kBleZ@ z19YyX#3!;gzuEi%%}7z~Qn}Ug0TSmn9sZNU{+~(zC!_yA75>dem5-Ws6*X;J;G>w^ z3&$3>JTvRpp$|J5p|cSHsKh#k5;oDd$>6o?3-}P#pV^i7Y6O~ z#sUA56qEuGm3NHpm+ybB*=2wG3odu#)vlhr z-g@$SYsce!%46_;jpmdKgYm?_&cXXwvH|e1s3&TGqo`AkAThzr^tZT{;va*5=Kram zP(0JBF8Yd-AOAD)f60u0+m{jBUo(AF3s-?IJ-q4dZlot4ebn#!+g!9S7Ulu<*Yxo* z%@iW{a74ND4K{Q+m^!lk1N6tiCLJ3vo-JPbUlzab(}`}xHWvOhZ=mQ0|AU?# z>x}cWSRZ(5rAKiqe4li`d~T2Z#K6DF0L272w|2z?$gyg|22lNQ>9%DqGvg-LhP^dL z-BJqwm(Rxr+=t^-_@7Pv|5W&2%=!NT|3UvN=U@1*z>Y3O9~IpHN7$jg*#Fe8gS&X) zUz$+dgX#^3(TyYY>O0f?k~^~Xms|-vdgZpY;E0?hZ2wkte;ayV_!rI$|Hy%O>wjJA zKCmwi(%S%vOHy+P4^X4rK)ApHu6fS4ZY3|Um(Lrot38=bm;CnsO#I(e_+QNWzeoC! zJ*oXDzN{Ji=sGBVUIH(b^OG;K5Bt&o(!bKe@wrJC3J=PQ7dLi(te0B4eB{WMO>=% z#Z7EI^{vnrj2|IoJ#*N9;>J|xGdfi5UfTT;%k!(Ewyuu6+Q!}3{g&VBu=S{0w0qJAIiTvP77Cii8E%Kwhn&EuauKjMA;^z~>heH6Z&+CK22eth-l{${X$ zf$mQLogXcCqVaD$Ae^Lp6rH!?mFlsk&r(h=!ee*;A@vpwhIQ$CUHJ)HARgaC^5cId z{x2EvZ??br$-Y)iJu&K}hww#@Y`Vdc;-xWRfuk`69 zM;Y5+y*$*PxP_RS;_K3_iDL6lhkx-B;wM{fr`HmGfcg@sW}$A&a(8UYJE>uQ3GQXk zVu7RuNAR!u%XrvfO9TJvaXVH2|K!HM?0@oR*n{;E{2!&qeQ(QAbgn6XcAfI>;&b!d zpXO}A|Mge9WBWf2KS@6i@UOmmiutJrkMH539N+ll`9=_2*PPO*4yn z=vyqT#o{mCKTn2#>HpS!^j#tL+K3HPxnnIdU^zX6=_UUCzb6jR2L7>mxbVt>u>4Dj zfd#*%@SlwM7yqxixOU>@>Ro4fzvTXu;a4Bq|66Wkpw{yKte38_PYb^B{2QY8HD^;j zbQ_QCpdS%2|1I?PP`(d!%Ie>z`rdBvuQ*#G>VIEje4$<9c|f0gVZ8&fWvcK2idA=U z;3M=}uEhSA&*S4B@v8|);XfJe|A7BC@UI>fvZbrZ{nb2+?EC+OUX`sr=5De7cxit` zfb4$p{^TMQ{qS#X23ZBQi48mH?Lq8J_Ot41C-8syxg!f{Cye(BvD_;^cR({oq-%~a zQ>dru1o=+B9>98<;)96^Som8C|4Rw~#q|6t{n6h6yDs=Y@@>FhJo8cX-kQ^?o_Qzk z_tLHdHCE1U;o4p3lD$E zoFt6@N5}t`BLAH9zZYd$v2e z^*!iEaDtuOpc;ShZ@qmYIX~Y1UmW^hwuWMX%2V`p!G6}7W(le1YT3`yxc}Q4uk2;NF{MKIH~AkAz2ot(T`&Ci)I8;m zZM@l)V;`$-M)6APyDTeiZOe>_X`u0VHy>HReU$r^i z)tsaCC$*ek#r>mle(CSolYsI6sd&Xuf1%t(i@^}jw7x*Z0JHYK58O9f&4c={_&mr& z%l}y#{lAit{}cXg-;q0`S$NgpziiubcXZ2J;GU0zcj2Fo)WSdej|bube_0>+B4^y` zAAZ4QDK~~$8V$t#n!vyH_!5u7@)%R>Kk?%KGvQw|*T(U$`b+wkA5MEKdBFA7d&%-I z&Hqm{{4d`6pV{dDl5zWV6)dZR{KNLiDSI{ zf8+nowYy{6e%Y15{mVaAy-XYDEd0y%PX_y6HB15jszb?6zrpo197E@i@K5?*IE+Ug z)^zw^8uov3@_zyU>J2)r#oaX;)53u5JQ|-&S;U<^0?VYZ2 z%L?LS_$%m1;oow9k`e#-I5`k5BN-!Jg5d^63Ymd_c_{vYaqG1gZF{@-E!zN$G- z<^ILuzdM5c#fN`izZCd?%fq6d`3Z6z8cUwyyI}r;;a~oLO8>{o(Ab}Y-VevQ(Em#X z|ApYc;;H|GnkVm+e?In4?mhS8cbmX}88!darvIbxZ}ET4-He{;ccHBlyGIh`XRrS+ z#=mAS9%GJiQ|Z&=_%|7x!vBo5-2wOEczO80F#j(Z@vpoY_00(SzY@KAioDZ0<(<=i zPW#QzqVzd{@x=eRi2k1x|MLII`AH`HS8DbuJnz3`u(w#GGQ?g#tB#`&xEZ;U)3-+v|!TTgGvIRyNx-|t2CeU}8m3;T(N{}lgkIq1aEtp5~xDA*gp{s_lJu{pzeOosp5O>13!)?MV= zlKT(;AMhW)-V?F-r|(y+{U71~yALi_{_kS@zk(QNX8O(G|0swbHvG#6^D;m}aUpNh z>Hie|7cc(d&((Vl8^Ac{t<;=sTJP!#9%kN$^gq12>3?j1xagh89!K|y|3AJ3{tsLT z|LU2q>mJMh``q6}l>bZpulg1!jv4qrpZ~Mcoyd3-vjB3r2(rJp266iqro;cz=KmM- z{Etq08YbR+O-AWMenQjPc|pEH~)_({;l`TP$PYD-(hodYVlLl_eXtm zFT{Vy{a-<@VIk?`L#j(_aW2>(A}`!9C=ztWY^ zY4L|9?sP-|!zV-pL!fsy#S&uG{6@`EllC zuXT0A01W@i{}b+g2Aer!gnmp{{2Y(0bs}2MCr_{d`{%6en zQEV&N{oyzl_J4BX-_M{H|6iw^bNGMtoizSmoD+7Z=Kw)JDj;4r?fubnef0kR9J4Xj z!2eUTNWOyja`|zg|BrI%toM(O2{!+4vh&gT+AKhPQlEz<{*O68@PER8!S|`dVAiqf zutt%A@$11no&HY_`+uqC|F=i#f7Sb6`TzOp#Q(GJBK{}*qj5|I==+bK0PyE=))hSU z6?*@#wi!8^RnkHYfW=-z{?GXQk4xu@Cd1`IJ!bgFZV@MCwLj!x)kXS$7SiXSKzZ2o zU{>!h)nbJ{;0rH@r^El!u>X@W|JT?5`TGBW{{r&=n~v^g&-vbeJ_jIvwg1}d9{>HW z^6*}lmv#;Mak;DCAr5fy<6qxFH2&59zmnb`xy(3fIL-_}>M-;jjG7#lJr)l+oWg&y z;osMtOw|8Q;D3#)ef~D~*?{rR+IPl1$7SDnO=tZF$VYB3{(;Nfcs=>QE13Ny{KxkH z4)ebjkNy|_HNU`S0HgnFz<)XTKSAzqN9j+%K6M!S4iXms$zlI5CH~+0otl?a11HVYENgD5pZmS|`u}?wtavo| zkIerO{x$z6bMrf054rzi_+cUb=ed}8d=E=e|6f@DmrVTMboeiZcglPC)8Jpc)3C?B zaZmBscM*xB+*kBpe|Ld9vFih_oE|{xRVWT{vEyGd*yEphCde~!f3h{R_I!}}pJnhc zOpv;)iyS>Cz)Z_ho)M+RsH znP%XQ3^%)zcYl)kKblj(yc}|6r8m{XT%Sc;dQVsh#{a7}$uEDN#{0upcI;bivrF|k z#A(*j`@1M(mCJeXPu%E8ON95s2E+dfYvJv%>F}Q%_Wx4k|22C;b8}mXqiL3o^_`*a zJm-#IcSC0z%=a8s?e8e|HyFeySop2SJ6ip*&A~qFN`L$hu8i3jW!u*>|JQSunqL@v zfSv=Zy$nb={I7D|cd%6NV+-6ff4z0zTGy?7RAgDr4rZEfzk(RdDp&aA*UiuV6a-r}%%1K`9SSz37PPh86M;!f~PEN|*XRQ{n%}cJ=>1js92PIpTmo zUp?sKg@5r7!_2=L#vZB9yxnRP)d!%Fp3Ry8sJ_|323U0XzkQAC+{+wO)&1~*O2=VHW&66c8@rJ{ZkHBT;Q*FMGrLR-e>tCmE>R7Gk>L?`3vowd*?p(KD8v; z_hGN=c&YnvuAdiu4!R%ksADSsf8u+Y!2iVj&kF3%qnqEvtj*`azVACZCjE~O8ZZ3g z0fT+IU=Wk+seQ)1lJPeBfHMDQhvtBh2S^=I=mWf{@K4@CE51ty7?ST_3HPVjpD%8H zx9e>^ZZ?;6zmNHO84w={FdhDr!~S1N{C|+Q0sra&YB_-TpXxi6z4=<#oP8hjKYibc z^BA4+i935+SA8$khjau(WB^X=r9B^I4gfR2;L2OQy{z5@F{g8w#rf5l%b;WSnE^U586;6|x;mcEzN)$%uK|9Dsc)8Ri^_5Y>u z|BJc)KN|l{*wB^(0H%~cteMkf;g z{%~RHOW2TeH-6}9d;n^JcdWEoK+2z0U3V9s$Kn8qh=17t#t-?NzfgzXwi{Un{)>@k zC&~NkEPDjsAKoQ`|Df@`2M~|%VLJRLhy8!K{QqLt|H4ZLOYtZ9(skgec*{z6^5Ac{ z;r?3op}NnZdE&_bkpYJs0{d2zfDh1<>ke&sH*r8}W$>44$pw`E(ti6&?CO;sV`gW| z*1m8B*|+e=P@C-eVewF^T~zF)WA_!N`%jU3owfT{+}L0ZoGWPO+9$?sI{YtQ`+qk2 zKiTqswg0961O8=)HepAL2eJIq&F^CN+!6Mm&pD4@?HRRy5&K`g2)!KWa3`5jbUH)5 zfT;ygEgR%@^W&AHW?Ej_6|BEL#4gfn~0{_&X zU{jxl11x_0e`5EOgM!{w{b@WrPe~lv!Q#UFJTiQNQKZtDVt4rVkHb~2bv5+LY{70( z483{JYOtkzz{&G4<^so;^BR&v();qY)U!~0g>o0fw#7*Nc)v+LQgR{AXL-iH+A;K487c0Bjh10GkaUJAlt6U!Vuy+pmkU8H?3d z^Rs3T_h}L5>&52xG8XLbTVr*8owqM%Zt)6>Syo}QRBl~HJ~MN^Pd+X;b?D zA{lzA-%Bj#GZp{0)cOB_|Bwfu96ISH)uCy|UNOG)$%j9OKI%gMD+?#FZs&;y4)s;I zqqlz)e{HoZB_BX}G>T(1pWxqe;b$SXF{LcC8s{wlVw zc12sRa@lF?UFLnCa_5FB;r)j_zZr?SB+k{IPX8x||38!dPsaFPH2%E}01u!z05-Jp z0IJ|Zj_>?cceX3X^~?WHFae-`80W?YTvP7D?nK&~;BCaG`FNo60aO(0PI42(vDWWmzB2g=m2j*D$gH1jevfOdd>X&zEchqBiGSl~ zF2;*(nM~?ThyUcT|Cb{FZ}>M{C?DO|(1v;7weX?Uss&~SbxZEuZg7-7bP0xk<;a~! zXPz7Ga%GSGv*pfLQ5U2_^D6@+XLLPP-`jpW@d(aAoJ8Gj z@f53FBX)l^=b4p$y(@nq4L+lX`bOnA`93G{>+>@i_Vw6w_)iY~zZCiZN%4=ql#N;o zmtMJ*9`xj+7JdKIZe*lA;p~6S1y@d>W)%qqASXYhj$&Kq>_CvS2(Sb^2nE2H(Y8;j4 zEIUiGAd!S^^5g$v`#<^Pf8Iy+c5&bU)el^=DC_C1Td{SWtHegl-t}HL&{b%hw05QE z0X-Kt!O2afJLd0k^(GurO#t{AqgK9)e8pqC-bXB5J#?{OumdCm6w{C$pq!iWTJ^vQ z$WIqfHy10S_btXB!F~(x-9((PaVI^?i1lf%-)Zvvj*`1@raj-sRp9+bB*V<_A2R!2 zpDl52)8Rik?Ej1LpS=CQCfn;)mx6BWB!9jQU$xQvRQ4_XXijXn(N!KhK>TmO>nL=t zI?@RHV~WScNmnUFPWiy+sZ+EWLExyn;>YgT_FIqvSK$w=hQlBpK(Fv-IJef_;y1|G z0GloJ-Do4`DQ-}FUH4>m0eF^-j9#*twDdPU`WtvX%cGRNkDuXnzVeWKZhtd5N$OQv z4))cvpaAS2+4C{iQ~v_z%z2OJ`G)99I3^DImxBF26aJHt|BIFb0spF>>DWh2?Jnl* zftkh~%hC07_7IkXV1AIasiZ!5AZB6H(q%G5nZz* z%iQ@PSBURYqh6up)+(o|k)Acu$I20G-b;*7J^}Vl7oS)BoNNQj8$>3AW4G*t$m-!3 zmL3zQ-^t&5Ti@p$H0)lBoZ)YFUE$hyFT>_X-)~vZg72ed@z~yvxvshw-QY;S`7OS# zDv_{0*||)o|C7W2PvPJ5Vpc!LzHVV3*I@(HW4BhKLl33DnR=j?v9+-Q=*1$1Y;ZWliK zztI29ZfwNY14#F(E+<|)SQ!Z;@NV6mMR7glAJiyz zN?l?FdF1)Rea1Un+2jA88$DahxoVyVoCT88dYZ_HN%-4z_)iY~KNJ3^%Ku%=`G3*& z{{;S(Gvn=I=|t`A8v24)&=)*&&->v4k5QYh{9ARS@%WF|4i^4>Uaa^Or0F^ZB8>oT%Skv9v-g&zdkod`8i>ke2|QYS>p7Rzi&Oe z!Mu8>R}yz9*|LuQ32VsdUCpeb8{FXo|J5}We&3xN(Hw8hQ((zw;p$0#>MADwEjjT& zoBY42@V}_*e_}Xqum6da?O%icy8>ub`^(qkfSmH7oZ)iT$T9g zf3NoyGb0Yb3cWJbD|A%#3gXwm+bB!3H0qA;bD8^p!yVdqGqv{XZPtNiiD{`0M@u|` zR;*kw{jPYz_*!Yb`Xxh@Csj_*lu~^BLVEX~U`FAQJs)v3NA|hF-g0Cmy}Zbw9wxWH z7u!j3K8z@n13Kr#za=OBqwD`>^8c3t{=E(0^H!9z)&V~zpH{OrYw7u1rv5Ysf87nA zEg|$Sn|sU+3U_hdz(SC;sE&e@LT9?~9~C>Pb5)!G91c~v=As|ElRx-7cWn3j$-%kS zdhQiqs~3_#SV9l3GJJ$`FkQiY*$I`bDpoZ-LNyq#0_$b`zGU+XS3(bx0(iy~n{Hq> z(J#BKhi|ps1fzqM;ClesgM8wIr04bji00RsM*i7}Ciwq!K~w#|wI8G-G^4cvf6?cd zdJajvkky)4e>b>cj^sn{*xBrD>D|R( z!3to?s~N@?7_q+k-ImkPTJ$4V^6Umz`0)RW&i=4Fvh^47*WUzRw~qQJav#8U7CyrX z<>_y{+8y0+tvi-}lRLiU=iL!<4YKb4Ls$6IFT48O?-0+=p*KM*vHU(aD4WCbdA#iL zvM9k}KIQ2xMf_iw|FabMzaWn+cUAnia#+BBvupt3SQW@V^`}00$H(YTU1za(`QHhZ zsxsvs7XImFNF9TGg0ZoFvj@~q)ao1Y&xg*|yMgw6*H!(ZYd-ymYs~wOYsos`nzQ!1 z<`V~9Q{Dsk^51ox6_2~KO~>d#QpQX&%}MIwHL>qUN8DLrBmLw=h}&1JR5^;$`in9>NWIy|GR{`7_&N=rGbCtj8E17XDa+B6aOFdf0&`Eyf)3IRUAOEF6&nV z51_bK_WqB$!R}n^#m6R})ozN{lZDSg`79CKin~+{PI}so_Oq}GugUX-ahrY0@qEmZ z9PmqW!~fp%kILQG>k;F#FroMu?o8m$8 ziJ=ugmyX`-jo7x44L-il$L}P}zPEfO-$_+{mbOr$Tn7-4V^h6EGV}{C`hauXJ)d>`{EO4CyQUp zKcfF@g-640!p|4~U3dn6WVh(Qo_Kcn?P5IgDUy)?pN;;X%KlHL_CY?^Jnk_TBr+ZppLzjnKiZ424!Vj>spQ~fxf1pa{Rc7yguj_7nnclUd-e*P* zh*y`jow_B%6<8Yhza0KQ+3o*;f7$=dtR`v$8zcjE(#vWavp49yUy!ldWqtF{m|al` z?&%XO{G0zT&p35k{8u3P@t?~7onHTIc1`QPbv7@s=XUDo$qTC8wc2`xR}m9Di62n( z^p~N>JF)vcKOXG=loc?I1h9(}u8@x5e&lZ=KTh(SXSg8YbN&6r zQS&m_7@GX`EO$ULMXu49daP43Fv9%5;Sur)`}+txD!+gnBH|IMb&e0ElEVK|;{Rf? zAAX+bZRzKQsi&=a>s6B*SV_N&)A#~Mcf8+qRXj=_yy|Z!;_BiA6)pEUHxZ>aIV6_U zqsmEEE`e+&tEr|&aCEfK)t0^Bj=k_L?5P3p?_(>2mZKPtYnQ@*3jd)$h@YvW`oJ(V zunrkePE4?B`wC`pUg7fheZciK98cc<8YCL&vLmWnGss)l%at z^<(D`q3=tc-|im&*BjlbC%`{-Me?79p;DEjXt{*1<_$0FZ>Ph5^7Q{o@&6MXU@Z3a zb00B4&9&2fn;-+!8?S@h;I2K)wWGgg^|p2Nz`2HgTjT~Y*XnF1b#mua4~zBBKDK(9 z6#U0gI9^|X{vRFdb)B^@xTn7UOYW5~uXBY@q?22P--LY7Y!K^r63Y*cztVi(nGXNS z(f=#O|4)y7;s4CRmF|pWfcoGv&o=bE?qJ4^`rx*cAE^0ORm`@lB{!guxxATo|DGG@ z%x3@6qZeO5+@Jo1Teue=UMrl%{2#;T)H~^Z_*uS?=6%tzkX}JW?q|1u(jED?E8XFL zyUOK1y~(&g)gNLRp!;Pfguk5sbDnqy-ZRDjTh9AZ=>IX9?$=|QZP%+gx~dO~EOCOG zt))4eny;Z6A@$R&QA_|CP{jO@ygPo=oh3hLL_M%9H$YsV>g>EtYd!({8WoqcH~Yk< z!>N(rv-@ev;zsBbLabzXsEwXP|Kg5*oxUTty}`Zmb>aUuFfZJzXP|l(C>9V${O8**O(Lr>fadh6wHUgt6oeB8B{6Biik@%j#3X54oqMqqvLlT-kBy)FA)_7D6% z`W`f+)6wdxM?BZz%3s>%UiikF-N_BBSS!H)8g~l(Yp#*+U8b5U>EU>YNWCxpzb3{1 zN}Gca=Bmv%wlC%N1Mb5yh5xCsdnwm*rsr5(Fk%Oo41g2V?A^va%Un4!pm_TlW^*&| za>viR272g>j`pyp6%SAkOdZkvU;>LE9-DjK-_t&yaeS(YP!AF8eLgc4ljKYHy5`*b zt!HR)#ue`5rYl|6#;e@X+t#>(XEyMBkAL+Z5eJaM|IB+i;65BL5C19teQ+`1DJ6!2AX~ein@deJhGt>+yu06VFW?{@;Tu5bm z?p|`v_aD)`&oT0(+KayLUf%K^W))FOvH6Y623+Y*ZCd4yZ&>RJpWPURfAtZHFaDcM z|6hY2tp4b5JrVm~bCA?KS}S@_#Or>*eK=kU{#E~z;{Rust&@MJeE$c-?5s#^(BcC0 z4$wRtVuMxN*U>X~9sP4xTCQ;Bga6(2(l2w!`eI|-TAs_u?7_7N{vzOCJcn!n%_1LEBpq96!+KZr9Q|Nk26 zhukoZe{cUUcKoOK|I5WbaY3-(%jzaJ;AaS^hd4d}Gd49(xbt?+5m26>>ILYVPmVw# z`~C2?54y&j2k6ONi%)O{89;xXB)!S^x1K}zIAf#T^cH={9oh0O>oZ=4Ow`PfGUlzT z|D5DN*2Zh-uasu^m%k9~|9Im+H|^>a{*%1__vAW$I{6^G&)a|U1A;Hmrv9PoAI5ym zc4~$ii4D}i3)Ye=T&Ldp>FeCFj5oX7AAP~~(NFh0c>?l}RX^zS;uRzCGrq)^^C;2* z8`|zkC&fh>dI>fD*8ZG=V=g=2a1O7b;|P4cVjh<_}Rf zQEl_WWPi#|x{^5}tKG>o>Z$%?6R$Bu4v>$VDt_u~t8@+gJ5C(Kb4 z*6bW+3!?KhGwa0W_3ot|A9a<7?xlvf9y_298(H%Oz>wL>ik|s?duUO=zim&BgK*)k zhVi$aK%fw*zMsOpavo*ptGBp5%g8y_V3jL;{GVMWGEW$8 ztEW$HE3=Jd(<$z2y`iWLUu^z=s{hx;xSb2^>ovST$PquFOEN&bpymlEK8Vf`cho>! zpotzr4LjaoeMC-fy4htiBe%8qN5sQhz>MbQ$!?D9dpl|``Xvk`+)X&;JT0!ltmO0L zKaY;~yIy8~XWsuuE{9p*nm1O5uCK#~Za@|`QbVaaD$N|MW!6$P_0)wK*SNChH-Pwl z>#OE_7VBE*^&Lyci6y&3`IU3Abnbrn&!L#5?2s0GwW5sck=^)qBfJqCTJsfJ(4*3&vZsR#DBgUnEB_Dl|9N;3 z@TcCY>PIZ!DAWwa9=)9)TjA9$*%=eBZ9gGthh1~t{jPA!4frc-OlIkvJMrxj3Hzpd zqH>Ugy?{p@LqBWHeUHxnN%8-4ZwpwypkhK1PN18dVa*om0smd_0Zn`H0d}vYj|hE) z=qXr^Pf&(#sU&Z(gq}htw!G8jeeVmdqw*>8;9HOZy_PFCz>FQu){;*hzgq3xS> zTi%_nXzSJXU1?T&3*TQi_z%2qqUfcdZ^Q9Y@IRCPztsDGT+Vl^n1E`AdWCtj1w8&G zgWItgTj(d)3NO$~Kfxw!$vXCa4fO-n*uXW|$C|x)ia7(Xq%n{0n}6h*3+`o(ZvlI= z7ry|#gbaw^Z9?bk9E5%4>5dLJyYAX&T|Rx0b26?2_sd)b{(S|!UM;y$EqhkD&b`a1 znd0;BT>~$&#%!W?o3o-?O3487Cbz@0J^#Pm$k`${Nt{9CHE_NY^qho?_T{H zx1T>(1VS@4v>iaDJ-!Y@zN$x?lK86yN`H^?wTg^PvBU33wYI^4bwQz;cAx zGaU!91LQl(hh(;ZFwv%3V*FwC6sRIzSdK4T$~pxnb}aoymwE6L%KjIiVz3$+u=P%GrxW5RK`={d?qMDzK7*!VsVm-idj zyVAw_Yv%k8RJ*p~N8G9J-0Cv7{=6$DhF7x|wQwVF zW~`2gU!dp_j;{Ctun&BwhvHQj6>U`A~&ii_a zk^3`W_)qcwb0;5yT^4*Z@yC9~h-wJ^tTEY_4dTPFg)6WJ%Gh_6@MdM2Kbn4ndwKIM z?)bfb>Z)Ja>H1p_5mRpf_kET}G%`BKef)664~&b`d=lBpRNf#1Bo|^=zg){H#8 zcJPPHCyL=A6YV<1>wIFrAN_3COa4Iw7oGd4Eycf-9MJq>apd*v!8)+epm^D?D~KDs z5qYq}^3e;ZF+7=mjmu8E&Yj%$c9(O<$6euf{=!u~_cd2@{BGA?`crqN>8Lx?Rp|Qr zN}2VIk2}=BYKp8nH#|_~2AJu6wx`gY1?OEgKXWbl-*PpFcDTZy{5`SB-*VZze!=D9 z*XNPDP=fra+x7<6wEc~)5ggXj54|2;*`$~T=i7=eqq&J*R|dSOZvspG6SSh_fbgVP zsmX|O8KBSZYbsS&Dc?wQSm$#oCSzFVddm*6&tUu9+x#82&%r(4m-U3=`-|6Jn(TdJ z_gBy_!sb-?znK4D<$n74Ri~f){EDJt^08-&|JZZT&y$_&QhaQ(Ycu|saReS0k^!B_ zfNo@K&tAAf_J0FhVco8E*aKR!3y=$nr;$@!P5;3%@`*~}*(^fJ*Z+f1KBZ2Vf;_}kR;kU0qGN*~L61G->^aTx9BNa=8|6Qma+wlbJk@07`w zZ0YXE^SZ#8a3-Ii72832S4%QrzE`u!T*+N;=S#Qw<tF>`}6#idBm|souVfT%G0Ub$Xkk5#LEHkZJu}IXRgD%EjxeNA;>H( z&P5!bWYPro^;wAT&=YF1?>L^1MenI_e^&UAl1Ch&{&Q-|7g!Li>K=WCzx`POfYB0piWdInn+ne%1t! zu2}@?J=R8SP4Trxa%&p2$Wc?BsyMhhzB6006_j_U`Za8Zddojth7QM%Ms`%Qq|X(D ztR^R+hVNOg(Pa5<?+swRg%J0cZ|PiO_f^2-r1rmLIBR15 z-zsuXuEgdu{eSv#d;r}qG5o7uL;J?>Df5%Ckz^-%TQ7zG`Q8iCLt!cQr1~Pi;9iaW+G_C*V?qvbhyf0RFL?VKqVc7Eg>=lfcuUVn0* zdP@BBy+}SwR_pszuR4o0Bj0P$3#$Jw#||t8|EIzHV_;wS*Zma!C%@M5+=G$>`aYwV zzFXC#SzZzP_<|$fx#2k+BiIi3)}Dx6ibu-MmVCDr__^@DhWVhI(LLz(cwUp#drzbP zFB$)e%zw-2BaDcjBOboD+ z-_`IOnI zi~hY{2W&@H53d<``)>6+;C*|-`$vxIgRpe2e`4T0_Ik(SPFVEV_i+qyMHk!uaC>08 z0*n`->(!6#(a*0en!vyKKkxt7seeTZ{|T_q;ibS`>=GuU_e9_eraMMI|APDM9nCwj z6&2@-eb4Z73jgt!373Qa8ZaIFfAM{VX{!wXk9-FEA6Xy{po~~iwQ|oQ{9i*PPeSz& zAxFYuKe9nt#sT9G+Hn$IGWI-EKVR&-O?OQ^)+WQhbo1<9Yqrn#GG^kv!_VF>nBVuF z?YS<==Lg(z^!i`93vfB&bEN;_{(O#gg!>26)x>#A$m2W>|5vzag?sd~t4{yquUF?O z|L1hZRrm$erm0_(<#sD4)8_$*M+_X2`k_q3f7u7vj{nHsM!2cEQ(a)?t{DV(l{ZK*L z3i5xJ8UD+eW1-q8;a_za`g^^OUQhX{7K@I&pXywFzEn#8$8|rX@SfawkNtil_!su9 z)*L*xX@P&MC5d2PwN1jXu&kWWN^(9=Vf&XbhoxZCa`)ul-tfp*e&fn7W^JU_BmIrw zf3@{&RqdsEwCQhEkD^@AW@Lc9HnHax{EUznqF9vYnK{BEO}0|lNZCfIJuv${Fd3HY zF|cmfm%caLdrUS9o2sP(|Eix7c7@?8u&iD)<;-3G=AZxJW9vWi z{8!cw7Nyg(e$#5|yM4~MdO68%P(5Y?uOa-)255#`Xr{-*xC}rRSbmDmX$t(O9#37H z)E=0-Js^8um{V_3>0x0}m}~-@9{b={x?g$TU|2nbk% zU%&S8e_UH|ikXb3Hm31j-J83s8K)@`6+gsuglbr#PCOznYv59L{g0%V%Y8SbP4_zg)NTmjC;;Kld7L z|L0d$-SX_eUe{W5o8kg1kP}xT1AHy1PsJI1%yHs);raWV zB;~EAR_Yq0_P}iSz=U26I8!c83#%FI$==tJUTy%JX7@|?gIQs>933t!p9a$<#QcPD zu-=yO-YefD?CbWX*SzNEzWGfylG#MZ{z*4Qc zmg)sH2g5IZ2K%-+=h*s_)X%AFlG+0?dte+dU?<=ROa**}o)7`|jp$`@boG(mX~KSW z#O_yL4`I20e7|Bi|5MCe|K_K!zvpdW-lO0Df9rc!-1OAHtgFt^Tz2IHvE(o4@6|)X zdIOBx0+HMR`2#HR0znR4bVMFVPT7*oO0Cp2NbP~?_do zydK>9`D*g{)$bI{gJEGgAN*%;SmU1h=k?Xw-o5IkXzYjkd;j>lzkcp3H}&UkT!p+q z21sULHwRD6UqG&J4&44zUwKpi?oVBRYruQ#F@60TUjO{xzxm$d-?(n<)CT1U zQ!9l0C}9O(pu%K;`X>7vLCtehj8J((>KQcYiu52+AClBcU4zsfxWqjWizobh;cMK- z2Y0gdYm~1my&SMloKJc`)cNIu{5@>e;a#$rk}%mL_aS)cB#kczm2^=)6d~OqMsKYyVPU! z-^Sjb>F4ks(a#HyUFtFVZ)5Mz^mBNR=;wvUF7+7wx3TwU`Z>IZf1dE5+FTuD@2hW* zU&QplzV7Qe!kn-tJHJABRDKWkzWVqmk6-nD`Plt=8^Qk9uNnK{-@fUg?||)StWS0S zv0qrd?8Se2>wV9D`R4wdudg@WpopA-(`o7(ru;#10_qn+Pg?2)YUv?aLoZ474vAf= zIjovw>G9a-rhZOclcl%^roxDDqu<$z$O!d-Rvlk0xU1f}jCrf%>*Jp*w@1Euk#x0U zc<}hyx2<(ge)+~TKl;q|JHB;^*bg!xHaBXfzF{6*&{h5 zKS4|PwPY{Y!afF-ZzIyg;YtBFSrE6P==iig|?&UYn7Vnci|1Gb5&HwfF_pN--5C3A#_9y>gZT^d2 zy?XHECh~}hCzhyoI8Dpj9L1bd3BJb#i}SUgb-avKI?dn2{x0_C$=>f$o)i6d(>*`- zcd|Ki}agm*2= z(F1d(tkYmH7c9Q?m8%Dz`G+<6kNxFU+c&*;)h+LM?S=9E$#6Z}$KLy&U;Cf_>H0UU z`66`#U;WtXFMjXV_5c3J=hhsek3bH6go-pzMC+$opI>F`iO;PndNO+1%puJkT6Ow~ z)Jk20)E+P_2oJWRup;cxqdT%zTCcuG!PZYc%lgdflaGF8&CB1r_3DSd1{T5OhyT|N zZ&>re*T42Z%?8)5X1Zs;&bbZK{rv{^vGK|NGa@^!w@mJoVeu6-Zry)D=iwfz%a9U4hgUNL_){6-Zry)D=iwfz%a9 zU4hgUNL_){6-Zry)D=iwfz%a9U4hgUNL_){6-Zry)D>7dS75{RA9cp~>r2!B{m<1* g|NGa?^!u6O(=)}lXNr%{6kne)K0oaTc%A$I0Y3|mssI20 literal 90567 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 From c8f3a85c22dd072725f12a14463185487efaa816 Mon Sep 17 00:00:00 2001 From: WinterPhoenix Date: Thu, 9 Jan 2025 20:47:12 -0500 Subject: [PATCH 87/87] Break disable-devtool --- html_chromium/ChromiumBrowser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/html_chromium/ChromiumBrowser.cpp b/html_chromium/ChromiumBrowser.cpp index cc71abe..0d70057 100644 --- a/html_chromium/ChromiumBrowser.cpp +++ b/html_chromium/ChromiumBrowser.cpp @@ -964,6 +964,11 @@ ChromiumBrowser::ReturnValue ChromiumBrowser::OnBeforeResourceLoad( CefRefPtrGetURL().ToString().find("disable-devtool") != std::string::npos ) { + return RV_CANCEL; + } + if ( strScheme != "http" && strScheme != "https" && strScheme != "asset" ) { return RV_CANCEL;