// Copyright (c) 2012 The Chromium Embedded Framework Authors. // Portions copyright (c) 2011 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. #include "libcef/browser/browser_host_impl.h" #include #include #include "libcef/browser/thread_util.h" #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/web_contents_view.h" #include "content/public/common/file_chooser_params.h" #include "content/public/common/renderer_preferences.h" #include "grit/cef_strings.h" #include "grit/ui_strings.h" #include "net/base/mime_util.h" #include "third_party/WebKit/public/web/gtk/WebInputEventFactory.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/base/l10n/l10n_util.h" namespace { void DestroyBrowser(CefRefPtr browser) { // Force the browser to be destroyed and release the reference added in // PlatformCreateWindow(). browser->WindowDestroyed(); } void browser_destroy(GtkWidget* widget, CefBrowserHostImpl* browser) { // Destroy the browser host after window destruction is complete. CEF_POST_TASK(CEF_UIT, base::Bind(DestroyBrowser, browser)); } void window_destroy(GtkWidget* widget, gpointer data) { } gboolean window_delete_event(GtkWidget* widget, GdkEvent* event, CefBrowserHostImpl* browser) { // Protect against multiple requests to close while the close is pending. if (browser && browser->destruction_state() <= CefBrowserHostImpl::DESTRUCTION_STATE_PENDING) { if (browser->destruction_state() == CefBrowserHostImpl::DESTRUCTION_STATE_NONE) { // Request that the browser close. browser->CloseBrowser(false); } // Cancel the close. return TRUE; } // Allow the close. return FALSE; } 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; int string_id; } kWildCardMimeTypes[] = { { "audio", IDS_APP_AUDIO_FILES }, { "image", IDS_APP_IMAGE_FILES }, { "text", IDS_APP_TEXT_FILES }, { "video", IDS_APP_VIDEO_FILES }, }; for (size_t i = 0; i < sizeof(kWildCardMimeTypes) / sizeof(kWildCardMimeTypes[0]); ++i) { if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*") return l10n_util::GetStringUTF8(kWildCardMimeTypes[i].string_id); } return std::string(); } void AddFiltersForAcceptTypes(GtkFileChooser* chooser, const std::vector& accept_types, bool include_all_files) { bool has_filter = false; for (size_t i = 0; i < accept_types.size(); ++i) { std::string ascii_type = base::UTF16ToASCII(accept_types[i]); if (ascii_type.length()) { // Just treat as extension if contains '.' as the first character. if (ascii_type[0] == '.') { GtkFileFilter* filter = gtk_file_filter_new(); std::string pattern = "*" + ascii_type; gtk_file_filter_add_pattern(filter, pattern.c_str()); gtk_file_filter_set_name(filter, pattern.c_str()); gtk_file_chooser_add_filter(chooser, filter); if (!has_filter) has_filter = true; } else { // Otherwise convert mime type to one or more extensions. GtkFileFilter* filter = NULL; std::string description = GetDescriptionFromMimeType(ascii_type); bool description_from_ext = description.empty(); std::vector ext; net::GetExtensionsForMimeType(ascii_type, &ext); for (size_t x = 0; x < ext.size(); ++x) { if (!filter) filter = gtk_file_filter_new(); std::string pattern = "*." + ext[x]; gtk_file_filter_add_pattern(filter, pattern.c_str()); if (description_from_ext) { if (x != 0) description += ";"; description += pattern; } } if (filter) { gtk_file_filter_set_name(filter, description.c_str()); gtk_file_chooser_add_filter(chooser, filter); if (!has_filter) has_filter = true; } } } } // 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, l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str()); gtk_file_chooser_add_filter(chooser, filter); } } bool RunFileDialog(const content::FileChooserParams& params, CefWindowHandle widget, std::vector* files) { GtkFileChooserAction action; const gchar* accept_button; if (params.mode == content::FileChooserParams::Open || params.mode == content::FileChooserParams::OpenMultiple) { action = GTK_FILE_CHOOSER_ACTION_OPEN; accept_button = GTK_STOCK_OPEN; } else if (params.mode == content::FileChooserParams::Save) { action = GTK_FILE_CHOOSER_ACTION_SAVE; accept_button = GTK_STOCK_SAVE; } else { NOTREACHED(); return false; } // Consider default file name if any. base::FilePath default_file_name(params.default_file_name); std::string base_name; if (!default_file_name.empty()) base_name = default_file_name.BaseName().value(); std::string title; if (!params.title.empty()) { title = base::UTF16ToUTF8(params.title); } else { int string_id = 0; switch (params.mode) { case content::FileChooserParams::Open: string_id = IDS_OPEN_FILE_DIALOG_TITLE; break; case content::FileChooserParams::OpenMultiple: string_id = IDS_OPEN_FILES_DIALOG_TITLE; break; case content::FileChooserParams::Save: string_id = IDS_SAVE_AS_DIALOG_TITLE; break; default: break; } title = l10n_util::GetStringUTF8(string_id); } GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(widget)); GtkWidget* dialog = gtk_file_chooser_dialog_new( title.c_str(), GTK_WINDOW(window), action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, NULL); if (params.mode == content::FileChooserParams::OpenMultiple) { gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); } else if (params.mode == content::FileChooserParams::Save) { gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); } if (params.mode == content::FileChooserParams::Save && !base_name.empty()) { gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), base_name.c_str()); } AddFiltersForAcceptTypes(GTK_FILE_CHOOSER(dialog), params.accept_types, true); bool success = false; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { if (params.mode == content::FileChooserParams::Open || params.mode == content::FileChooserParams::Save) { char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); files->push_back(base::FilePath(filename)); success = true; } else if (params.mode == content::FileChooserParams::OpenMultiple) { GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); if (filenames) { for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) { base::FilePath path(static_cast(iter->data)); g_free(iter->data); files->push_back(path); } g_slist_free(filenames); success = true; } } } gtk_widget_destroy(dialog); return success; } // Returns the number of seconds since system boot. long GetSystemUptime() { struct sysinfo info; if (sysinfo(&info) == 0) return info.uptime; return 0; } } // namespace bool CefBrowserHostImpl::PlatformCreateWindow() { GtkWidget* window; GtkWidget* parentView = window_info_.parent_widget; if (parentView == NULL) { // Create a new window. window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); parentView = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), parentView); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_widget_show_all(GTK_WIDGET(window)); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(window_destroy), NULL); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(window_delete_event), this); window_info_.parent_widget = parentView; } // Add a reference that will be released in the destroy handler. AddRef(); // Parent the TabContents to the browser window. window_info_.widget = web_contents_->GetView()->GetNativeView(); gtk_container_add(GTK_CONTAINER(window_info_.parent_widget), window_info_.widget); g_signal_connect(G_OBJECT(window_info_.widget), "destroy", G_CALLBACK(browser_destroy), this); // As an additional requirement on Linux, we must set the colors for the // render widgets in webkit. content::RendererPreferences* prefs = web_contents_->GetMutableRendererPrefs(); prefs->focus_ring_color = SkColorSetARGB(255, 229, 151, 0); prefs->thumb_active_color = SkColorSetRGB(244, 244, 244); prefs->thumb_inactive_color = SkColorSetRGB(234, 234, 234); prefs->track_color = SkColorSetRGB(211, 211, 211); prefs->active_selection_bg_color = SkColorSetRGB(30, 144, 255); prefs->active_selection_fg_color = SK_ColorWHITE; prefs->inactive_selection_bg_color = SkColorSetRGB(200, 200, 200); prefs->inactive_selection_fg_color = SkColorSetRGB(50, 50, 50); return true; } void CefBrowserHostImpl::PlatformCloseWindow() { if (window_info_.widget != NULL) { GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(window_info_.widget)); // Send the "delete_event" signal. GdkEvent event; memset(&event, 0, sizeof(GdkEvent)); event.any.type = GDK_DELETE; event.any.send_event = TRUE; event.any.window = window->window; gtk_main_do_event(&event); } } void CefBrowserHostImpl::PlatformSizeTo(int width, int height) { if (window_info_.widget != NULL) { GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(window_info_.widget)); gtk_widget_set_size_request(window, width, height); } } CefWindowHandle CefBrowserHostImpl::PlatformGetWindowHandle() { return window_info_.widget; } bool CefBrowserHostImpl::PlatformViewText(const std::string& text) { CEF_REQUIRE_UIT(); char buff[] = "/tmp/CEFSourceXXXXXX"; int fd = mkstemp(buff); if (fd == -1) return false; FILE* srcOutput = fdopen(fd, "w+"); if (!srcOutput) return false; if (fputs(text.c_str(), srcOutput) < 0) { fclose(srcOutput); return false; } fclose(srcOutput); std::string newName(buff); newName.append(".txt"); if (rename(buff, newName.c_str()) != 0) return false; std::string openCommand("xdg-open "); openCommand += newName; if (system(openCommand.c_str()) != 0) return false; return true; } void CefBrowserHostImpl::PlatformHandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) { // TODO(cef): Is something required here to handle shortcut keys? } void CefBrowserHostImpl::PlatformRunFileChooser( const content::FileChooserParams& params, RunFileChooserCallback callback) { std::vector files; if (params.mode == content::FileChooserParams::Open || params.mode == content::FileChooserParams::OpenMultiple || params.mode == content::FileChooserParams::Save) { ::RunFileDialog(params, PlatformGetWindowHandle(), &files); } else { NOTIMPLEMENTED(); } callback.Run(files); } void CefBrowserHostImpl::PlatformHandleExternalProtocol(const GURL& url) { } void CefBrowserHostImpl::PlatformTranslateKeyEvent( content::NativeWebKeyboardEvent& result, const CefKeyEvent& key_event) { // Use a synthetic GdkEventKey in order to obtain the windowsKeyCode member // from the NativeWebKeyboardEvent constructor. This is the only member // which cannot be easily translated (without hardcoding keyCodes). guint state = 0; if (key_event.modifiers & EVENTFLAG_SHIFT_DOWN) state |= GDK_SHIFT_MASK; if (key_event.modifiers & EVENTFLAG_CAPS_LOCK_ON) state |= GDK_LOCK_MASK; if (key_event.modifiers & EVENTFLAG_CONTROL_DOWN) state |= GDK_CONTROL_MASK; if (key_event.modifiers & EVENTFLAG_ALT_DOWN) state |= GDK_MOD1_MASK; if (key_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) state |= GDK_BUTTON1_MASK; if (key_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) state |= GDK_BUTTON2_MASK; if (key_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) state |= GDK_BUTTON3_MASK; GdkKeymap* keymap = gdk_keymap_get_for_display(gdk_display_get_default()); GdkKeymapKey *keys = NULL; gint n_keys = 0; if (gdk_keymap_get_entries_for_keyval(keymap, key_event.native_key_code, &keys, &n_keys)) { GdkEventKey event; event.type = GDK_KEY_PRESS; event.window = NULL; event.send_event = 0; event.time = 0; event.state = state; event.keyval = key_event.native_key_code; event.length = 0; event.string = NULL; event.hardware_keycode = keys[0].keycode; event.group = keys[0].group; event.is_modifier = 0; g_free(keys); result = content::NativeWebKeyboardEvent( reinterpret_cast(&event)); } result.timeStampSeconds = GetSystemUptime(); switch (key_event.type) { case KEYEVENT_RAWKEYDOWN: case KEYEVENT_KEYDOWN: result.type = blink::WebInputEvent::RawKeyDown; break; case KEYEVENT_KEYUP: result.type = blink::WebInputEvent::KeyUp; break; case KEYEVENT_CHAR: result.type = blink::WebInputEvent::Char; break; default: NOTREACHED(); } } void CefBrowserHostImpl::PlatformTranslateClickEvent( blink::WebMouseEvent& result, const CefMouseEvent& mouse_event, MouseButtonType type, bool mouseUp, int clickCount) { PlatformTranslateMouseEvent(result, mouse_event); switch (type) { case MBT_LEFT: result.type = mouseUp ? blink::WebInputEvent::MouseUp : blink::WebInputEvent::MouseDown; result.button = blink::WebMouseEvent::ButtonLeft; break; case MBT_MIDDLE: result.type = mouseUp ? blink::WebInputEvent::MouseUp : blink::WebInputEvent::MouseDown; result.button = blink::WebMouseEvent::ButtonMiddle; break; case MBT_RIGHT: result.type = mouseUp ? blink::WebInputEvent::MouseUp : blink::WebInputEvent::MouseDown; result.button = blink::WebMouseEvent::ButtonRight; break; default: NOTREACHED(); } result.clickCount = clickCount; } void CefBrowserHostImpl::PlatformTranslateMoveEvent( blink::WebMouseEvent& result, const CefMouseEvent& mouse_event, bool mouseLeave) { PlatformTranslateMouseEvent(result, mouse_event); if (!mouseLeave) { result.type = blink::WebInputEvent::MouseMove; if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonLeft; else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonMiddle; else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonRight; else result.button = blink::WebMouseEvent::ButtonNone; } else { result.type = blink::WebInputEvent::MouseLeave; result.button = blink::WebMouseEvent::ButtonNone; } result.clickCount = 0; } void CefBrowserHostImpl::PlatformTranslateWheelEvent( blink::WebMouseWheelEvent& result, const CefMouseEvent& mouse_event, int deltaX, int deltaY) { result = blink::WebMouseWheelEvent(); PlatformTranslateMouseEvent(result, mouse_event); result.type = blink::WebInputEvent::MouseWheel; static const double scrollbarPixelsPerGtkTick = 40.0; result.deltaX = deltaX; result.deltaY = deltaY; result.wheelTicksX = result.deltaX / scrollbarPixelsPerGtkTick; result.wheelTicksY = result.deltaY / scrollbarPixelsPerGtkTick; result.hasPreciseScrollingDeltas = true; // Unless the phase and momentumPhase are passed in as parameters to this // function, there is no way to know them result.phase = blink::WebMouseWheelEvent::PhaseNone; result.momentumPhase = blink::WebMouseWheelEvent::PhaseNone; if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonLeft; else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonMiddle; else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) result.button = blink::WebMouseEvent::ButtonRight; else result.button = blink::WebMouseEvent::ButtonNone; } void CefBrowserHostImpl::PlatformTranslateMouseEvent( blink::WebMouseEvent& result, const CefMouseEvent& mouse_event) { // position result.x = mouse_event.x; result.y = mouse_event.y; result.windowX = result.x; result.windowY = result.y; result.globalX = result.x; result.globalY = result.y; // global position GtkWidget* window = gtk_widget_get_toplevel(GetWindowHandle()); GdkWindow* gdk_window = gtk_widget_get_window(window); gint xorigin, yorigin; gdk_window_get_root_origin(gdk_window, &xorigin, &yorigin); result.globalX = xorigin + result.x; result.globalY = yorigin + result.y; // modifiers result.modifiers |= TranslateModifiers(mouse_event.modifiers); // timestamp result.timeStampSeconds = GetSystemUptime(); }