From c5951b67bd59014f249f75912db153c8a3b3aadf Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 09:23:22 +0100 Subject: [PATCH] Provide default implementation for js and file dialogs on Linux (#241)... In wxpython.py and gtk2.py examples the js alert dialog when run from a popup gives focus to the main window. The popup window is created internally by CEF and this probably needs to be changed so that popups are created by wx in OnBeforePopup and this will resolve issue with alert focus. In qt4.py and tkinter.py and hello_world.py examples focus works fine in the popup window. Add --hello-world flag to build.py and run_examples.py tools. --- src/client_handler/Makefile | 4 +- src/client_handler/client_handler.cpp | 1 + src/client_handler/client_handler.h | 8 + src/client_handler/dialog_handler.cpp | 37 ++ src/client_handler/dialog_handler.h | 38 ++ src/client_handler/dialog_handler_gtk.cpp | 464 ++++++++++++++++++++++ src/client_handler/dialog_handler_gtk.h | 59 +++ src/client_handler/js_dialog_handler.cpp | 32 +- src/client_handler/js_dialog_handler.h | 12 +- src/compile_time_constants.pxi | 2 +- src/handlers/lifespan_handler.pyx | 9 + src/window_utils_mac.pyx | 4 + src/window_utils_win.pyx | 4 + tools/build.py | 22 +- tools/run_examples.py | 24 +- 15 files changed, 704 insertions(+), 16 deletions(-) create mode 100644 src/client_handler/dialog_handler.cpp create mode 100644 src/client_handler/dialog_handler.h create mode 100644 src/client_handler/dialog_handler_gtk.cpp create mode 100644 src/client_handler/dialog_handler_gtk.h 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}'..."