Skip to content

Instantly share code, notes, and snippets.

@LinArcX
Last active September 25, 2023 15:08
Show Gist options
  • Save LinArcX/0e9653bb4b9d7ef346adcc9b50282114 to your computer and use it in GitHub Desktop.
Save LinArcX/0e9653bb4b9d7ef346adcc9b50282114 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <SDL.h>
#include <include/cef_app.h>
#include <include/cef_client.h>
#include <include/cef_render_handler.h>
#include <include/cef_life_span_handler.h>
#include <include/cef_load_handler.h>
#include <include/wrapper/cef_helpers.h>
#include "sdl_keyboard_utils.h"
class RenderHandler :
public CefRenderHandler
{
public:
RenderHandler(SDL_Renderer* renderer, int w, int h) :
width(w),
height(h),
renderer(renderer)
{
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STREAMING, w, h);
}
~RenderHandler()
{
if (texture)
{
SDL_DestroyTexture(texture);
}
renderer = nullptr;
}
void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect)
{
rect = CefRect(0, 0, width, height);
//return true;
}
void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList& dirtyRects, const void* buffer, int w, int h)
{
if (texture)
{
unsigned char* texture_data = NULL;
int texture_pitch = 0;
SDL_LockTexture(texture, 0, (void**)&texture_data, &texture_pitch);
memcpy(texture_data, buffer, w * h * 4);
SDL_UnlockTexture(texture);
}
}
void resize(int w, int h)
{
if (texture)
{
SDL_DestroyTexture(texture);
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STREAMING, w, h);
width = w;
height = h;
}
void render()
{
SDL_RenderCopy(renderer, texture, NULL, NULL);
}
private:
int width;
int height;
SDL_Renderer* renderer = nullptr;
SDL_Texture* texture = nullptr;
IMPLEMENT_REFCOUNTING(RenderHandler);
};
// for manual render handler
class BrowserClient :
public CefClient,
public CefLifeSpanHandler,
public CefLoadHandler
{
public:
BrowserClient(CefRefPtr<CefRenderHandler> ptr) :
handler(ptr)
{
}
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler()
{
return this;
}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler()
{
return this;
}
virtual CefRefPtr<CefRenderHandler> GetRenderHandler()
{
return handler;
}
// CefLifeSpanHandler methods.
void OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
// Must be executed on the UI thread.
CEF_REQUIRE_UI_THREAD();
browser_id = browser->GetIdentifier();
}
bool DoClose(CefRefPtr<CefBrowser> browser)
{
// Must be executed on the UI thread.
CEF_REQUIRE_UI_THREAD();
// Closing the main window requires special handling. See the DoClose()
// documentation in the CEF header for a detailed description of this
// process.
if (browser->GetIdentifier() == browser_id)
{
// Set a flag to indicate that the window close should be allowed.
closing = true;
}
// Allow the close. For windowed browsers this will result in the OS close
// event being sent.
return false;
}
void OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode)
{
std::cout << "OnLoadEnd(" << httpStatusCode << ")" << std::endl;
loaded = true;
}
bool OnLoadError(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefLoadHandler::ErrorCode errorCode, const CefString& failedUrl, CefString& errorText)
{
std::cout << "OnLoadError()" << std::endl;
loaded = true;
}
void OnLoadingStateChange(CefRefPtr<CefBrowser> browser, bool isLoading, bool canGoBack, bool canGoForward)
{
std::cout << "OnLoadingStateChange()" << std::endl;
}
void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame)
{
std::cout << "OnLoadStart()" << std::endl;
}
bool closeAllowed() const
{
return closing;
}
bool isLoaded() const
{
return loaded;
}
private:
int browser_id;
bool closing = false;
bool loaded = false;
CefRefPtr<CefRenderHandler> handler;
IMPLEMENT_REFCOUNTING(BrowserClient);
};
CefBrowserHost::MouseButtonType translateMouseButton(SDL_MouseButtonEvent const& e)
{
CefBrowserHost::MouseButtonType result;
switch (e.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_X1:
result = MBT_LEFT;
break;
case SDL_BUTTON_MIDDLE:
result = MBT_MIDDLE;
break;
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_X2:
result = MBT_RIGHT;
break;
}
return result;
}
int main(int argc, char* argv[])
{
CefMainArgs args;
{
int result = CefExecuteProcess(args, nullptr, nullptr);
// checkout CefApp, derive it and set it as second parameter, for more control on
// command args and resources.
if (result >= 0) // child proccess has endend, so exit.
{
return result;
}
else if (result == -1)
{
// we are here in the father proccess.
}
}
{
CefSettings settings;
// CefString(&settings.resources_dir_path).FromASCII("");
{
std::ostringstream ss;
ss << SDL_GetBasePath() << "locales/";
CefString(&settings.locales_dir_path) = ss.str();
}
// CefString(&settings.locales_dir_path).FromASCII("");
CefString(&settings.resources_dir_path) = SDL_GetBasePath();
// checkout detailed settings options http://magpcss.org/ceforum/apidocs/projects/%28default%29/_cef_settings_t.html
// nearly all the settings can be set via args too.
// settings.multi_threaded_message_loop = true; // not supported, except windows
// CefString(&settings.browser_subprocess_path).FromASCII("sub_proccess path, by default uses and starts this executeable as child");
// CefString(&settings.cache_path).FromASCII("");
// CefString(&settings.log_file).FromASCII("");
// settings.log_severity = LOGSEVERITY_DEFAULT;
bool result = CefInitialize(args, settings, nullptr, nullptr);
// CefInitialize creates a sub-proccess and executes the same executeable, as calling CefInitialize, if not set different in settings.browser_subprocess_path
// if you create an extra program just for the childproccess you only have to call CefExecuteProcess(...) in it.
if (!result)
{
// handle error
return -1;
}
}
//Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
return 1;
}
int width = 800;
int height = 600;
// // Run the CEF message loop. This will block until CefQuitMessageLoop() is
// // called.
//CefRunMessageLoop();
auto window = SDL_CreateWindow("Render CEF with SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_RESIZABLE);
if (window)
{
auto renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer)
{
SDL_Event e;
CefRefPtr<RenderHandler> renderHandler = new RenderHandler(renderer, width, height);
// create browser-window
CefRefPtr<CefBrowser> browser;
CefRefPtr<BrowserClient> browserClient;
{
CefWindowInfo window_info;
CefBrowserSettings browserSettings;
// browserSettings.windowless_frame_rate = 60; // 30 is default
//window_info.SetAsWindowless(NULL, false); // false means no transparency (site background colour)
browserClient = new BrowserClient(renderHandler);
browser = CefBrowserHost::CreateBrowserSync(window_info, browserClient.get(), "http://www.google.com", browserSettings, nullptr,nullptr);
// inject user-input by calling - non-trivial for non-windows - checkout the cefclient source and the platform specific cpp, like cefclient_osr_widget_gtk.cpp for linux
// browser->GetHost()->SendKeyEvent(...);
// browser->GetHost()->SendMouseMoveEvent(...);
// browser->GetHost()->SendMouseClickEvent(...);
// browser->GetHost()->SendMouseWheelEvent(...);
}
bool shutdown = false;
bool js_executed = false;
while (!browserClient->closeAllowed())
{
// send events to browser
while (!shutdown && SDL_PollEvent(&e) != 0)
{
switch (e.type)
{
case SDL_QUIT:
shutdown = true;
browser->GetHost()->CloseBrowser(false);
break;
case SDL_KEYDOWN:
{
CefKeyEvent event;
event.modifiers = getKeyboardModifiers(e.key.keysym.mod);
event.windows_key_code = getWindowsKeyCode(e.key.keysym);
event.type = KEYEVENT_RAWKEYDOWN;
browser->GetHost()->SendKeyEvent(event);
event.type = KEYEVENT_CHAR;
browser->GetHost()->SendKeyEvent(event);
}
break;
case SDL_KEYUP:
{
CefKeyEvent event;
event.modifiers = getKeyboardModifiers(e.key.keysym.mod);
event.windows_key_code = getWindowsKeyCode(e.key.keysym);
event.type = KEYEVENT_KEYUP;
browser->GetHost()->SendKeyEvent(event);
}
break;
case SDL_WINDOWEVENT:
switch (e.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
renderHandler->resize(e.window.data1, e.window.data2);
browser->GetHost()->WasResized();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
browser->GetHost()->SetFocus(true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
browser->GetHost()->SetFocus(false);
break;
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
//browser->GetHost()->SetWindowVisibility(false);
browser->GetHost()->WasHidden(true);
break;
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_RESTORED:
//browser->GetHost()->SetWindowVisibility(true);
browser->GetHost()->WasHidden(false);
break;
case SDL_WINDOWEVENT_CLOSE:
e.type = SDL_QUIT;
SDL_PushEvent(&e);
break;
}
break;
case SDL_MOUSEMOTION:
{
CefMouseEvent event;
event.x = e.motion.x;
event.y = e.motion.y;
browser->GetHost()->SendMouseMoveEvent(event, false);
}
break;
case SDL_MOUSEBUTTONUP:
{
CefMouseEvent event;
event.x = e.button.x;
event.y = e.button.y;
browser->GetHost()->SendMouseClickEvent(event, translateMouseButton(e.button), true, 1);
}
break;
case SDL_MOUSEBUTTONDOWN:
{
CefMouseEvent event;
event.x = e.button.x;
event.y = e.button.y;
browser->GetHost()->SendMouseClickEvent(event, translateMouseButton(e.button), false, 1);
}
break;
case SDL_MOUSEWHEEL:
{
int delta_x = e.wheel.x;
int delta_y = e.wheel.y;
if (SDL_MOUSEWHEEL_FLIPPED == e.wheel.direction)
{
delta_y *= -1;
}
else
{
delta_x *= -1;
}
CefMouseEvent event;
browser->GetHost()->SendMouseWheelEvent(event, delta_x, delta_y);
}
break;
}
}
if (!js_executed && browserClient->isLoaded())
{
js_executed = true;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');", frame->GetURL(), 0);
}
// let browser process events
//CefDoMessageLoopWork();
CefRunMessageLoop();
// render
SDL_RenderClear(renderer);
renderHandler->render();
// Update screen
SDL_RenderPresent(renderer);
//Wait two seconds
//SDL_Delay(2000);
}
browser = nullptr;
browserClient = nullptr;
renderHandler = nullptr;
CefShutdown();
SDL_DestroyRenderer(renderer);
}
}
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#ifndef _KEYBOARD_UTILS_H_
#define _KEYBOARD_UTILS_H_
#include <SDL.h>
int getKeyboardModifiers(uint16_t const mod)
{
int result = EVENTFLAG_NONE;
if (mod & KMOD_NUM)
{
result |= EVENTFLAG_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS)
{
result |= EVENTFLAG_CAPS_LOCK_ON;
}
if (mod & KMOD_CTRL)
{
result |= EVENTFLAG_CONTROL_DOWN;
}
if (mod & KMOD_SHIFT)
{
result |= EVENTFLAG_SHIFT_DOWN;
}
if (mod & KMOD_ALT)
{
result |= EVENTFLAG_ALT_DOWN;
}
return result;
}
int getWindowsKeyCode(SDL_Keysym const &key)
{
int result = 0;
bool shift = !!(key.mod & KMOD_SHIFT);
bool caps = !!(key.mod & KMOD_CAPS);
bool alt_gr = !!(key.mod & KMOD_RALT);
bool uppercase = (caps && !shift) || (shift && !caps);
// mapped from azerty windows 8 asus laptop
switch (key.sym)
{
case SDLK_RETURN:
result = 13;
break;
case SDLK_ESCAPE:
result = 27;
break;
case SDLK_BACKSPACE:
result = 8;
break;
case SDLK_TAB:
result = 9;
break;
case SDLK_SPACE:
result = 32;
break;
case SDLK_EXCLAIM:
result = uppercase ? 167 : 33; // § : !
break;
case SDLK_QUOTEDBL:
result = 34;
break;
case SDLK_HASH:
result = 35;
break;
case SDLK_DOLLAR:
result = 36;
break;
case SDLK_PERCENT:
result = 37;
break;
case SDLK_AMPERSAND:
result = 38;
break;
case SDLK_QUOTE:
result = 39;
break;
case SDLK_LEFTPAREN:
result = 40;
break;
case SDLK_RIGHTPAREN:
result = alt_gr ? 93 : uppercase ? 176 : 41; // ] ? ° : )
break;
case SDLK_ASTERISK:
result = uppercase ? 181 : 42; // µ : *
break;
case SDLK_PLUS:
result = 43;
break;
case SDLK_COMMA:
result = uppercase ? 63 : 44; // '?' : ,
break;
case SDLK_MINUS:
result = 45;
break;
case SDLK_PERIOD:
result = 46;
break;
case SDLK_SLASH:
result = 47;
break;
case SDLK_0:
result = alt_gr ? 64 : uppercase ? 48 : 224; // @ ? 0 : à
break;
case SDLK_1:
result = uppercase ? 49 : 38; // 1 : & (KO)
break;
case SDLK_2:
result = alt_gr ? 126 : uppercase ? 50 : 233; // ~ ? 2 : é
break;
case SDLK_3:
result = alt_gr ? 35 : uppercase ? 51 : 34; // # ? 3 : "
break;
case SDLK_4:
result = alt_gr ? 123 : uppercase ? 52 : 39; // { ? 4 : '
break;
case SDLK_5:
result = alt_gr ? 91 : uppercase ? 53 : 40; // [ ? 5 : ( (KO)
break;
case SDLK_6:
result = alt_gr ? 124 : uppercase ? 54 : 45; // | ? 6 : -
break;
case SDLK_7:
result = alt_gr ? 96 : uppercase ? 55 : 232; // ` ? 7 : è
break;
case SDLK_8:
result = alt_gr ? 92 : uppercase ? 56 : 95; // \ ? 8 : _
break;
case SDLK_9:
result = alt_gr ? 94 : uppercase ? 57 : 231; // ^ ? 9 : ç
break;
case SDLK_COLON:
result = uppercase ? 47 : 58; // / : :
break;
case SDLK_SEMICOLON:
result = uppercase ? 46 : 59; // . (KO) : ;
break;
case SDLK_LESS:
result = uppercase ? 62 : 60; // > : <
break;
case SDLK_EQUALS:
result = alt_gr ? 125 : uppercase ? 43 : 61; // } ? + : =
break;
case SDLK_GREATER:
result = 62;
break;
case SDLK_QUESTION:
result = 63;
break;
case SDLK_AT:
result = 64;
break;
case SDLK_LEFTBRACKET:
result = 91;
break;
case SDLK_BACKSLASH:
result = 92;
break;
case SDLK_RIGHTBRACKET:
result = 93;
break;
case SDLK_CARET:
result = uppercase ? 168 : 94; // ^ : ¨
break;
case SDLK_UNDERSCORE:
result = 95;
break;
case SDLK_BACKQUOTE:
result = 96;
break;
case SDLK_a:
result = uppercase ? 65 : 97;
break;
case SDLK_b:
result = uppercase ? 66 : 98;
break;
case SDLK_c:
result = uppercase ? 67 : 99;
break;
case SDLK_d:
result = uppercase ? 68 : 100;
break;
case SDLK_e:
result = uppercase ? 69 : 101;
break;
case SDLK_f:
result = uppercase ? 70 : 102;
break;
case SDLK_g:
result = uppercase ? 71 : 103;
break;
case SDLK_h:
result = uppercase ? 72 : 104;
break;
case SDLK_i:
result = uppercase ? 73 : 105;
break;
case SDLK_j:
result = uppercase ? 74 : 106;
break;
case SDLK_k:
result = uppercase ? 75 : 107;
break;
case SDLK_l:
result = uppercase ? 76 : 108;
break;
case SDLK_m:
result = uppercase ? 77 : 109;
break;
case SDLK_n:
result = uppercase ? 78 : 110;
break;
case SDLK_o:
result = uppercase ? 79 : 111;
break;
case SDLK_p:
result = uppercase ? 80 : 112;
break;
case SDLK_q:
result = uppercase ? 81 : 113;
break;
case SDLK_r:
result = uppercase ? 82 : 114;
break;
case SDLK_s:
result = uppercase ? 83 : 115;
break;
case SDLK_t:
result = uppercase ? 84 : 116;
break;
case SDLK_u:
result = uppercase ? 85 : 117;
break;
case SDLK_v:
result = uppercase ? 86 : 118;
break;
case SDLK_w:
result = uppercase ? 87 : 119;
break;
case SDLK_x:
result = uppercase ? 88 : 120;
break;
case SDLK_y:
result = uppercase ? 89 : 121;
break;
case SDLK_z:
result = uppercase ? 90 : 122;
break;
}
return result;
}
#endif // _KEYBOARD_UTILS_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment