diff --git a/src/client_handler/Makefile b/src/client_handler/Makefile index 4465d28b..13586c8e 100644 --- a/src/client_handler/Makefile +++ b/src/client_handler/Makefile @@ -12,7 +12,7 @@ UNAME_S = $(shell uname -s) CCFLAGS = -fPIC $(CEF_CCFLAGS) ifeq ($(UNAME_S), Linux) - SRC_MORE = x11.cpp + SRC_MORE = x11.cpp dialog_handler_gtk.cpp else ifeq ($(UNAME_S), Darwin) SRC_MORE = util_mac.mm endif @@ -22,7 +22,7 @@ SRC = client_handler.cpp cookie_visitor.cpp resource_handler.cpp \ task.cpp context_menu_handler.cpp display_handler.cpp \ download_handler.cpp focus_handler.cpp js_dialog_handler.cpp \ keyboard_handler.cpp lifespan_handler.cpp load_handler.cpp \ - render_handler.cpp request_handler.cpp \ + render_handler.cpp request_handler.cpp dialog_handler.cpp \ $(SRC_MORE) OBJ = $(filter %.o, $(SRC:.cpp=.o) $(SRC:.mm=.o)) diff --git a/src/client_handler/client_handler.cpp b/src/client_handler/client_handler.cpp index d8f6189e..f14fbaf8 100644 --- a/src/client_handler/client_handler.cpp +++ b/src/client_handler/client_handler.cpp @@ -24,6 +24,7 @@ // CefClient // ---------------------------------------------------------------------------- + bool ClientHandler::OnProcessMessageReceived( CefRefPtr browser, CefProcessId source_process, diff --git a/src/client_handler/client_handler.h b/src/client_handler/client_handler.h index 33c12507..4a5c29b7 100644 --- a/src/client_handler/client_handler.h +++ b/src/client_handler/client_handler.h @@ -13,6 +13,7 @@ #include "common/cefpython_public_api.h" #include "context_menu_handler.h" +#include "dialog_handler.h" #include "display_handler.h" #include "download_handler.h" #include "focus_handler.h" @@ -26,6 +27,7 @@ class ClientHandler : public CefClient, public ContextMenuHandler, + public DialogHandler, public DisplayHandler, public DownloadHandler, public FocusHandler, @@ -44,6 +46,12 @@ class ClientHandler : public CefClient, return this; } +#if defined(OS_LINUX) + CefRefPtr GetDialogHandler() override { + return this; + } +#endif + CefRefPtr GetDisplayHandler() override { return this; } diff --git a/src/client_handler/dialog_handler.cpp b/src/client_handler/dialog_handler.cpp new file mode 100644 index 00000000..ab90de9b --- /dev/null +++ b/src/client_handler/dialog_handler.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#include "dialog_handler.h" + + +DialogHandler::DialogHandler() +{ +#if defined(OS_LINUX) + // Provide the GTK-based default dialog implementation on Linux. + dialog_handler_ = new ClientDialogHandlerGtk(); +#endif +} + + +bool DialogHandler::OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) +{ +#if defined(OS_LINUX) + return dialog_handler_->OnFileDialog(browser, + mode, + title, + default_file_path, + accept_filters, + selected_accept_filter, + callback); +#else + return false; +#endif + +} diff --git a/src/client_handler/dialog_handler.h b/src/client_handler/dialog_handler.h new file mode 100644 index 00000000..21d79a60 --- /dev/null +++ b/src/client_handler/dialog_handler.h @@ -0,0 +1,38 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#pragma once + +#include "common/cefpython_public_api.h" +#include "include/cef_dialog_handler.h" + +#if defined(OS_LINUX) +#include "dialog_handler_gtk.h" +#endif + + +class DialogHandler : public CefDialogHandler +{ +public: + DialogHandler(); + virtual ~DialogHandler(){} + + bool OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) + override; + +public: +#if defined(OS_LINUX) + // Default dialog handler impl for GTK. + CefRefPtr dialog_handler_; +#endif + +private: + IMPLEMENT_REFCOUNTING(DialogHandler); +}; diff --git a/src/client_handler/dialog_handler_gtk.cpp b/src/client_handler/dialog_handler_gtk.cpp new file mode 100644 index 00000000..1db13290 --- /dev/null +++ b/src/client_handler/dialog_handler_gtk.cpp @@ -0,0 +1,464 @@ +// Default dialog handler implementation on Linux. +// Copied from upstream cefclient with changes: +// - Rewrote GetWindow() func +// - Removed "client" namespace +// - Changed titles of JS alerts, removed URL and "Javascript" word + +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include +#include +#include +#include + +#include "include/cef_browser.h" +#include "include/cef_parser.h" +#include "include/wrapper/cef_helpers.h" + +#include "dialog_handler_gtk.h" +#include "x11.h" + +namespace { + +const char kPromptTextId[] = "cef_prompt_text"; + +// If there's a text entry in the dialog, get the text from the first one and +// return it. +std::string GetPromptText(GtkDialog* dialog) { + GtkWidget* widget = static_cast( + g_object_get_data(G_OBJECT(dialog), kPromptTextId)); + if (widget) + return gtk_entry_get_text(GTK_ENTRY(widget)); + return std::string(); +} + +std::string GetDescriptionFromMimeType(const std::string& mime_type) { + // Check for wild card mime types and return an appropriate description. + static const struct { + const char* mime_type; + const char* label; + } kWildCardMimeTypes[] = { + { "audio", "Audio Files" }, + { "image", "Image Files" }, + { "text", "Text Files" }, + { "video", "Video Files" }, + }; + + for (size_t i = 0; + i < sizeof(kWildCardMimeTypes) / sizeof(kWildCardMimeTypes[0]); ++i) { + if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*") + return std::string(kWildCardMimeTypes[i].label); + } + + return std::string(); +} + +void AddFilters(GtkFileChooser* chooser, + const std::vector& accept_filters, + bool include_all_files, + std::vector* filters) { + bool has_filter = false; + + for (size_t i = 0; i < accept_filters.size(); ++i) { + const std::string& filter = accept_filters[i]; + if (filter.empty()) + continue; + + std::vector extensions; + std::string description; + + size_t sep_index = filter.find('|'); + if (sep_index != std::string::npos) { + // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3". + description = filter.substr(0, sep_index); + + const std::string& exts = filter.substr(sep_index + 1); + size_t last = 0; + size_t size = exts.size(); + for (size_t i = 0; i <= size; ++i) { + if (i == size || exts[i] == ';') { + std::string ext(exts, last, i - last); + if (!ext.empty() && ext[0] == '.') + extensions.push_back(ext); + last = i + 1; + } + } + } else if (filter[0] == '.') { + // Treat as an extension beginning with the '.' character. + extensions.push_back(filter); + } else { + // Otherwise convert mime type to one or more extensions. + description = GetDescriptionFromMimeType(filter); + + std::vector ext; + CefGetExtensionsForMimeType(filter, ext); + for (size_t x = 0; x < ext.size(); ++x) + extensions.push_back("." + ext[x].ToString()); + } + + if (extensions.empty()) + continue; + + GtkFileFilter* gtk_filter = gtk_file_filter_new(); + + std::string ext_str; + for (size_t x = 0; x < extensions.size(); ++x) { + const std::string& pattern = "*" + extensions[x]; + if (x != 0) + ext_str += ";"; + ext_str += pattern; + gtk_file_filter_add_pattern(gtk_filter, pattern.c_str()); + } + + if (description.empty()) + description = ext_str; + else + description += " (" + ext_str + ")"; + + gtk_file_filter_set_name(gtk_filter, description.c_str()); + gtk_file_chooser_add_filter(chooser, gtk_filter); + if (!has_filter) + has_filter = true; + + filters->push_back(gtk_filter); + } + + // Add the *.* filter, but only if we have added other filters (otherwise it + // is implied). + if (include_all_files && has_filter) { + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*"); + gtk_file_filter_set_name(filter, "All Files (*)"); + gtk_file_chooser_add_filter(chooser, filter); + } +} + +GtkWindow* GetWindow(CefRefPtr browser) { + // -- REWRITTEN FOR CEF PYTHON USE CASE -- + // X11 window handle + ::Window xwindow = browser->GetHost()->GetWindowHandle(); + // X11 display + ::Display* xdisplay = cef_get_xdisplay(); + // GDK display + GdkDisplay* gdk_display = NULL; + if (xdisplay) { + // See if we can find GDK display using X11 display + gdk_display = gdk_x11_lookup_xdisplay(xdisplay); + } + if (!gdk_display) { + // If not then get the default display + gdk_display = gdk_display_get_default(); + } + if (!gdk_display) { + // The tkinter_.py and hello_world.py examples do not use GTK + // internally, so GTK wasn't yet initialized and must do it + // now, so that display is available. Also must install X11 + // error handlers to avoid 'BadWindow' errors. + gtk_init(0, NULL); + InstallX11ErrorHandlers(); + // Now the display is available + gdk_display = gdk_display_get_default(); + } + // In kivy_.py example getting error message: + // > Can't create GtkPlug as child of non-GtkSocket + // However dialog handler works just fine. + GtkWidget* widget = gtk_plug_new_for_display(gdk_display, xwindow); + // Getting top level widget doesn't seem to be required. + // OFF: GtkWidget* toplevel = gtk_widget_get_toplevel(widget); + GtkWindow* window = GTK_WINDOW(widget); + if (!window) { + LOG(ERROR) << "No GtkWindow for browser"; + } + return window; +} + +} // namespace + + +ClientDialogHandlerGtk::ClientDialogHandlerGtk() + : gtk_dialog_(NULL) { +} + +bool ClientDialogHandlerGtk::OnFileDialog( + CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) { + std::vector files; + + GtkFileChooserAction action; + const gchar* accept_button; + + // Remove any modifier flags. + FileDialogMode mode_type = + static_cast(mode & FILE_DIALOG_TYPE_MASK); + + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) { + action = GTK_FILE_CHOOSER_ACTION_OPEN; + accept_button = GTK_STOCK_OPEN; + } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) { + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + accept_button = GTK_STOCK_OPEN; + } else if (mode_type == FILE_DIALOG_SAVE) { + action = GTK_FILE_CHOOSER_ACTION_SAVE; + accept_button = GTK_STOCK_SAVE; + } else { + NOTREACHED(); + return false; + } + + std::string title_str; + if (!title.empty()) { + title_str = title; + } else { + switch (mode_type) { + case FILE_DIALOG_OPEN: + title_str = "Open File"; + break; + case FILE_DIALOG_OPEN_MULTIPLE: + title_str = "Open Files"; + break; + case FILE_DIALOG_OPEN_FOLDER: + title_str = "Open Folder"; + break; + case FILE_DIALOG_SAVE: + title_str = "Save File"; + break; + default: + break; + } + } + + GtkWindow* window = GetWindow(browser); + if (!window) + return false; + + GtkWidget* dialog = gtk_file_chooser_dialog_new( + title_str.c_str(), + GTK_WINDOW(window), + action, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + accept_button, GTK_RESPONSE_ACCEPT, + NULL); + + if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + + if (mode_type == FILE_DIALOG_SAVE) { + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(dialog), + !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG)); + } + + gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), + !(mode & FILE_DIALOG_HIDEREADONLY_FLAG)); + + if (!default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) { + const std::string& file_path = default_file_path; + bool exists = false; + + struct stat sb; + if (stat(file_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) { + // Use the directory and name of the existing file. + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), + file_path.data()); + exists = true; + } + + if (!exists) { + // Set the current file name but let the user choose the directory. + std::string file_name_str = file_path; + const char* file_name = basename(const_cast(file_name_str.data())); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), file_name); + } + } + + std::vector filters; + AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, true, &filters); + if (selected_accept_filter < static_cast(filters.size())) { + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), + filters[selected_accept_filter]); + } + + bool success = false; + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_FOLDER || + mode_type == FILE_DIALOG_SAVE) { + char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + files.push_back(std::string(filename)); + success = true; + } else if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) { + GSList* filenames = + gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + if (filenames) { + for (GSList* iter = filenames; iter != NULL; + iter = g_slist_next(iter)) { + std::string path(static_cast(iter->data)); + g_free(iter->data); + files.push_back(path); + } + g_slist_free(filenames); + success = true; + } + } + } + + int filter_index = selected_accept_filter; + if (success) { + GtkFileFilter* selected_filter = + gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); + if (selected_filter != NULL) { + for (size_t x = 0; x < filters.size(); ++x) { + if (filters[x] == selected_filter) { + filter_index = x; + break; + } + } + } + } + + gtk_widget_destroy(dialog); + + if (success) + callback->Continue(filter_index, files); + else + callback->Cancel(); + + return true; +} + +bool ClientDialogHandlerGtk::OnJSDialog( + CefRefPtr browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr callback, + bool& suppress_message) { + CEF_REQUIRE_UI_THREAD(); + + GtkButtonsType buttons = GTK_BUTTONS_NONE; + GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER; + std::string title; + + switch (dialog_type) { + case JSDIALOGTYPE_ALERT: + buttons = GTK_BUTTONS_NONE; + gtk_message_type = GTK_MESSAGE_WARNING; + title = "Alert"; + break; + + case JSDIALOGTYPE_CONFIRM: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "Confirm"; + break; + + case JSDIALOGTYPE_PROMPT: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "Prompt"; + break; + } + + js_dialog_callback_ = callback; + + if (!origin_url.empty()) { + // title += " - "; + // title += CefFormatUrlForSecurityDisplay(origin_url).ToString(); + } + + GtkWindow* window = GetWindow(browser); + if (!window) + return false; + + gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_MODAL, + gtk_message_type, + buttons, + "%s", + message_text.ToString().c_str()); + g_signal_connect(gtk_dialog_, + "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), + NULL); + + gtk_window_set_title(GTK_WINDOW(gtk_dialog_), title.c_str()); + + GtkWidget* ok_button = gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), + GTK_STOCK_OK, + GTK_RESPONSE_OK); + + if (dialog_type != JSDIALOGTYPE_PROMPT) + gtk_widget_grab_focus(ok_button); + + if (dialog_type == JSDIALOGTYPE_PROMPT) { + GtkWidget* content_area = + gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_)); + GtkWidget* text_box = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(text_box), + default_prompt_text.ToString().c_str()); + gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0); + g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box); + gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE); + } + + gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK); + g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this); + gtk_widget_show_all(GTK_WIDGET(gtk_dialog_)); + + return true; +} + +bool ClientDialogHandlerGtk::OnBeforeUnloadDialog( + CefRefPtr browser, + const CefString& message_text, + bool is_reload, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + const std::string& new_message_text = + message_text.ToString() + "\n\nIs it OK to leave/reload this page?"; + bool suppress_message = false; + + return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM, + new_message_text, CefString(), callback, suppress_message); +} + +void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + if (!gtk_dialog_) + return; + gtk_widget_destroy(gtk_dialog_); + gtk_dialog_ = NULL; + js_dialog_callback_ = NULL; +} + +// static +void ClientDialogHandlerGtk::OnDialogResponse(GtkDialog* dialog, + gint response_id, + ClientDialogHandlerGtk* handler) { + CEF_REQUIRE_UI_THREAD(); + + DCHECK_EQ(dialog, GTK_DIALOG(handler->gtk_dialog_)); + switch (response_id) { + case GTK_RESPONSE_OK: + handler->js_dialog_callback_->Continue(true, GetPromptText(dialog)); + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + handler->js_dialog_callback_->Continue(false, CefString()); + break; + default: + NOTREACHED(); + } + + handler->OnResetDialogState(NULL); +} diff --git a/src/client_handler/dialog_handler_gtk.h b/src/client_handler/dialog_handler_gtk.h new file mode 100644 index 00000000..aba4857a --- /dev/null +++ b/src/client_handler/dialog_handler_gtk.h @@ -0,0 +1,59 @@ +// Default dialog handler implementation on Linux. +// Copied from upstream cefclient with changes: +// - Removed "client" namespace + +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ +#pragma once + +#include + +#include "include/cef_dialog_handler.h" +#include "include/cef_jsdialog_handler.h" + +class ClientDialogHandlerGtk : public CefDialogHandler, + public CefJSDialogHandler { + public: + ClientDialogHandlerGtk(); + + // CefDialogHandler methods. + bool OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) OVERRIDE; + + // CefJSDialogHandler methods. + bool OnJSDialog(CefRefPtr browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr callback, + bool& suppress_message) OVERRIDE; + bool OnBeforeUnloadDialog( + CefRefPtr browser, + const CefString& message_text, + bool is_reload, + CefRefPtr callback) OVERRIDE; + void OnResetDialogState(CefRefPtr browser) OVERRIDE; + + private: + static void OnDialogResponse(GtkDialog *dialog, + gint response_id, + ClientDialogHandlerGtk* handler); + + GtkWidget* gtk_dialog_; + CefRefPtr js_dialog_callback_; + + IMPLEMENT_REFCOUNTING(ClientDialogHandlerGtk); + DISALLOW_COPY_AND_ASSIGN(ClientDialogHandlerGtk); +}; + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ diff --git a/src/client_handler/js_dialog_handler.cpp b/src/client_handler/js_dialog_handler.cpp index a1f641a0..3d3ecd09 100644 --- a/src/client_handler/js_dialog_handler.cpp +++ b/src/client_handler/js_dialog_handler.cpp @@ -4,6 +4,14 @@ #include "js_dialog_handler.h" +JSDialogHandler::JSDialogHandler() +{ +#if defined(OS_LINUX) + // Provide the GTK-based default dialog implementation on Linux. + dialog_handler_ = new ClientDialogHandlerGtk(); +#endif +} + bool JSDialogHandler::OnJSDialog(CefRefPtr browser, const CefString& origin_url, @@ -14,11 +22,18 @@ bool JSDialogHandler::OnJSDialog(CefRefPtr browser, bool& suppress_message) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnJavascriptDialog( + bool ret = JavascriptDialogHandler_OnJavascriptDialog( browser, origin_url, dialog_type, message_text, default_prompt_text, callback, suppress_message); + if (!ret) { + // Default implementation + return dialog_handler_->OnJSDialog(browser, origin_url, dialog_type, + message_text, default_prompt_text, + callback, suppress_message); + } + return ret; } @@ -29,21 +44,30 @@ bool JSDialogHandler::OnBeforeUnloadDialog( CefRefPtr callback) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( + bool ret = JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( browser, message_text, is_reload, callback); + if (!ret) { + // Default implementation + return dialog_handler_->OnBeforeUnloadDialog(browser, message_text, + is_reload, callback); + } + return ret; } void JSDialogHandler::OnResetDialogState(CefRefPtr browser) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnResetJavascriptDialogState(browser); + // Default implementation + dialog_handler_->OnResetDialogState(browser); + // User implementation + JavascriptDialogHandler_OnResetJavascriptDialogState(browser); } void JSDialogHandler::OnDialogClosed(CefRefPtr browser) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnJavascriptDialogClosed(browser); + JavascriptDialogHandler_OnJavascriptDialogClosed(browser); } diff --git a/src/client_handler/js_dialog_handler.h b/src/client_handler/js_dialog_handler.h index 6d3ce3e4..43fdf251 100644 --- a/src/client_handler/js_dialog_handler.h +++ b/src/client_handler/js_dialog_handler.h @@ -5,11 +5,15 @@ #include "common/cefpython_public_api.h" #include "include/cef_jsdialog_handler.h" +#if defined(OS_LINUX) +#include "dialog_handler_gtk.h" +#endif + class JSDialogHandler : public CefJSDialogHandler { public: - JSDialogHandler(){} + JSDialogHandler(); virtual ~JSDialogHandler(){} typedef cef_jsdialog_type_t JSDialogType; @@ -31,6 +35,12 @@ class JSDialogHandler : public CefJSDialogHandler void OnResetDialogState(CefRefPtr browser) override; void OnDialogClosed(CefRefPtr browser) override; +public: +#if defined(OS_LINUX) + // Default dialog handler impl for GTK. + CefRefPtr dialog_handler_; +#endif + private: IMPLEMENT_REFCOUNTING(JSDialogHandler); }; diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 47dedaec..35f85002 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 3 +DEF PY_MAJOR_VERSION = 2 diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index ad6730b1..32a3f0c4 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -113,6 +113,15 @@ cdef public void LifespanHandler_OnBeforeClose( cdef object callback try: Debug("LifespanHandler_OnBeforeClose") + # NOTE: browser_id may not necessarily be in g_pyBrowsers currently. + # I haven't yet debugged it but the logic in Shutdown that + # tries to force close browsers and removes references might + # have something to do with it. Such scenario is reproducible + # with the following steps: + # 1. Run wxpython.py example + # 2. Google "js alert" and open w3schools + # 3. Open demo popup + # 4. Close main window (not popup) pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeClose") callback = pyBrowser.GetClientCallback("OnBeforeClose") if callback: diff --git a/src/window_utils_mac.pyx b/src/window_utils_mac.pyx index 07c70b64..d683daf8 100644 --- a/src/window_utils_mac.pyx +++ b/src/window_utils_mac.pyx @@ -37,3 +37,7 @@ class WindowUtils: def IsWindowHandle(WindowHandle windowHandle): Debug("WindowUtils::IsWindowHandle() not implemented (always True)") return True + + @staticmethod + def InstallX11ErrorHandlers(): + pass diff --git a/src/window_utils_win.pyx b/src/window_utils_win.pyx index a1b61387..0dd9ee70 100644 --- a/src/window_utils_win.pyx +++ b/src/window_utils_win.pyx @@ -145,3 +145,7 @@ class WindowUtils(object): return bool(IsWindow(windowHandle)) ELSE: return False + + @staticmethod + def InstallX11ErrorHandlers(): + pass diff --git a/tools/build.py b/tools/build.py index 9b6db351..7debfffc 100644 --- a/tools/build.py +++ b/tools/build.py @@ -19,6 +19,7 @@ Usage: build.py VERSION [--rebuild-cpp] [--fast] [--clean] [--kivy] + [--hello-world] Options: VERSION Version number eg. 50.0 @@ -26,6 +27,7 @@ --fast Fast mode --clean Clean C++ projects build files on Linux/Mac --kivy Run only Kivy example + --hello-world Run only Hello World example """ # --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) @@ -70,6 +72,7 @@ FAST_FLAG = False CLEAN_FLAG = False KIVY_FLAG = False +HELLO_WORLD_FLAG = False REBUILD_CPP = False # First run @@ -107,7 +110,7 @@ def main(): def command_line_args(): - global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG,\ + global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG, HELLO_WORLD_FLAG, \ REBUILD_CPP, VERSION, NO_RUN_EXAMPLES VERSION = get_version_from_command_line_args(__file__) @@ -141,7 +144,12 @@ def command_line_args(): # --kivy if "--kivy" in sys.argv: KIVY_FLAG = True - print("[build.py] KIVY mode enabled") + print("[build.py] KIVY example") + + # --kivy + if "--hello-world" in sys.argv: + HELLO_WORLD_FLAG = True + print("[build.py] HELLO WORLD example") # --rebuild-cpp # Rebuild c++ projects @@ -848,12 +856,16 @@ def install_and_run(): if not NO_RUN_EXAMPLES: print("[build.py] Run examples") os.chdir(EXAMPLES_DIR) - kivy_flag = "--kivy" if KIVY_FLAG else "" + flags = "" + if KIVY_FLAG: + flags += " --kivy" + if HELLO_WORLD_FLAG: + flags += " --hello-world" run_examples = os.path.join(TOOLS_DIR, "run_examples.py") - command = ("\"{python}\" {run_examples} {kivy_flag}" + command = ("\"{python}\" {run_examples} {flags}" .format(python=sys.executable, run_examples=run_examples, - kivy_flag=kivy_flag)) + flags=flags)) ret = os.system(command) if ret != 0: print("[build.py] ERROR while running examples") diff --git a/tools/run_examples.py b/tools/run_examples.py index 6562af66..a4a1f0bb 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -20,6 +20,19 @@ def main(): os.chdir(EXAMPLES_DIR) + # When importing Kivy package there can't be any flags unknown to Kivy, + # use sys.argv.remove to remove them. + + kivy_flag = False + if "--kivy" in sys.argv: + sys.argv.remove("--kivy") + kivy_flag = True + + hello_world_flag = False + if "--hello-world" in sys.argv: + sys.argv.remove("--hello-world") + hello_world_flag = True + packages = check_installed_packages() examples = list() examples.append("hello_world.py") @@ -83,15 +96,20 @@ def main(): print(["run_examples.py] PASS: tkinter_.py (tkinter not installed)"]) passed.append("tkinter_.py") - # kivy if LINUX and packages["kivy"] and packages["gtk"]: - if "--kivy" in sys.argv: - # When --kivy flag passed run only Kivy example + # When --kivy flag passed run only Kivy example + if kivy_flag: examples = list() passed = list() examples.append("{linux_dir}/binaries_64bit/kivy_.py" .format(linux_dir=LINUX_DIR)) + # When --hello-world flag is passed run only hello_world.py example + if hello_world_flag: + examples = list() + passed = list() + examples.append("hello_world.py") + # Run all for example in examples: print("[run_examples.py] Running '{example}'..."