diff --git a/app/atom_main.cc b/app/atom_main.cc index c673fdab67de..619138618c5c 100644 --- a/app/atom_main.cc +++ b/app/atom_main.cc @@ -20,9 +20,12 @@ #include "common/crash_reporter/win/crash_service_main.h" #include "content/public/app/startup_helper_win.h" #include "sandbox/win/src/sandbox_types.h" -#else // defined(OS_WIN) +#elif defined(OS_LINUX) // defined(OS_WIN) +#include "app/atom_main_delegate.h" // NOLINT +#include "content/public/app/content_main.h" +#else // defined(OS_LINUX) #include "app/atom_library_main.h" -#endif // defined(OS_MACOSX) || defined(OS_LINUX) +#endif // defined(OS_MACOSX) // Declaration of node::Start. namespace node { @@ -98,7 +101,18 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { return content::ContentMain(instance, &sandbox_info, &delegate); } -#else // defined(OS_WIN) +#elif defined(OS_LINUX) // defined(OS_WIN) + +int main(int argc, const char* argv[]) { + char* node_indicator = getenv("ATOM_SHELL_INTERNAL_RUN_AS_NODE"); + if (node_indicator != NULL && strcmp(node_indicator, "1") == 0) + return node::Start(argc, const_cast(argv)); + + atom::AtomMainDelegate delegate; + return content::ContentMain(argc, argv, &delegate); +} + +#else // defined(OS_LINUX) int main(int argc, const char* argv[]) { char* node_indicator = getenv("ATOM_SHELL_INTERNAL_RUN_AS_NODE"); @@ -108,4 +122,4 @@ int main(int argc, const char* argv[]) { return AtomMain(argc, argv); } -#endif // defined(OS_MACOSX) || defined(OS_LINUX) +#endif // defined(OS_MACOSX) diff --git a/app/atom_main_delegate.cc b/app/atom_main_delegate.cc index af5d4b607957..5a529b26fe89 100644 --- a/app/atom_main_delegate.cc +++ b/app/atom_main_delegate.cc @@ -60,8 +60,8 @@ void AtomMainDelegate::PreSandboxStartup() { std::string process_type = command_line->GetSwitchValueASCII( switches::kProcessType); - // Don't append arguments for renderer process. - if (process_type == switches::kRendererProcess) + // Only append arguments for browser process. + if (!process_type.empty()) return; // Add a flag to mark the start of switches added by atom-shell. diff --git a/atom.gyp b/atom.gyp index 646869dff1d7..a17155bb3760 100644 --- a/atom.gyp +++ b/atom.gyp @@ -51,6 +51,8 @@ 'browser/api/atom_api_event.h', 'browser/api/atom_api_menu.cc', 'browser/api/atom_api_menu.h', + 'browser/api/atom_api_menu_gtk.cc', + 'browser/api/atom_api_menu_gtk.h', 'browser/api/atom_api_menu_mac.h', 'browser/api/atom_api_menu_mac.mm', 'browser/api/atom_api_menu_win.cc', @@ -66,6 +68,7 @@ 'browser/auto_updater.cc', 'browser/auto_updater.h', 'browser/auto_updater_delegate.h', + 'browser/auto_updater_linux.cc', 'browser/auto_updater_mac.mm', 'browser/auto_updater_win.cc', 'browser/atom_application_mac.h', @@ -83,11 +86,14 @@ 'browser/atom_javascript_dialog_manager.h', 'browser/browser.cc', 'browser/browser.h', + 'browser/browser_linux.cc', 'browser/browser_mac.mm', 'browser/browser_win.cc', 'browser/browser_observer.h', 'browser/native_window.cc', 'browser/native_window.h', + 'browser/native_window_gtk.cc', + 'browser/native_window_gtk.h', 'browser/native_window_mac.h', 'browser/native_window_mac.mm', 'browser/native_window_win.cc', @@ -103,6 +109,7 @@ 'browser/net/url_request_string_job.h', 'browser/ui/accelerator_util.cc', 'browser/ui/accelerator_util.h', + 'browser/ui/accelerator_util_gtk.cc', 'browser/ui/accelerator_util_mac.mm', 'browser/ui/accelerator_util_win.cc', 'browser/ui/cocoa/atom_menu_controller.h', @@ -112,9 +119,17 @@ 'browser/ui/cocoa/nsalert_synchronous_sheet.h', 'browser/ui/cocoa/nsalert_synchronous_sheet.mm', 'browser/ui/file_dialog.h', + 'browser/ui/file_dialog_gtk.cc', 'browser/ui/file_dialog_mac.mm', 'browser/ui/file_dialog_win.cc', + 'browser/ui/gtk/gtk_custom_menu.cc', + 'browser/ui/gtk/gtk_custom_menu.h', + 'browser/ui/gtk/gtk_custom_menu_item.cc', + 'browser/ui/gtk/gtk_custom_menu_item.h', + 'browser/ui/gtk/gtk_window_util.cc', + 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/message_box.h', + 'browser/ui/message_box_gtk.cc', 'browser/ui/message_box_mac.mm', 'browser/ui/message_box_win.cc', 'browser/ui/win/menu_2.cc', @@ -147,6 +162,8 @@ 'common/api/object_life_monitor.h', 'common/crash_reporter/crash_reporter.cc', 'common/crash_reporter/crash_reporter.h', + 'common/crash_reporter/crash_reporter_linux.cc', + 'common/crash_reporter/crash_reporter_linux.h', 'common/crash_reporter/crash_reporter_mac.h', 'common/crash_reporter/crash_reporter_mac.mm', 'common/crash_reporter/crash_reporter_win.cc', @@ -157,8 +174,11 @@ 'common/crash_reporter/win/crash_service_main.h', 'common/draggable_region.cc', 'common/draggable_region.h', + 'common/linux/application_info.cc', 'common/node_bindings.cc', 'common/node_bindings.h', + 'common/node_bindings_linux.cc', + 'common/node_bindings_linux.h', 'common/node_bindings_mac.cc', 'common/node_bindings_mac.h', 'common/node_bindings_win.cc', @@ -166,6 +186,7 @@ 'common/options_switches.cc', 'common/options_switches.h', 'common/platform_util.h', + 'common/platform_util_linux.cc', 'common/platform_util_mac.mm', 'common/platform_util_win.cc', 'common/swap_or_assign.h', @@ -209,9 +230,8 @@ ], 'configurations': { 'Debug': { - 'defines': [ - 'DEBUG', - ], + 'defines': [ 'DEBUG', '_DEBUG' ], + 'cflags': [ '-g', '-O0' ], }, }, }, @@ -306,6 +326,24 @@ }, ], }], # OS=="win" + ['OS=="linux"', { + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)', + 'files': [ + '<(libchromiumcontent_library_dir)/libchromiumcontent.so', + '<(libchromiumcontent_library_dir)/libffmpegsumo.so', + '<(libchromiumcontent_resources_dir)/content_shell.pak', + ], + }, + { + 'destination': '<(PRODUCT_DIR)/resources/browser', + 'files': [ + 'browser/default_app', + ] + }, + ], + }], # OS=="linux" ], }, # target <(project_name) { @@ -353,12 +391,24 @@ 'vendor/breakpad/breakpad.gyp:breakpad_handler', 'vendor/breakpad/breakpad.gyp:breakpad_sender', ], - }], + }], # OS=="win" ['OS=="mac"', { 'dependencies': [ 'vendor/breakpad/breakpad.gyp:breakpad', ], - }], + }], # OS=="mac" + ['OS=="linux"', { + 'link_settings': { + 'ldflags': [ + # Make binary search for libraries under current directory, so we + # don't have to manually set $LD_LIBRARY_PATH: + # http://serverfault.com/questions/279068/cant-find-so-in-the-same-directory-as-the-executable + '-rpath \$$ORIGIN', + # Make native module dynamic loading work. + '-rdynamic', + ], + }, + }], # OS=="linux" ], }, # target <(product_name)_lib { @@ -385,8 +435,7 @@ '<(RULE_INPUT_PATH)', '<(PRODUCT_DIR)/<(product_name).app/Contents/Resources/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', ], - }], # OS=="mac" - ['OS=="win"', { + },{ # OS=="mac" 'outputs': [ '<(PRODUCT_DIR)/resources/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', ], @@ -396,7 +445,7 @@ '<(RULE_INPUT_PATH)', '<(PRODUCT_DIR)/resources/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', ], - }], # OS=="win" + }], # OS=="win" or OS=="linux" ], }, ], diff --git a/browser/api/atom_api_menu.cc b/browser/api/atom_api_menu.cc index cc3a514d5690..4067829166c8 100644 --- a/browser/api/atom_api_menu.cc +++ b/browser/api/atom_api_menu.cc @@ -340,7 +340,7 @@ void Menu::Initialize(v8::Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "popup", Popup); -#if defined(OS_WIN) +#if defined(OS_WIN) || defined(TOOLKIT_GTK) NODE_SET_PROTOTYPE_METHOD(t, "attachToWindow", AttachToWindow); #endif diff --git a/browser/api/atom_api_menu.h b/browser/api/atom_api_menu.h index d642892f576a..25f94b835487 100644 --- a/browser/api/atom_api_menu.h +++ b/browser/api/atom_api_menu.h @@ -69,7 +69,7 @@ class Menu : public EventEmitter, static void Popup(const v8::FunctionCallbackInfo& args); -#if defined(OS_WIN) +#if defined(OS_WIN) || defined(TOOLKIT_GTK) static void AttachToWindow(const v8::FunctionCallbackInfo& args); #elif defined(OS_MACOSX) static void SetApplicationMenu( diff --git a/browser/api/atom_api_menu_gtk.cc b/browser/api/atom_api_menu_gtk.cc new file mode 100644 index 000000000000..041c9f282bf4 --- /dev/null +++ b/browser/api/atom_api_menu_gtk.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/api/atom_api_menu_gtk.h" + +namespace atom { + +namespace api { + +MenuGtk::MenuGtk(v8::Handle wrapper) + : Menu(wrapper) { +} + +MenuGtk::~MenuGtk() { +} + +void MenuGtk::Popup(NativeWindow* native_window) { +} + +// static +void Menu::AttachToWindow(const v8::FunctionCallbackInfo& args) { +} + +// static +Menu* Menu::Create(v8::Handle wrapper) { + return new MenuGtk(wrapper); +} + +} // namespace api + +} // namespace atom diff --git a/browser/api/atom_api_menu_gtk.h b/browser/api/atom_api_menu_gtk.h new file mode 100644 index 000000000000..88e05105a7f7 --- /dev/null +++ b/browser/api/atom_api_menu_gtk.h @@ -0,0 +1,30 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_MENU_GTK_H_ +#define ATOM_BROWSER_API_ATOM_API_MENU_GTK_H_ + +#include "browser/api/atom_api_menu.h" + +namespace atom { + +namespace api { + +class MenuGtk : public Menu { + public: + explicit MenuGtk(v8::Handle wrapper); + virtual ~MenuGtk(); + + protected: + virtual void Popup(NativeWindow* window) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MenuGtk); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_MENU_GTK_H_ diff --git a/browser/api/atom_api_protocol.cc b/browser/api/atom_api_protocol.cc index 107477d0eb29..1499f38cff7f 100644 --- a/browser/api/atom_api_protocol.cc +++ b/browser/api/atom_api_protocol.cc @@ -368,6 +368,12 @@ void Protocol::Initialize(v8::Handle target) { // Remember the protocol object, used for emitting event later. g_protocol_object.reset(target); +#if defined(OS_LINUX) + // Make sure the job factory has been created. + AtomBrowserContext::Get()->url_request_context_getter()-> + GetURLRequestContext(); +#endif + NODE_SET_METHOD(target, "registerProtocol", RegisterProtocol); NODE_SET_METHOD(target, "unregisterProtocol", UnregisterProtocol); NODE_SET_METHOD(target, "isHandledProtocol", IsHandledProtocol); diff --git a/browser/api/lib/browser-window.coffee b/browser/api/lib/browser-window.coffee index 8503d8a4378d..38946c5500af 100644 --- a/browser/api/lib/browser-window.coffee +++ b/browser/api/lib/browser-window.coffee @@ -34,7 +34,8 @@ BrowserWindow::restart = -> @loadUrl(@getUrl()) BrowserWindow::setMenu = (menu) -> - throw new Error('BrowserWindow.setMenu is only available on Windows') unless process.platform is 'win32' + if process.platform is 'darwin' + throw new Error('BrowserWindow.setMenu is not available on OS X') throw new TypeError('Invalid menu') unless menu?.constructor?.name is 'Menu' diff --git a/browser/auto_updater_linux.cc b/browser/auto_updater_linux.cc new file mode 100644 index 000000000000..ccdc4ed8e008 --- /dev/null +++ b/browser/auto_updater_linux.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/auto_updater.h" + +namespace auto_updater { + +// static +void AutoUpdater::SetFeedURL(const std::string& url) { +} + +// static +void AutoUpdater::CheckForUpdates() { +} + +} // namespace auto_updater diff --git a/browser/browser_linux.cc b/browser/browser_linux.cc new file mode 100644 index 000000000000..fcff198fcde6 --- /dev/null +++ b/browser/browser_linux.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/browser.h" + +#include + +#include "browser/native_window.h" +#include "browser/window_list.h" +#include "common/atom_version.h" + +namespace atom { + +void Browser::Terminate() { + is_quiting_ = true; + exit(0); +} + +void Browser::Focus() { + // Focus on the first visible window. + WindowList* list = WindowList::GetInstance(); + for (WindowList::iterator iter = list->begin(); iter != list->end(); ++iter) { + NativeWindow* window = *iter; + if (window->IsVisible()) { + window->Focus(true); + break; + } + } +} + +std::string Browser::GetExecutableFileVersion() const { + return ATOM_VERSION_STRING; +} + +std::string Browser::GetExecutableFileProductName() const { + return "Atom-Shell"; +} + +void Browser::CancelQuit() { + // No way to cancel quit on Linux. +} + +} // namespace atom diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc new file mode 100644 index 000000000000..8b9ecc217c8b --- /dev/null +++ b/browser/native_window_gtk.cc @@ -0,0 +1,429 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/native_window_gtk.h" + +#include "base/values.h" +#include "browser/ui/gtk/gtk_window_util.h" +#include "common/draggable_region.h" +#include "common/options_switches.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/renderer_preferences.h" +#include "ui/base/x/x11_util.h" +#include "ui/gfx/gtk_util.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/skia_utils_gtk.h" + +namespace atom { + +namespace { + +// Dividing GTK's cursor blink cycle time (in milliseconds) by this value yields +// an appropriate value for content::RendererPreferences::caret_blink_interval. +// This matches the logic in the WebKit GTK port. +const double kGtkCursorBlinkCycleFactor = 2000.0; + +} // namespace + +NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, + base::DictionaryValue* options) + : NativeWindow(web_contents, options), + window_(GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL))), + state_(GDK_WINDOW_STATE_WITHDRAWN), + is_always_on_top_(false) { + gtk_container_add(GTK_CONTAINER(window_), + GetWebContents()->GetView()->GetNativeView()); + + int width = 800, height = 600; + options->GetInteger(switches::kWidth, &width); + options->GetInteger(switches::kHeight, &height); + gtk_window_set_default_size(window_, width, height); + + if (!icon_.IsEmpty()) + gtk_window_set_icon(window_, icon_.ToGdkPixbuf()); + + // In some (older) versions of compiz, raising top-level windows when they + // are partially off-screen causes them to get snapped back on screen, not + // always even on the current virtual desktop. If we are running under + // compiz, suppress such raises, as they are not necessary in compiz anyway. + if (ui::GuessWindowManager() == ui::WM_COMPIZ) + suppress_window_raise_ = true; + + g_signal_connect(window_, "delete-event", + G_CALLBACK(OnWindowDeleteEventThunk), this); + g_signal_connect(window_, "focus-out-event", + G_CALLBACK(OnFocusOutThunk), this); + + if (!has_frame_) { + gtk_window_set_decorated(window_, false); + + g_signal_connect(window_, "motion-notify-event", + G_CALLBACK(OnMouseMoveEventThunk), this); + g_signal_connect(window_, "button-press-event", + G_CALLBACK(OnButtonPressThunk), this); + } + + SetWebKitColorStyle(); +} + +NativeWindowGtk::~NativeWindowGtk() { + if (window_) + gtk_widget_destroy(GTK_WIDGET(window_)); +} + +void NativeWindowGtk::Close() { + CloseWebContents(); +} + +void NativeWindowGtk::CloseImmediately() { + gtk_widget_destroy(GTK_WIDGET(window_)); + window_ = NULL; +} + +void NativeWindowGtk::Move(const gfx::Rect& pos) { + gtk_window_move(window_, pos.x(), pos.y()); + gtk_window_resize(window_, pos.width(), pos.height()); +} + +void NativeWindowGtk::Focus(bool focus) { + if (!IsVisible()) + return; + + if (focus) + gtk_window_present(window_); + else + gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); +} + +bool NativeWindowGtk::IsFocused() { + return gtk_window_is_active(window_); +} + +void NativeWindowGtk::Show() { + gtk_widget_show_all(GTK_WIDGET(window_)); +} + +void NativeWindowGtk::Hide() { + gtk_widget_hide(GTK_WIDGET(window_)); +} + +bool NativeWindowGtk::IsVisible() { + return gtk_widget_get_visible(GTK_WIDGET(window_)); +} + +void NativeWindowGtk::Maximize() { + gtk_window_maximize(window_); +} + +void NativeWindowGtk::Unmaximize() { + gtk_window_unmaximize(window_); +} + +void NativeWindowGtk::Minimize() { + gtk_window_iconify(window_); +} + +void NativeWindowGtk::Restore() { + gtk_window_present(window_); +} + +void NativeWindowGtk::SetFullscreen(bool fullscreen) { + if (fullscreen) + gtk_window_fullscreen(window_); + else + gtk_window_unfullscreen(window_); +} + +bool NativeWindowGtk::IsFullscreen() { + return state_ & GDK_WINDOW_STATE_FULLSCREEN; +} + +void NativeWindowGtk::SetSize(const gfx::Size& size) { + gtk_window_resize(window_, size.width(), size.height()); +} + +gfx::Size NativeWindowGtk::GetSize() { + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + + GdkRectangle frame_extents; + gdk_window_get_frame_extents(gdk_window, &frame_extents); + + return gfx::Size(frame_extents.width, frame_extents.height); +} + +void NativeWindowGtk::SetMinimumSize(const gfx::Size& size) { + minimum_size_ = size; + + GdkGeometry geometry = { 0 }; + geometry.min_width = size.width(); + geometry.min_height = size.height(); + int hints = GDK_HINT_POS | GDK_HINT_MIN_SIZE; + gtk_window_set_geometry_hints( + window_, GTK_WIDGET(window_), &geometry, (GdkWindowHints)hints); +} + +gfx::Size NativeWindowGtk::GetMinimumSize() { + return minimum_size_; +} + +void NativeWindowGtk::SetMaximumSize(const gfx::Size& size) { + maximum_size_ = size; + + GdkGeometry geometry = { 0 }; + geometry.max_width = size.width(); + geometry.max_height = size.height(); + int hints = GDK_HINT_POS | GDK_HINT_MAX_SIZE; + gtk_window_set_geometry_hints( + window_, GTK_WIDGET(window_), &geometry, (GdkWindowHints)hints); +} + +gfx::Size NativeWindowGtk::GetMaximumSize() { + return maximum_size_; +} + +void NativeWindowGtk::SetResizable(bool resizable) { + // Should request widget size after setting unresizable, otherwise the + // window will shrink to a very small size. + if (!IsResizable()) { + gint width, height; + gtk_window_get_size(window_, &width, &height); + gtk_widget_set_size_request(GTK_WIDGET(window_), width, height); + } + + gtk_window_set_resizable(window_, resizable); +} + +bool NativeWindowGtk::IsResizable() { + return gtk_window_get_resizable(window_); +} + +void NativeWindowGtk::SetAlwaysOnTop(bool top) { + is_always_on_top_ = top; + gtk_window_set_keep_above(window_, top ? TRUE : FALSE); +} + +bool NativeWindowGtk::IsAlwaysOnTop() { + return is_always_on_top_; +} + +void NativeWindowGtk::Center() { + gtk_window_set_position(window_, GTK_WIN_POS_CENTER); +} + +void NativeWindowGtk::SetPosition(const gfx::Point& position) { + gtk_window_move(window_, position.x(), position.y()); +} + +gfx::Point NativeWindowGtk::GetPosition() { + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + + GdkRectangle frame_extents; + gdk_window_get_frame_extents(gdk_window, &frame_extents); + + return gfx::Point(frame_extents.x, frame_extents.y); +} + +void NativeWindowGtk::SetTitle(const std::string& title) { + gtk_window_set_title(window_, title.c_str()); +} + +std::string NativeWindowGtk::GetTitle() { + return gtk_window_get_title(window_); +} + +void NativeWindowGtk::FlashFrame(bool flash) { + gtk_window_set_urgency_hint(window_, flash); +} + +void NativeWindowGtk::SetKiosk(bool kiosk) { + SetFullscreen(kiosk); +} + +bool NativeWindowGtk::IsKiosk() { + return IsFullscreen(); +} + +bool NativeWindowGtk::HasModalDialog() { + // FIXME(zcbenz): Implement me. + return false; +} + +gfx::NativeWindow NativeWindowGtk::GetNativeWindow() { + return window_; +} + +void NativeWindowGtk::UpdateDraggableRegions( + const std::vector& regions) { + // Draggable region is not supported for non-frameless window. + if (has_frame_) + return; + + SkRegion draggable_region; + + // By default, the whole window is non-draggable. We need to explicitly + // include those draggable regions. + for (std::vector::const_iterator iter = + regions.begin(); + iter != regions.end(); ++iter) { + const DraggableRegion& region = *iter; + draggable_region.op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + + draggable_region_ = draggable_region; +} + +void NativeWindowGtk::SetWebKitColorStyle() { + content::RendererPreferences* prefs = + GetWebContents()->GetMutableRendererPrefs(); + GtkStyle* frame_style = gtk_rc_get_style(GTK_WIDGET(window_)); + prefs->focus_ring_color = + gfx::GdkColorToSkColor(frame_style->bg[GTK_STATE_SELECTED]); + prefs->thumb_active_color = SkColorSetRGB(244, 244, 244); + prefs->thumb_inactive_color = SkColorSetRGB(234, 234, 234); + prefs->track_color = SkColorSetRGB(211, 211, 211); + + GtkWidget* url_entry = gtk_entry_new(); + GtkStyle* entry_style = gtk_rc_get_style(url_entry); + prefs->active_selection_bg_color = + gfx::GdkColorToSkColor(entry_style->base[GTK_STATE_SELECTED]); + prefs->active_selection_fg_color = + gfx::GdkColorToSkColor(entry_style->text[GTK_STATE_SELECTED]); + prefs->inactive_selection_bg_color = + gfx::GdkColorToSkColor(entry_style->base[GTK_STATE_ACTIVE]); + prefs->inactive_selection_fg_color = + gfx::GdkColorToSkColor(entry_style->text[GTK_STATE_ACTIVE]); + gtk_widget_destroy(url_entry); + + const base::TimeDelta cursor_blink_time = gfx::GetCursorBlinkCycle(); + prefs->caret_blink_interval = + cursor_blink_time.InMilliseconds() ? + cursor_blink_time.InMilliseconds() / kGtkCursorBlinkCycleFactor : + 0; +} + +bool NativeWindowGtk::IsMaximized() const { + return state_ & GDK_WINDOW_STATE_MAXIMIZED; +} + +bool NativeWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { + if (has_frame_) + return false; + + if (IsMaximized() || IsFullscreen()) + return false; + + return gtk_window_util::GetWindowEdge(GetSize(), 0, x, y, edge); +} + +gboolean NativeWindowGtk::OnWindowDeleteEvent(GtkWidget* widget, + GdkEvent* event) { + Close(); + return TRUE; +} + +gboolean NativeWindowGtk::OnFocusOut(GtkWidget* window, GdkEventFocus*) { + NotifyWindowBlur(); + return FALSE; +} + +gboolean NativeWindowGtk::OnWindowState(GtkWidget* window, + GdkEventWindowState* event) { + state_ = event->new_window_state; + return FALSE; +} + +gboolean NativeWindowGtk::OnMouseMoveEvent(GtkWidget* widget, + GdkEventMotion* event) { + if (!IsResizable()) + return FALSE; + + int win_x, win_y; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + gdk_window_get_origin(gdk_window, &win_x, &win_y); + gfx::Point point(static_cast(event->x_root - win_x), + static_cast(event->y_root - win_y)); + + // Update the cursor if we're on the custom frame border. + GdkWindowEdge edge; + bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); + GdkCursorType new_cursor = GDK_LAST_CURSOR; + if (has_hit_edge) + new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); + + GdkCursorType last_cursor = GDK_LAST_CURSOR; + if (frame_cursor_) + last_cursor = frame_cursor_->type; + + if (last_cursor != new_cursor) { + frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), + frame_cursor_); + } + return FALSE; +} + +gboolean NativeWindowGtk::OnButtonPress(GtkWidget* widget, + GdkEventButton* event) { + // Make the button press coordinate relative to the browser window. + int win_x, win_y; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + gdk_window_get_origin(gdk_window, &win_x, &win_y); + + bool resizable = IsResizable(); + GdkWindowEdge edge; + gfx::Point point(static_cast(event->x_root - win_x), + static_cast(event->y_root - win_y)); + bool has_hit_edge = resizable && GetWindowEdge(point.x(), point.y(), &edge); + bool has_hit_titlebar = !draggable_region_.isEmpty() && + draggable_region_.contains(event->x, event->y); + + if (event->button == 1) { + if (GDK_BUTTON_PRESS == event->type) { + // Raise the window after a click on either the titlebar or the border to + // match the behavior of most window managers, unless that behavior has + // been suppressed. + if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) + gdk_window_raise(GTK_WIDGET(widget)->window); + + if (has_hit_edge) { + gtk_window_begin_resize_drag(window_, edge, event->button, + static_cast(event->x_root), + static_cast(event->y_root), + event->time); + return TRUE; + } else if (has_hit_titlebar) { + return gtk_window_util::HandleTitleBarLeftMousePress( + window_, gfx::Rect(GetPosition(), GetSize()), event); + } + } else if (GDK_2BUTTON_PRESS == event->type) { + if (has_hit_titlebar && resizable) { + // Maximize/restore on double click. + if (IsMaximized()) + gtk_window_unmaximize(window_); + else + gtk_window_maximize(window_); + return TRUE; + } + } + } else if (event->button == 2) { + if (has_hit_titlebar || has_hit_edge) + gdk_window_lower(gdk_window); + return TRUE; + } + return FALSE; +} + +// static +NativeWindow* NativeWindow::Create(content::WebContents* web_contents, + base::DictionaryValue* options) { + return new NativeWindowGtk(web_contents, options); +} + +} // namespace atom diff --git a/browser/native_window_gtk.h b/browser/native_window_gtk.h new file mode 100644 index 000000000000..6fee4fc66103 --- /dev/null +++ b/browser/native_window_gtk.h @@ -0,0 +1,109 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NATIVE_WINDOW_GTK_H_ +#define ATOM_BROWSER_NATIVE_WINDOW_GTK_H_ + +#include + +#include "browser/native_window.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/gfx/size.h" + +namespace atom { + +class NativeWindowGtk : public NativeWindow { + public: + explicit NativeWindowGtk(content::WebContents* web_contents, + base::DictionaryValue* options); + virtual ~NativeWindowGtk(); + + // NativeWindow implementation. + virtual void Close() OVERRIDE; + virtual void CloseImmediately() OVERRIDE; + virtual void Move(const gfx::Rect& pos) OVERRIDE; + virtual void Focus(bool focus) OVERRIDE; + virtual bool IsFocused() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual bool IsVisible() OVERRIDE; + virtual void Maximize() OVERRIDE; + virtual void Unmaximize() OVERRIDE; + virtual void Minimize() OVERRIDE; + virtual void Restore() OVERRIDE; + virtual void SetFullscreen(bool fullscreen) OVERRIDE; + virtual bool IsFullscreen() OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetSize() OVERRIDE; + virtual void SetMinimumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetMinimumSize() OVERRIDE; + virtual void SetMaximumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetMaximumSize() OVERRIDE; + virtual void SetResizable(bool resizable) OVERRIDE; + virtual bool IsResizable() OVERRIDE; + virtual void SetAlwaysOnTop(bool top) OVERRIDE; + virtual bool IsAlwaysOnTop() OVERRIDE; + virtual void Center() OVERRIDE; + virtual void SetPosition(const gfx::Point& position) OVERRIDE; + virtual gfx::Point GetPosition() OVERRIDE; + virtual void SetTitle(const std::string& title) OVERRIDE; + virtual std::string GetTitle() OVERRIDE; + virtual void FlashFrame(bool flash) OVERRIDE; + virtual void SetKiosk(bool kiosk) OVERRIDE; + virtual bool IsKiosk() OVERRIDE; + virtual bool HasModalDialog() OVERRIDE; + virtual gfx::NativeWindow GetNativeWindow() OVERRIDE; + + protected: + virtual void UpdateDraggableRegions( + const std::vector& regions) OVERRIDE; + + private: + // Set WebKit's style from current theme. + void SetWebKitColorStyle(); + + // Whether window is maximized. + bool IsMaximized() const; + + // If the point (|x|, |y|) is within the resize border area of the window, + // returns true and sets |edge| to the appropriate GdkWindowEdge value. + // Otherwise, returns false. + bool GetWindowEdge(int x, int y, GdkWindowEdge* edge); + + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowDeleteEvent, + GdkEvent*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnFocusOut, GdkEventFocus*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowState, + GdkEventWindowState*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnMouseMoveEvent, + GdkEventMotion*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnButtonPress, + GdkEventButton*); + + GtkWindow* window_; + + GdkWindowState state_; + bool is_always_on_top_; + gfx::Size minimum_size_; + gfx::Size maximum_size_; + + // The region is treated as title bar, can be dragged to move + // and double clicked to maximize. + SkRegion draggable_region_; + + // If true, don't call gdk_window_raise() when we get a click in the title + // bar or window border. This is to work around a compiz bug. + bool suppress_window_raise_; + + // The current window cursor. We set it to a resize cursor when over the + // custom frame border. We set it to NULL if we want the default cursor. + GdkCursor* frame_cursor_; + + DISALLOW_COPY_AND_ASSIGN(NativeWindowGtk); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NATIVE_WINDOW_GTK_H_ diff --git a/browser/native_window_win.h b/browser/native_window_win.h index 738c98d68a40..705f4b64bee3 100644 --- a/browser/native_window_win.h +++ b/browser/native_window_win.h @@ -5,9 +5,8 @@ #ifndef ATOM_BROWSER_NATIVE_WINDOW_WIN_H_ #define ATOM_BROWSER_NATIVE_WINDOW_WIN_H_ -#include "base/strings/string16.h" - #include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" #include "browser/native_window.h" #include "ui/gfx/size.h" #include "ui/views/widget/widget_delegate.h" diff --git a/browser/net/atom_url_request_context_getter.cc b/browser/net/atom_url_request_context_getter.cc index 0c5a5c6ec04a..bdc72028e6e5 100644 --- a/browser/net/atom_url_request_context_getter.cc +++ b/browser/net/atom_url_request_context_getter.cc @@ -66,6 +66,7 @@ AtomURLRequestContextGetter::~AtomURLRequestContextGetter() { net::URLRequestContext* AtomURLRequestContextGetter::GetURLRequestContext() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + base::AutoLock auto_lock(lock_); if (!url_request_context_.get()) { url_request_context_.reset(new net::URLRequestContext()); network_delegate_ = network_delegate_factory_.Run().Pass(); diff --git a/browser/net/atom_url_request_context_getter.h b/browser/net/atom_url_request_context_getter.h index f66be82e399b..e2c5f365c2fb 100644 --- a/browser/net/atom_url_request_context_getter.h +++ b/browser/net/atom_url_request_context_getter.h @@ -8,6 +8,7 @@ #include "base/callback.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" #include "content/public/browser/content_browser_client.h" #include "net/url_request/url_request_context_getter.h" @@ -59,6 +60,8 @@ class AtomURLRequestContextGetter : public net::URLRequestContextGetter { base::Callback(void)> network_delegate_factory_; + base::Lock lock_; + scoped_ptr proxy_config_service_; scoped_ptr network_delegate_; scoped_ptr storage_; diff --git a/browser/ui/accelerator_util_gtk.cc b/browser/ui/accelerator_util_gtk.cc new file mode 100644 index 000000000000..ac14b260be5e --- /dev/null +++ b/browser/ui/accelerator_util_gtk.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/ui/accelerator_util.h" + +#include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/platform_accelerator_gtk.h" + +namespace accelerator_util { + +void SetPlatformAccelerator(ui::Accelerator* accelerator) { + scoped_ptr platform_accelerator( + new ui::PlatformAcceleratorGtk( + GetGdkKeyCodeForAccelerator(*accelerator), + GetGdkModifierForAccelerator(*accelerator))); + accelerator->set_platform_accelerator(platform_accelerator.Pass()); +} + +} // namespace accelerator_util diff --git a/browser/ui/file_dialog_gtk.cc b/browser/ui/file_dialog_gtk.cc new file mode 100644 index 000000000000..1b47e0e440e8 --- /dev/null +++ b/browser/ui/file_dialog_gtk.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/ui/file_dialog.h" + +#include "base/callback.h" + +namespace file_dialog { + +bool ShowOpenDialog(atom::NativeWindow* parent_window, + const std::string& title, + const base::FilePath& default_path, + int properties, + std::vector* paths) { + return false; +} + +void ShowOpenDialog(atom::NativeWindow* parent_window, + const std::string& title, + const base::FilePath& default_path, + int properties, + const OpenDialogCallback& callback) { + callback.Run(false, std::vector()); +} + +bool ShowSaveDialog(atom::NativeWindow* parent_window, + const std::string& title, + const base::FilePath& default_path, + base::FilePath* path) { + return false; +} + +void ShowSaveDialog(atom::NativeWindow* parent_window, + const std::string& title, + const base::FilePath& default_path, + const SaveDialogCallback& callback) { + callback.Run(false, base::FilePath()); +} + +} // namespace file_dialog diff --git a/browser/ui/gtk/gtk_custom_menu.cc b/browser/ui/gtk/gtk_custom_menu.cc new file mode 100644 index 000000000000..4606cafdcfae --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2012 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 "browser/ui/gtk/gtk_custom_menu.h" + +#include "browser/ui/gtk/gtk_custom_menu_item.h" + +G_DEFINE_TYPE(GtkCustomMenu, gtk_custom_menu, GTK_TYPE_MENU) + +// Stolen directly from gtkmenushell.c. I'd love to call the library version +// instead, but it's static and isn't exported. :( +static gint gtk_menu_shell_is_item(GtkMenuShell* menu_shell, + GtkWidget* child) { + GtkWidget *parent; + + g_return_val_if_fail(GTK_IS_MENU_SHELL(menu_shell), FALSE); + g_return_val_if_fail(child != NULL, FALSE); + + parent = gtk_widget_get_parent(child); + while (GTK_IS_MENU_SHELL(parent)) { + if (parent == reinterpret_cast(menu_shell)) + return TRUE; + parent = GTK_MENU_SHELL(parent)->parent_menu_shell; + } + + return FALSE; +} + +// Stolen directly from gtkmenushell.c. I'd love to call the library version +// instead, but it's static and isn't exported. :( +static GtkWidget* gtk_menu_shell_get_item(GtkMenuShell* menu_shell, + GdkEvent* event) { + GtkWidget* menu_item = gtk_get_event_widget(event); + + while (menu_item && !GTK_IS_MENU_ITEM(menu_item)) + menu_item = gtk_widget_get_parent(menu_item); + + if (menu_item && gtk_menu_shell_is_item(menu_shell, menu_item)) + return menu_item; + else + return NULL; +} + +// When processing a button event, abort processing if the cursor isn't in a +// clickable region. +static gboolean gtk_custom_menu_button_press(GtkWidget* widget, + GdkEventButton* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), reinterpret_cast(event)); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + if (!gtk_custom_menu_item_is_in_clickable_region( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + return TRUE; + } + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + button_press_event(widget, event); +} + +// When processing a button event, abort processing if the cursor isn't in a +// clickable region. If it's in a button that doesn't dismiss the menu, fire +// that event and abort having the normal GtkMenu code run. +static gboolean gtk_custom_menu_button_release(GtkWidget* widget, + GdkEventButton* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), reinterpret_cast(event)); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + if (!gtk_custom_menu_item_is_in_clickable_region( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + // Stop processing this event. This isn't a clickable region. + return TRUE; + } + + if (gtk_custom_menu_item_try_no_dismiss_command( + GTK_CUSTOM_MENU_ITEM(menu_item))) { + return TRUE; + } + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + button_release_event(widget, event); +} + +// Manually forward button press events to the menu item (and then do what we'd +// do normally). +static gboolean gtk_custom_menu_motion_notify(GtkWidget* widget, + GdkEventMotion* event) { + GtkWidget* menu_item = gtk_menu_shell_get_item( + GTK_MENU_SHELL(widget), (GdkEvent*)event); + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item), + event->x, event->y); + } + + return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)-> + motion_notify_event(widget, event); +} + +static void gtk_custom_menu_move_current(GtkMenuShell* menu_shell, + GtkMenuDirectionType direction) { + // If the currently selected item is custom, we give it first chance to catch + // up/down events. + + // TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need + // to fix this by the time gtk3 comes out. + GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + switch (direction) { + case GTK_MENU_DIR_PREV: + case GTK_MENU_DIR_NEXT: + if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item), + direction)) + return; + break; + default: + break; + } + } + + GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)-> + move_current(menu_shell, direction); + + // In the case of hitting PREV and transitioning to a custom menu, we want to + // make sure we're selecting the final item in the list, not the first one. + menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item; + if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) { + gtk_custom_menu_item_select_item_by_direction( + GTK_CUSTOM_MENU_ITEM(menu_item), direction); + } +} + +static void gtk_custom_menu_init(GtkCustomMenu* menu) { +} + +static void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) { + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass); + + widget_class->button_press_event = gtk_custom_menu_button_press; + widget_class->button_release_event = gtk_custom_menu_button_release; + widget_class->motion_notify_event = gtk_custom_menu_motion_notify; + + menu_shell_class->move_current = gtk_custom_menu_move_current; +} + +GtkWidget* gtk_custom_menu_new() { + return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL)); +} diff --git a/browser/ui/gtk/gtk_custom_menu.h b/browser/ui/gtk/gtk_custom_menu.h new file mode 100644 index 000000000000..7aaa2b54f6f5 --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ +#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ + +// GtkCustomMenu is a GtkMenu subclass that can contain, and collaborates with, +// GtkCustomMenuItem instances. GtkCustomMenuItem is a GtkMenuItem that can +// have buttons and other normal widgets embeded in it. GtkCustomMenu exists +// only to override most of the button/motion/move callback functions so +// that the normal GtkMenu implementation doesn't handle events related to +// GtkCustomMenuItem items. +// +// For a more through overview of this system, see the comments in +// gtk_custom_menu_item.h. + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_MENU \ + (gtk_custom_menu_get_type()) +#define GTK_CUSTOM_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenu)) +#define GTK_CUSTOM_MENU_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass)) +#define GTK_IS_CUSTOM_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU)) +#define GTK_IS_CUSTOM_MENU_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU)) +#define GTK_CUSTOM_MENU_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass)) + +typedef struct _GtkCustomMenu GtkCustomMenu; +typedef struct _GtkCustomMenuClass GtkCustomMenuClass; + +struct _GtkCustomMenu { + GtkMenu menu; +}; + +struct _GtkCustomMenuClass { + GtkMenuClass parent_class; +}; + +GType gtk_custom_menu_get_type(void) G_GNUC_CONST; +GtkWidget* gtk_custom_menu_new(); + +G_END_DECLS + +#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_ diff --git a/browser/ui/gtk/gtk_custom_menu_item.cc b/browser/ui/gtk/gtk_custom_menu_item.cc new file mode 100644 index 000000000000..617c1711995a --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu_item.cc @@ -0,0 +1,493 @@ +// Copyright (c) 2012 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 "browser/ui/gtk/gtk_custom_menu_item.h" + +#include "base/i18n/rtl.h" +#include "browser/ui/gtk/gtk_custom_menu.h" +#include "ui/gfx/gtk_compat.h" + +// This method was autogenerated by the program glib-genmarshall, which +// generated it from the line "BOOL:INT". Two different attempts at getting gyp +// to autogenerate this didn't work. If we need more non-standard marshallers, +// this should be deleted, and an actual build step should be added. +void chrome_marshall_BOOLEAN__INT(GClosure* closure, + GValue* return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue* param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) { + typedef gboolean(*GMarshalFunc_BOOLEAN__INT)(gpointer data1, + gint arg_1, + gpointer data2); + register GMarshalFunc_BOOLEAN__INT callback; + register GCClosure *cc = (GCClosure*)closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail(return_value != NULL); + g_return_if_fail(n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + // Note: This line (and the line setting data1 in the other if branch) + // were macros in the original autogenerated output. This is with the + // macro resolved for release mode. In debug mode, it uses an accessor + // that asserts saying that the object pointed to by param_values doesn't + // hold a pointer. This appears to be the cause of http://crbug.com/58945. + // + // This is more than a little odd because the gtype on this first param + // isn't set correctly by the time we get here, while I watched it + // explicitly set upstack. I verified that v_pointer is still set + // correctly. I'm not sure what's going on. :( + data2 = (param_values + 0)->data[0].v_pointer; + } else { + data1 = (param_values + 0)->data[0].v_pointer; + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__INT)(marshal_data ? marshal_data : + cc->callback); + + v_return = callback(data1, + g_value_get_int(param_values + 1), + data2); + + g_value_set_boolean(return_value, v_return); +} + +enum { + BUTTON_PUSHED, + TRY_BUTTON_PUSHED, + LAST_SIGNAL +}; + +static guint custom_menu_item_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(GtkCustomMenuItem, gtk_custom_menu_item, GTK_TYPE_MENU_ITEM) + +static void set_selected(GtkCustomMenuItem* item, GtkWidget* selected) { + if (selected != item->currently_selected_button) { + if (item->currently_selected_button) { + gtk_widget_set_state(item->currently_selected_button, GTK_STATE_NORMAL); + gtk_widget_set_state( + gtk_bin_get_child(GTK_BIN(item->currently_selected_button)), + GTK_STATE_NORMAL); + } + + item->currently_selected_button = selected; + if (item->currently_selected_button) { + gtk_widget_set_state(item->currently_selected_button, GTK_STATE_SELECTED); + gtk_widget_set_state( + gtk_bin_get_child(GTK_BIN(item->currently_selected_button)), + GTK_STATE_PRELIGHT); + } + } +} + +// When GtkButtons set the label text, they rebuild the widget hierarchy each +// and every time. Therefore, we can't just fish out the label from the button +// and set some properties; we have to create this callback function that +// listens on the button's "notify" signal, which is emitted right after the +// label has been (re)created. (Label values can change dynamically.) +static void on_button_label_set(GObject* object) { + GtkButton* button = GTK_BUTTON(object); + GtkWidget* child = gtk_bin_get_child(GTK_BIN(button)); + gtk_widget_set_sensitive(child, FALSE); + gtk_misc_set_padding(GTK_MISC(child), 2, 0); +} + +static void gtk_custom_menu_item_finalize(GObject *object); +static gint gtk_custom_menu_item_expose(GtkWidget* widget, + GdkEventExpose* event); +static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget, + GdkEventExpose* event, + GtkCustomMenuItem* menu_item); +static void gtk_custom_menu_item_select(GtkItem *item); +static void gtk_custom_menu_item_deselect(GtkItem *item); +static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item); + +static void gtk_custom_menu_item_init(GtkCustomMenuItem* item) { + item->all_widgets = NULL; + item->button_widgets = NULL; + item->currently_selected_button = NULL; + item->previously_selected_button = NULL; + + GtkWidget* menu_hbox = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(item), menu_hbox); + + item->label = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(item->label), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(menu_hbox), item->label, TRUE, TRUE, 0); + + item->hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(menu_hbox), item->hbox, FALSE, FALSE, 0); + + g_signal_connect(item->hbox, "expose-event", + G_CALLBACK(gtk_custom_menu_item_hbox_expose), + item); + + gtk_widget_show_all(menu_hbox); +} + +static void gtk_custom_menu_item_class_init(GtkCustomMenuItemClass* klass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkItemClass* item_class = GTK_ITEM_CLASS(klass); + GtkMenuItemClass* menu_item_class = GTK_MENU_ITEM_CLASS(klass); + + gobject_class->finalize = gtk_custom_menu_item_finalize; + + widget_class->expose_event = gtk_custom_menu_item_expose; + + item_class->select = gtk_custom_menu_item_select; + item_class->deselect = gtk_custom_menu_item_deselect; + + menu_item_class->activate = gtk_custom_menu_item_activate; + + custom_menu_item_signals[BUTTON_PUSHED] = + g_signal_new("button-pushed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + custom_menu_item_signals[TRY_BUTTON_PUSHED] = + g_signal_new("try-button-pushed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + chrome_marshall_BOOLEAN__INT, + G_TYPE_BOOLEAN, 1, G_TYPE_INT); +} + +static void gtk_custom_menu_item_finalize(GObject *object) { + GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(object); + g_list_free(item->all_widgets); + g_list_free(item->button_widgets); + + G_OBJECT_CLASS(gtk_custom_menu_item_parent_class)->finalize(object); +} + +static gint gtk_custom_menu_item_expose(GtkWidget* widget, + GdkEventExpose* event) { + if (gtk_widget_get_visible(widget) && + gtk_widget_get_mapped(widget) && + gtk_bin_get_child(GTK_BIN(widget))) { + // We skip the drawing in the GtkMenuItem class it draws the highlighted + // background and we don't want that. + gtk_container_propagate_expose(GTK_CONTAINER(widget), + gtk_bin_get_child(GTK_BIN(widget)), + event); + } + + return FALSE; +} + +static void gtk_custom_menu_item_expose_button(GtkWidget* hbox, + GdkEventExpose* event, + GList* button_item) { + // We search backwards to find the leftmost and rightmost buttons. The + // current button may be that button. + GtkWidget* current_button = GTK_WIDGET(button_item->data); + GtkWidget* first_button = current_button; + for (GList* i = button_item; i && GTK_IS_BUTTON(i->data); + i = g_list_previous(i)) { + first_button = GTK_WIDGET(i->data); + } + + GtkWidget* last_button = current_button; + for (GList* i = button_item; i && GTK_IS_BUTTON(i->data); + i = g_list_next(i)) { + last_button = GTK_WIDGET(i->data); + } + + if (base::i18n::IsRTL()) + std::swap(first_button, last_button); + + GtkAllocation first_allocation; + gtk_widget_get_allocation(first_button, &first_allocation); + GtkAllocation current_allocation; + gtk_widget_get_allocation(current_button, ¤t_allocation); + GtkAllocation last_allocation; + gtk_widget_get_allocation(last_button, &last_allocation); + + int x = first_allocation.x; + int y = first_allocation.y; + int width = last_allocation.width + last_allocation.x - first_allocation.x; + int height = last_allocation.height; + + gtk_paint_box(gtk_widget_get_style(hbox), + gtk_widget_get_window(hbox), + gtk_widget_get_state(current_button), + GTK_SHADOW_OUT, + ¤t_allocation, hbox, "button", + x, y, width, height); + + // Propagate to the button's children. + gtk_container_propagate_expose( + GTK_CONTAINER(current_button), + gtk_bin_get_child(GTK_BIN(current_button)), + event); +} + +static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget, + GdkEventExpose* event, + GtkCustomMenuItem* menu_item) { + // First render all the buttons that aren't the currently selected item. + for (GList* current_item = menu_item->all_widgets; + current_item != NULL; current_item = g_list_next(current_item)) { + if (GTK_IS_BUTTON(current_item->data)) { + if (GTK_WIDGET(current_item->data) != + menu_item->currently_selected_button) { + gtk_custom_menu_item_expose_button(widget, event, current_item); + } + } + } + + // As a separate pass, draw the buton separators above. We need to draw the + // separators in a separate pass because we are drawing on top of the + // buttons. Otherwise, the vlines are overwritten by the next button. + for (GList* current_item = menu_item->all_widgets; + current_item != NULL; current_item = g_list_next(current_item)) { + if (GTK_IS_BUTTON(current_item->data)) { + // Check to see if this is the last button in a run. + GList* next_item = g_list_next(current_item); + if (next_item && GTK_IS_BUTTON(next_item->data)) { + GtkWidget* current_button = GTK_WIDGET(current_item->data); + GtkAllocation button_allocation; + gtk_widget_get_allocation(current_button, &button_allocation); + GtkAllocation child_alloc; + gtk_widget_get_allocation(gtk_bin_get_child(GTK_BIN(current_button)), + &child_alloc); + GtkStyle* style = gtk_widget_get_style(widget); + int half_offset = style->xthickness / 2; + gtk_paint_vline(style, + gtk_widget_get_window(widget), + gtk_widget_get_state(current_button), + &event->area, widget, "button", + child_alloc.y, + child_alloc.y + child_alloc.height, + button_allocation.x + + button_allocation.width - half_offset); + } + } + } + + // Finally, draw the selected item on top of the separators so there are no + // artifacts inside the button area. + GList* selected = g_list_find(menu_item->all_widgets, + menu_item->currently_selected_button); + if (selected) { + gtk_custom_menu_item_expose_button(widget, event, selected); + } + + return TRUE; +} + +static void gtk_custom_menu_item_select(GtkItem* item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item); + + // When we are selected, the only thing we do is clear information from + // previous selections. Actual selection of a button is done either in the + // "mouse-motion-event" or is manually set from GtkCustomMenu's overridden + // "move-current" handler. + custom_item->previously_selected_button = NULL; + + gtk_widget_queue_draw(GTK_WIDGET(item)); +} + +static void gtk_custom_menu_item_deselect(GtkItem* item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item); + + // When we are deselected, we store the item that was currently selected so + // that it can be acted on. Menu items are first deselected before they are + // activated. + custom_item->previously_selected_button = + custom_item->currently_selected_button; + if (custom_item->currently_selected_button) + set_selected(custom_item, NULL); + + gtk_widget_queue_draw(GTK_WIDGET(item)); +} + +static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item); + + // We look at |previously_selected_button| because by the time we've been + // activated, we've already gone through our deselect handler. + if (custom_item->previously_selected_button) { + gpointer id_ptr = g_object_get_data( + G_OBJECT(custom_item->previously_selected_button), "command-id"); + if (id_ptr != NULL) { + int command_id = GPOINTER_TO_INT(id_ptr); + g_signal_emit(custom_item, custom_menu_item_signals[BUTTON_PUSHED], 0, + command_id); + set_selected(custom_item, NULL); + } + } +} + +GtkWidget* gtk_custom_menu_item_new(const char* title) { + GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM( + g_object_new(GTK_TYPE_CUSTOM_MENU_ITEM, NULL)); + gtk_label_set_text(GTK_LABEL(item->label), title); + return GTK_WIDGET(item); +} + +GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item, + int command_id) { + GtkWidget* button = gtk_button_new(); + g_object_set_data(G_OBJECT(button), "command-id", + GINT_TO_POINTER(command_id)); + gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, button); + menu_item->button_widgets = g_list_append(menu_item->button_widgets, button); + + return button; +} + +GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item, + int command_id) { + GtkWidget* button = gtk_button_new_with_label(""); + g_object_set_data(G_OBJECT(button), "command-id", + GINT_TO_POINTER(command_id)); + gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0); + g_signal_connect(button, "notify::label", + G_CALLBACK(on_button_label_set), NULL); + gtk_widget_show(button); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, button); + + return button; +} + +void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item) { + GtkWidget* fixed = gtk_fixed_new(); + gtk_widget_set_size_request(fixed, 5, -1); + + gtk_box_pack_start(GTK_BOX(menu_item->hbox), fixed, FALSE, FALSE, 0); + gtk_widget_show(fixed); + + menu_item->all_widgets = g_list_append(menu_item->all_widgets, fixed); +} + +void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item, + gdouble x, gdouble y) { + GtkWidget* new_selected_widget = NULL; + GList* current = menu_item->button_widgets; + for (; current != NULL; current = current->next) { + GtkWidget* current_widget = GTK_WIDGET(current->data); + GtkAllocation alloc; + gtk_widget_get_allocation(current_widget, &alloc); + int offset_x, offset_y; + gtk_widget_translate_coordinates(current_widget, GTK_WIDGET(menu_item), + 0, 0, &offset_x, &offset_y); + if (x >= offset_x && x < (offset_x + alloc.width) && + y >= offset_y && y < (offset_y + alloc.height)) { + new_selected_widget = current_widget; + break; + } + } + + set_selected(menu_item, new_selected_widget); +} + +gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item, + GtkMenuDirectionType direction) { + GtkWidget* current = menu_item->currently_selected_button; + if (menu_item->button_widgets && current) { + switch (direction) { + case GTK_MENU_DIR_PREV: { + if (g_list_first(menu_item->button_widgets)->data == current) + return FALSE; + + set_selected(menu_item, GTK_WIDGET(g_list_previous(g_list_find( + menu_item->button_widgets, current))->data)); + break; + } + case GTK_MENU_DIR_NEXT: { + if (g_list_last(menu_item->button_widgets)->data == current) + return FALSE; + + set_selected(menu_item, GTK_WIDGET(g_list_next(g_list_find( + menu_item->button_widgets, current))->data)); + break; + } + default: + break; + } + } + + return TRUE; +} + +void gtk_custom_menu_item_select_item_by_direction( + GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction) { + menu_item->previously_selected_button = NULL; + + // If we're just told to be selected by the menu system, select the first + // item. + if (menu_item->button_widgets) { + switch (direction) { + case GTK_MENU_DIR_PREV: { + GtkWidget* last_button = + GTK_WIDGET(g_list_last(menu_item->button_widgets)->data); + if (last_button) + set_selected(menu_item, last_button); + break; + } + case GTK_MENU_DIR_NEXT: { + GtkWidget* first_button = + GTK_WIDGET(g_list_first(menu_item->button_widgets)->data); + if (first_button) + set_selected(menu_item, first_button); + break; + } + default: + break; + } + } + + gtk_widget_queue_draw(GTK_WIDGET(menu_item)); +} + +gboolean gtk_custom_menu_item_is_in_clickable_region( + GtkCustomMenuItem* menu_item) { + return menu_item->currently_selected_button != NULL; +} + +gboolean gtk_custom_menu_item_try_no_dismiss_command( + GtkCustomMenuItem* menu_item) { + GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item); + gboolean activated = TRUE; + + // We work with |currently_selected_button| instead of + // |previously_selected_button| since we haven't been "deselect"ed yet. + gpointer id_ptr = g_object_get_data( + G_OBJECT(custom_item->currently_selected_button), "command-id"); + if (id_ptr != NULL) { + int command_id = GPOINTER_TO_INT(id_ptr); + g_signal_emit(custom_item, custom_menu_item_signals[TRY_BUTTON_PUSHED], 0, + command_id, &activated); + } + + return activated; +} + +void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item, + GtkCallback callback, + gpointer callback_data) { + // Even though we're filtering |all_widgets| on GTK_IS_BUTTON(), this isn't + // equivalent to |button_widgets| because we also want the button-labels. + for (GList* i = menu_item->all_widgets; i && GTK_IS_BUTTON(i->data); + i = g_list_next(i)) { + if (GTK_IS_BUTTON(i->data)) { + callback(GTK_WIDGET(i->data), callback_data); + } + } +} diff --git a/browser/ui/gtk/gtk_custom_menu_item.h b/browser/ui/gtk/gtk_custom_menu_item.h new file mode 100644 index 000000000000..46e5cf721e9b --- /dev/null +++ b/browser/ui/gtk/gtk_custom_menu_item.h @@ -0,0 +1,139 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ +#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ + +// GtkCustomMenuItem is a GtkMenuItem subclass that has buttons in it and acts +// to support this. GtkCustomMenuItems only render properly when put in a +// GtkCustomMenu; there's a lot of collaboration between these two classes +// necessary to work around how gtk normally does menus. +// +// We can't rely on the normal event infrastructure. While a menu is up, the +// GtkMenu has a grab on all events. Instead of trying to pump events through +// the normal channels, we have the GtkCustomMenu selectively forward mouse +// motion through a back channel. The GtkCustomMenu only listens for button +// press information so it can block the effects of the click if the cursor +// isn't in a button in the menu item. +// +// A GtkCustomMenuItem doesn't try to take these signals and forward them to +// the buttons it owns. The GtkCustomMenu class keeps track of which button is +// selected (due to key events and mouse movement) and otherwise acts like a +// normal GtkItem. The buttons are only for sizing and rendering; they don't +// respond to events. Instead, when the GtkCustomMenuItem is activated by the +// GtkMenu, it uses which button was selected as a signal of what to do. +// +// Users should connect to the "button-pushed" signal to be notified when a +// button was pushed. We don't go through the normal "activate" signal because +// we need to communicate additional information, namely which button was +// activated. + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_MENU_ITEM \ + (gtk_custom_menu_item_get_type()) +#define GTK_CUSTOM_MENU_ITEM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItem)) +#define GTK_CUSTOM_MENU_ITEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItemClass)) +#define GTK_IS_CUSTOM_MENU_ITEM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU_ITEM)) +#define GTK_IS_CUSTOM_MENU_ITEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU_ITEM)) +#define GTK_CUSTOM_MENU_ITEM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \ + GtkCustomMenuItemClass)) + +typedef struct _GtkCustomMenuItem GtkCustomMenuItem; +typedef struct _GtkCustomMenuItemClass GtkCustomMenuItemClass; + +struct _GtkCustomMenuItem { + GtkMenuItem menu_item; + + // Container for button widgets. + GtkWidget* hbox; + + // Label on left side of menu item. + GtkWidget* label; + + // List of all widgets we added. Used to find the leftmost and rightmost + // continuous buttons. + GList* all_widgets; + + // Possible button widgets. Used for keyboard navigation. + GList* button_widgets; + + // The widget that currently has highlight. + GtkWidget* currently_selected_button; + + // The widget that was selected *before* |currently_selected_button|. Why do + // we hang on to this? Because the menu system sends us a deselect signal + // right before activating us. We need to listen to deselect since that's + // what we receive when the mouse cursor leaves us entirely. + GtkWidget* previously_selected_button; +}; + +struct _GtkCustomMenuItemClass { + GtkMenuItemClass parent_class; +}; + +GType gtk_custom_menu_item_get_type(void) G_GNUC_CONST; +GtkWidget* gtk_custom_menu_item_new(const char* title); + +// Adds a button to our list of items in the |hbox|. +GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item, + int command_id); + +// Adds a button to our list of items in the |hbox|, but that isn't part of +// |button_widgets| to prevent it from being activatable. +GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item, + int command_id); + +// Adds a vertical space in the |hbox|. +void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item); + +// Receives a motion event from the GtkCustomMenu that contains us. We can't +// just subscribe to motion-event or the individual widget enter/leave events +// because the top level GtkMenu has an event grab. +void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item, + gdouble x, gdouble y); + +// Notification that the menu got a cursor key event. Used to move up/down +// within the menu buttons. Returns TRUE to stop the default signal handler +// from running. +gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item, + GtkMenuDirectionType direction); + +// Because we only get a generic "selected" signal when we've changed, we need +// to have a way for the GtkCustomMenu to tell us that we were just +// selected. +void gtk_custom_menu_item_select_item_by_direction( + GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction); + +// Whether we are currently hovering over a clickable region on the menu +// item. Used by GtkCustomMenu to determine whether it should discard click +// events. +gboolean gtk_custom_menu_item_is_in_clickable_region( + GtkCustomMenuItem* menu_item); + +// If the button is released while the |currently_selected_button| isn't +// supposed to dismiss the menu, this signals to our listeners that we want to +// run this command if it doesn't dismiss the menu. Returns TRUE if we acted +// on this button click (and should prevent the normal GtkMenu machinery from +// firing an "activate" signal). +gboolean gtk_custom_menu_item_try_no_dismiss_command( + GtkCustomMenuItem* menu_item); + +// Calls |callback| with every button and button-label in the container. +void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item, + GtkCallback callback, + gpointer callback_data); + +G_END_DECLS + +#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_ diff --git a/browser/ui/gtk/gtk_window_util.cc b/browser/ui/gtk/gtk_window_util.cc new file mode 100644 index 000000000000..c06e3302b373 --- /dev/null +++ b/browser/ui/gtk/gtk_window_util.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2012 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 "browser/ui/gtk/gtk_window_util.h" + +#include +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" + +using content::RenderWidgetHost; +using content::WebContents; + +namespace gtk_window_util { + +const int kFrameBorderThickness = 4; +const int kResizeAreaCornerSize = 16; + +// Keep track of the last click time and the last click position so we can +// filter out extra GDK_BUTTON_PRESS events when a double click happens. +static guint32 last_click_time; +static int last_click_x; +static int last_click_y; + +// Performs Cut/Copy/Paste operation on the |window|. +// If the current render view is focused, then just call the specified |method| +// against the current render view host, otherwise emit the specified |signal| +// against the focused widget. +// TODO(suzhe): This approach does not work for plugins. +void DoCutCopyPaste(GtkWindow* window, + WebContents* web_contents, + void (RenderWidgetHost::*method)(), + const char* signal) { + GtkWidget* widget = gtk_window_get_focus(window); + if (widget == NULL) + return; // Do nothing if no focused widget. + + if (web_contents && + widget == web_contents->GetView()->GetContentNativeView()) { + (web_contents->GetRenderViewHost()->*method)(); + } else { + guint id; + if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0) + g_signal_emit(widget, id, 0); + } +} + +void DoCut(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Cut, "cut-clipboard"); +} + +void DoCopy(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Copy, "copy-clipboard"); +} + +void DoPaste(GtkWindow* window, WebContents* web_contents) { + DoCutCopyPaste(window, web_contents, + &RenderWidgetHost::Paste, "paste-clipboard"); +} + +// Ubuntu patches their version of GTK+ so that there is always a +// gripper in the bottom right corner of the window. We dynamically +// look up this symbol because it's a non-standard Ubuntu extension to +// GTK+. We always need to disable this feature since we can't +// communicate this to WebKit easily. +typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean); +gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym; + +void DisableResizeGrip(GtkWindow* window) { + static bool resize_grip_looked_up = false; + if (!resize_grip_looked_up) { + resize_grip_looked_up = true; + gtk_window_set_has_resize_grip_sym = + reinterpret_cast( + dlsym(NULL, "gtk_window_set_has_resize_grip")); + } + if (gtk_window_set_has_resize_grip_sym) + gtk_window_set_has_resize_grip_sym(window, FALSE); +} + +GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { + switch (edge) { + case GDK_WINDOW_EDGE_NORTH_WEST: + return GDK_TOP_LEFT_CORNER; + case GDK_WINDOW_EDGE_NORTH: + return GDK_TOP_SIDE; + case GDK_WINDOW_EDGE_NORTH_EAST: + return GDK_TOP_RIGHT_CORNER; + case GDK_WINDOW_EDGE_WEST: + return GDK_LEFT_SIDE; + case GDK_WINDOW_EDGE_EAST: + return GDK_RIGHT_SIDE; + case GDK_WINDOW_EDGE_SOUTH_WEST: + return GDK_BOTTOM_LEFT_CORNER; + case GDK_WINDOW_EDGE_SOUTH: + return GDK_BOTTOM_SIDE; + case GDK_WINDOW_EDGE_SOUTH_EAST: + return GDK_BOTTOM_RIGHT_CORNER; + default: + NOTREACHED(); + } + return GDK_LAST_CURSOR; +} + +bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds) { + // A screen can be composed of multiple monitors. + GdkScreen* screen = gtk_window_get_screen(window); + GdkRectangle monitor_size; + + if (gtk_widget_get_realized(GTK_WIDGET(window))) { + // |window| has been realized. + gint monitor_num = gdk_screen_get_monitor_at_window(screen, + gtk_widget_get_window(GTK_WIDGET(window))); + gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size); + return bounds.size() == gfx::Size(monitor_size.width, monitor_size.height); + } + + // Make sure the window doesn't match any monitor size. We compare against + // all monitors because we don't know which monitor the window is going to + // open on before window realized. + gint num_monitors = gdk_screen_get_n_monitors(screen); + for (gint i = 0; i < num_monitors; ++i) { + GdkRectangle monitor_size; + gdk_screen_get_monitor_geometry(screen, i, &monitor_size); + if (bounds.size() == gfx::Size(monitor_size.width, monitor_size.height)) + return true; + } + return false; +} + +bool HandleTitleBarLeftMousePress( + GtkWindow* window, + const gfx::Rect& bounds, + GdkEventButton* event) { + // We want to start a move when the user single clicks, but not start a + // move when the user double clicks. However, a double click sends the + // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, + // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we + // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS, + // the call to gtk_window_maximize fails. To work around this, we + // keep track of the last click and if it's going to be a double click, + // we don't call gtk_window_begin_move_drag. + DCHECK_EQ(event->type, GDK_BUTTON_PRESS); + DCHECK_EQ(event->button, 1); + + static GtkSettings* settings = gtk_settings_get_default(); + gint double_click_time = 250; + gint double_click_distance = 5; + g_object_get(G_OBJECT(settings), + "gtk-double-click-time", &double_click_time, + "gtk-double-click-distance", &double_click_distance, + NULL); + + guint32 click_time = event->time - last_click_time; + int click_move_x = abs(event->x - last_click_x); + int click_move_y = abs(event->y - last_click_y); + + last_click_time = event->time; + last_click_x = static_cast(event->x); + last_click_y = static_cast(event->y); + + if (click_time > static_cast(double_click_time) || + click_move_x > double_click_distance || + click_move_y > double_click_distance) { + // Ignore drag requests if the window is the size of the screen. + // We do this to avoid triggering fullscreen mode in metacity + // (without the --no-force-fullscreen flag) and in compiz (with + // Legacy Fullscreen Mode enabled). + if (!BoundsMatchMonitorSize(window, bounds)) { + gtk_window_begin_move_drag(window, event->button, + static_cast(event->x_root), + static_cast(event->y_root), + event->time); + } + return TRUE; + } + return FALSE; +} + +void UnMaximize(GtkWindow* window, + const gfx::Rect& bounds, + const gfx::Rect& restored_bounds) { + gtk_window_unmaximize(window); + + // It can happen that you end up with a window whose restore size is the same + // as the size of the screen, so unmaximizing it merely remaximizes it due to + // the same WM feature that SetWindowSize() works around. We try to detect + // this and resize the window to work around the issue. + if (bounds.size() == restored_bounds.size()) + gtk_window_resize(window, bounds.width(), bounds.height() - 1); +} + +void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass) { + gtk_window_set_wmclass(window, + wmclass.c_str(), + gdk_get_program_class()); + + // Set WM_WINDOW_ROLE for session management purposes. + // See http://tronche.com/gui/x/icccm/sec-5.html . + gtk_window_set_role(window, wmclass.c_str()); +} + +void SetWindowSize(GtkWindow* window, const gfx::Size& size) { + gfx::Size new_size = size; + gint current_width = 0; + gint current_height = 0; + gtk_window_get_size(window, ¤t_width, ¤t_height); + GdkRectangle size_with_decorations = {0}; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); + if (gdk_window) { + gdk_window_get_frame_extents(gdk_window, + &size_with_decorations); + } + + if (current_width == size_with_decorations.width && + current_height == size_with_decorations.height) { + // Make sure the window doesn't match any monitor size. We compare against + // all monitors because we don't know which monitor the window is going to + // open on (the WM decides that). + GdkScreen* screen = gtk_window_get_screen(window); + gint num_monitors = gdk_screen_get_n_monitors(screen); + for (gint i = 0; i < num_monitors; ++i) { + GdkRectangle monitor_size; + gdk_screen_get_monitor_geometry(screen, i, &monitor_size); + if (gfx::Size(monitor_size.width, monitor_size.height) == size) { + gtk_window_resize(window, size.width(), size.height() - 1); + return; + } + } + } else { + // gtk_window_resize is the size of the window not including decorations, + // but we are given the |size| including window decorations. + if (size_with_decorations.width > current_width) { + new_size.set_width(size.width() - size_with_decorations.width + + current_width); + } + if (size_with_decorations.height > current_height) { + new_size.set_height(size.height() - size_with_decorations.height + + current_height); + } + } + + gtk_window_resize(window, new_size.width(), new_size.height()); +} + +bool GetWindowEdge(const gfx::Size& window_size, + int top_edge_inset, + int x, + int y, + GdkWindowEdge* edge) { + gfx::Rect middle(window_size); + middle.Inset(kFrameBorderThickness, + kFrameBorderThickness - top_edge_inset, + kFrameBorderThickness, + kFrameBorderThickness); + if (middle.Contains(x, y)) + return false; + + gfx::Rect north(0, 0, window_size.width(), + kResizeAreaCornerSize - top_edge_inset); + gfx::Rect west(0, 0, kResizeAreaCornerSize, window_size.height()); + gfx::Rect south(0, window_size.height() - kResizeAreaCornerSize, + window_size.width(), kResizeAreaCornerSize); + gfx::Rect east(window_size.width() - kResizeAreaCornerSize, 0, + kResizeAreaCornerSize, window_size.height()); + + if (north.Contains(x, y)) { + if (west.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_NORTH_WEST; + else if (east.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_NORTH_EAST; + else + *edge = GDK_WINDOW_EDGE_NORTH; + } else if (south.Contains(x, y)) { + if (west.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_SOUTH_WEST; + else if (east.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_SOUTH_EAST; + else + *edge = GDK_WINDOW_EDGE_SOUTH; + } else { + if (west.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_WEST; + else if (east.Contains(x, y)) + *edge = GDK_WINDOW_EDGE_EAST; + else + return false; // The cursor must be outside the window. + } + return true; +} + +} // namespace gtk_window_util diff --git a/browser/ui/gtk/gtk_window_util.h b/browser/ui/gtk/gtk_window_util.h new file mode 100644 index 000000000000..2234f38e2640 --- /dev/null +++ b/browser/ui/gtk/gtk_window_util.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 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. + +#ifndef ATOM_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ +#define ATOM_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ + +#include +#include +#include "ui/gfx/rect.h" + +namespace content { +class WebContents; +} + +namespace gtk_window_util { + +// The frame border is only visible in restored mode and is hardcoded to 4 px +// on each side regardless of the system window border size. +extern const int kFrameBorderThickness; +// In the window corners, the resize areas don't actually expand bigger, but +// the 16 px at the end of each edge triggers diagonal resizing. +extern const int kResizeAreaCornerSize; + +// Performs Cut/Copy/Paste operation on the |window|'s |web_contents|. +void DoCut(GtkWindow* window, content::WebContents* web_contents); +void DoCopy(GtkWindow* window, content::WebContents* web_contents); +void DoPaste(GtkWindow* window, content::WebContents* web_contents); + +// Ubuntu patches their version of GTK+ to that there is always a +// gripper in the bottom right corner of the window. We always need to +// disable this feature since we can't communicate this to WebKit easily. +void DisableResizeGrip(GtkWindow* window); + +// Returns the resize cursor corresponding to the window |edge|. +GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge); + +// Returns |true| if the window bounds match the monitor size. +bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds); + +bool HandleTitleBarLeftMousePress(GtkWindow* window, + const gfx::Rect& bounds, + GdkEventButton* event); + +// Request the underlying window to unmaximize. Also tries to work around +// a window manager "feature" that can prevent this in some edge cases. +void UnMaximize(GtkWindow* window, + const gfx::Rect& bounds, + const gfx::Rect& restored_bounds); + +// Set a custom WM_CLASS for a window. +void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass); + +// A helper method for setting the GtkWindow size that should be used in place +// of calling gtk_window_resize directly. This is done to avoid a WM "feature" +// where setting the window size to the monitor size causes the WM to set the +// EWMH for full screen mode. +void SetWindowSize(GtkWindow* window, const gfx::Size& size); + +// If the point (|x|, |y|) is within the resize border area of the window, +// returns true and sets |edge| to the appropriate GdkWindowEdge value. +// Otherwise, returns false. +// |top_edge_inset| specifies how much smaller (in px) than the default edge +// size the top edge should be, used by browser windows to make it easier to +// move the window since a lot of title bar space is taken by the tabs. +bool GetWindowEdge(const gfx::Size& window_size, + int top_edge_inset, + int x, + int y, + GdkWindowEdge* edge); + +} // namespace gtk_window_util + +#endif // ATOM_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_ diff --git a/browser/ui/message_box_gtk.cc b/browser/ui/message_box_gtk.cc new file mode 100644 index 000000000000..f5fc72ee8021 --- /dev/null +++ b/browser/ui/message_box_gtk.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/ui/message_box.h" + +#include "base/callback.h" + +namespace atom { + +int ShowMessageBox(NativeWindow* parent_window, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail) { + return 0; +} + +void ShowMessageBox(NativeWindow* parent_window, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail, + const MessageBoxCallback& callback) { + callback.Run(0); +} + +} // namespace atom diff --git a/browser/window_list.h b/browser/window_list.h index b2ee7e58b963..d788b50b600a 100644 --- a/browser/window_list.h +++ b/browser/window_list.h @@ -19,14 +19,17 @@ class WindowListObserver; class WindowList { public: typedef std::vector WindowVector; + typedef WindowVector::iterator iterator; typedef WindowVector::const_iterator const_iterator; - typedef WindowVector::const_reverse_iterator const_reverse_iterator; // Windows are added to the list before they have constructed windows, // so the |window()| member function may return NULL. const_iterator begin() const { return windows_.begin(); } const_iterator end() const { return windows_.end(); } + iterator begin() { return windows_.begin(); } + iterator end() { return windows_.end(); } + bool empty() const { return windows_.empty(); } size_t size() const { return windows_.size(); } diff --git a/common.gypi b/common.gypi index 4649539daa69..62cce1ef8cd0 100644 --- a/common.gypi +++ b/common.gypi @@ -2,7 +2,7 @@ 'variables': { 'clang': 0, 'conditions': [ - ['OS=="mac"', { + ['OS=="mac" or OS=="linux"', { 'clang': 1, }], ['OS=="win" and (MSVS_VERSION=="2012e" or MSVS_VERSION=="2010e")', { @@ -76,6 +76,21 @@ '-Wno-return-type', ], }, + 'conditions': [ + ['OS=="linux"', { + 'cflags': [ + '-Wno-parentheses-equality', + '-Wno-unused-function', + '-Wno-sometimes-uninitialized', + '-Wno-pointer-sign', + '-Wno-string-plus-int', + '-Wno-unused-variable', + '-Wno-unused-value', + '-Wno-deprecated-declarations', + '-Wno-return-type', + ], + }], + ], }], ['_target_name in ["node_lib", "atom_lib"]', { 'include_dirs': [ @@ -153,7 +168,9 @@ ], 'target_defaults': { 'cflags_cc': [ - '-std=c++11', + # Use gnu++11 instead of c++11 here, see: + # https://code.google.com/p/chromium/issues/detail?id=224515 + '-std=gnu++11', ], 'xcode_settings': { 'CC': '/usr/bin/clang', diff --git a/common/api/atom_api_screen.cc b/common/api/atom_api_screen.cc index 85e1ff2a95f8..8e7945003e3f 100644 --- a/common/api/atom_api_screen.cc +++ b/common/api/atom_api_screen.cc @@ -9,6 +9,11 @@ #include "common/v8/node_common.h" +#if defined(TOOLKIT_GTK) +#include "base/command_line.h" +#include "ui/gfx/gtk_util.h" +#endif + #define UNWRAP_SCREEN_AND_CHECK \ Screen* self = ObjectWrap::Unwrap(args.This()); \ if (self == NULL) \ @@ -67,6 +72,10 @@ void Screen::GetPrimaryDisplay( // static void Screen::Initialize(v8::Handle target) { +#if defined(TOOLKIT_GTK) + gfx::GdkInitFromCommandLine(*CommandLine::ForCurrentProcess()); +#endif + v8::Local t = v8::FunctionTemplate::New(New); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(v8::String::NewSymbol("Screen")); diff --git a/common/crash_reporter/crash_reporter_linux.cc b/common/crash_reporter/crash_reporter_linux.cc new file mode 100644 index 000000000000..2589c99aa35b --- /dev/null +++ b/common/crash_reporter/crash_reporter_linux.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/crash_reporter/crash_reporter_linux.h" + +#include "base/memory/singleton.h" + +namespace crash_reporter { + +CrashReporterLinux::CrashReporterLinux() { +} + +CrashReporterLinux::~CrashReporterLinux() { +} + +void CrashReporterLinux::InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) { +} + +void CrashReporterLinux::SetUploadParameters() { +} + +// static +CrashReporterLinux* CrashReporterLinux::GetInstance() { + return Singleton::get(); +} + +// static +CrashReporter* CrashReporter::GetInstance() { + return CrashReporterLinux::GetInstance(); +} + +} // namespace crash_reporter diff --git a/common/crash_reporter/crash_reporter_linux.h b/common/crash_reporter/crash_reporter_linux.h new file mode 100644 index 000000000000..a968a3946591 --- /dev/null +++ b/common/crash_reporter/crash_reporter_linux.h @@ -0,0 +1,38 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ +#define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ + +#include "base/compiler_specific.h" +#include "common/crash_reporter/crash_reporter.h" + +template struct DefaultSingletonTraits; + +namespace crash_reporter { + +class CrashReporterLinux : public CrashReporter { + public: + static CrashReporterLinux* GetInstance(); + + virtual void InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) OVERRIDE; + virtual void SetUploadParameters() OVERRIDE; + + private: + friend struct DefaultSingletonTraits; + + CrashReporterLinux(); + virtual ~CrashReporterLinux(); + + DISALLOW_COPY_AND_ASSIGN(CrashReporterLinux); +}; + +} // namespace crash_reporter + +#endif // ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_LINUX_H_ diff --git a/common/linux/application_info.cc b/common/linux/application_info.cc new file mode 100644 index 000000000000..93d33b567242 --- /dev/null +++ b/common/linux/application_info.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2013 GitHub, Inc. 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 "common/atom_version.h" + +namespace brightray { + +std::string GetApplicationName() { + return "Atom-Shell"; +} + +std::string GetApplicationVersion() { + return ATOM_VERSION_STRING; +} + +} // namespace brightray diff --git a/common/node_bindings_linux.cc b/common/node_bindings_linux.cc new file mode 100644 index 000000000000..035441c8dc37 --- /dev/null +++ b/common/node_bindings_linux.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/node_bindings_linux.h" + +#include + +namespace atom { + +NodeBindingsLinux::NodeBindingsLinux(bool is_browser) + : NodeBindings(is_browser), + epoll_(epoll_create(1)) { + int backend_fd = uv_backend_fd(uv_loop_); + struct epoll_event ev = { 0 }; + ev.events = EPOLLIN; + ev.data.fd = backend_fd; + epoll_ctl(epoll_, EPOLL_CTL_ADD, backend_fd, &ev); +} + +NodeBindingsLinux::~NodeBindingsLinux() { +} + +void NodeBindingsLinux::RunMessageLoop() { + // Get notified when libuv's watcher queue changes. + uv_loop_->data = this; + uv_loop_->on_watcher_queue_updated = OnWatcherQueueChanged; + + NodeBindings::RunMessageLoop(); +} + +// static +void NodeBindingsLinux::OnWatcherQueueChanged(uv_loop_t* loop) { + NodeBindingsLinux* self = static_cast(loop->data); + + // We need to break the io polling in the epoll thread when loop's watcher + // queue changes, otherwise new events cannot be notified. + self->WakeupEmbedThread(); +} + +void NodeBindingsLinux::PollEvents() { + int timeout = uv_backend_timeout(uv_loop_); + + // Wait for new libuv events. + int r; + do { + struct epoll_event ev; + r = epoll_wait(epoll_, &ev, 1, timeout); + } while (r == -1 && errno == EINTR); +} + +// static +NodeBindings* NodeBindings::Create(bool is_browser) { + return new NodeBindingsLinux(is_browser); +} + +} // namespace atom diff --git a/common/node_bindings_linux.h b/common/node_bindings_linux.h new file mode 100644 index 000000000000..83711543d80f --- /dev/null +++ b/common/node_bindings_linux.h @@ -0,0 +1,34 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NODE_BINDINGS_LINUX_H_ +#define ATOM_COMMON_NODE_BINDINGS_LINUX_H_ + +#include "base/compiler_specific.h" +#include "common/node_bindings.h" + +namespace atom { + +class NodeBindingsLinux : public NodeBindings { + public: + explicit NodeBindingsLinux(bool is_browser); + virtual ~NodeBindingsLinux(); + + virtual void RunMessageLoop() OVERRIDE; + + private: + // Called when uv's watcher queue changes. + static void OnWatcherQueueChanged(uv_loop_t* loop); + + virtual void PollEvents() OVERRIDE; + + // Epoll to poll for uv's backend fd. + int epoll_; + + DISALLOW_COPY_AND_ASSIGN(NodeBindingsLinux); +}; + +} // namespace atom + +#endif // ATOM_COMMON_NODE_BINDINGS_LINUX_H_ diff --git a/common/platform_util_linux.cc b/common/platform_util_linux.cc new file mode 100644 index 000000000000..d2ac2f88f93a --- /dev/null +++ b/common/platform_util_linux.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/platform_util.h" + +#include + +#include "base/file_util.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "url/gurl.h" + +namespace { + +void XDGUtil(const std::string& util, const std::string& arg) { + std::vector argv; + argv.push_back(util); + argv.push_back(arg); + + base::LaunchOptions options; + // xdg-open can fall back on mailcap which eventually might plumb through + // to a command that needs a terminal. Set the environment variable telling + // it that we definitely don't have a terminal available and that it should + // bring up a new terminal if necessary. See "man mailcap". + options.environ["MM_NOTTTY"] = "1"; + + base::ProcessHandle handle; + if (base::LaunchProcess(argv, options, &handle)) + base::EnsureProcessGetsReaped(handle); +} + +void XDGOpen(const std::string& path) { + XDGUtil("xdg-open", path); +} + +void XDGEmail(const std::string& email) { + XDGUtil("xdg-email", email); +} + +} // namespace + +namespace platform_util { + +// TODO(estade): It would be nice to be able to select the file in the file +// manager, but that probably requires extending xdg-open. For now just +// show the folder. +void ShowItemInFolder(const base::FilePath& full_path) { + base::FilePath dir = full_path.DirName(); + if (!base::DirectoryExists(dir)) + return; + + XDGOpen(dir.value()); +} + +void OpenItem(const base::FilePath& full_path) { + XDGOpen(full_path.value()); +} + +void OpenExternal(const GURL& url) { + if (url.SchemeIs("mailto")) + XDGEmail(url.spec()); + else + XDGOpen(url.spec()); +} + +void MoveItemToTrash(const base::FilePath& full_path) { + XDGUtil("gvfs-trash", full_path.value()); +} + +void Beep() { + // echo '\a' > /dev/console + FILE* console = fopen("/dev/console", "r"); + if (console == NULL) + return; + fprintf(console, "\a"); + fclose(console); +} + +} // namespace platform_util diff --git a/docs/README.md b/docs/README.md index cf0cc9905ff2..671e1fd4bcfd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ ## Guides * [Quick start](quick-start.md) -* [Build native modules](build-native-modules.md) +* [Use native modules](use-native-modules.md) ## Development @@ -11,6 +11,7 @@ * [Source code directory structure](development/source-code-directory-structure.md) * [Build instructions (Mac)](development/build-instructions-mac.md) * [Build instructions (Windows)](development/build-instructions-windows.md) +* [Build instructions (Linux)](development/build-instructions-linux.md) ## API References diff --git a/docs/api/browser/browser-window.md b/docs/api/browser/browser-window.md index 66b875771ebd..d6ef45380a61 100644 --- a/docs/api/browser/browser-window.md +++ b/docs/api/browser/browser-window.md @@ -116,6 +116,22 @@ shouldn't!). Emitted when the memory taken by the native window is released. Usually you should dereference the javascript object when received this event. +### Event: 'unresponsive' + +Emiited when the web page becomes unresponsive. + +### Event: 'responsive' + +Emitted when the unresponsive web page becomes responsive again. + +### Event: 'crashed' + +Emitted when the renderer process is crashed. + +### Event: 'blur' + +Emiited when window loses focus. + ### Class Method: BrowserWindow.getAllWindows() Returns an array of all opened browser windows. diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md new file mode 100644 index 000000000000..f4c5a6263b1a --- /dev/null +++ b/docs/development/build-instructions-linux.md @@ -0,0 +1,71 @@ +# Build instructions (Linux) + +## Prerequisites + +* [node.js](http://nodejs.org) +* clang and headers of GTK+ and libnotify + +On Ubuntu you could install the libraries via: + +```bash +$ sudo apt-get install clang libgtk2.0-dev libnotify-dev +``` + +## Getting the code + +```bash +$ git clone https://github.com/atom/atom-shell.git +``` + +## Bootstrapping + +The bootstrap script will download all necessary build dependencies and create +build project files. Notice that we're using `ninja` to build `atom-shell` so +there is no `Makefile` generated. + +```bash +$ cd atom-shell +$ ./script/bootstrap.py +``` + +## Building + +Build both `Release` and `Debug` targets: + +```bash +$ ./script/build.py +``` + +You can also only build the `Debug` target: + +```bash +$ ./script/build.py -c Debug +``` + +After building is done, you can find `Atom.app` under `out/Debug`. + +## Troubleshooting + +If you got an error like this: + +```` +In file included from /usr/include/stdio.h:28:0, + from ../../../svnsrc/libgcc/../gcc/tsystem.h:88, + from ../../../svnsrc/libgcc/libgcc2.c:29: +/usr/include/features.h:324:26: fatal error: bits/predefs.h: No such file or directory + #include +```` + +Then you need to install `gcc-multilib` and `g++-multilib`, on Ubuntu you can do +this: + +```bash +$ sudo apt-get install gcc-multilib g++-multilib +``` + +## Tests + +```bash +$ ./script/test.py +``` + diff --git a/docs/build-native-modules.md b/docs/use-native-modules.md similarity index 63% rename from docs/build-native-modules.md rename to docs/use-native-modules.md index feb5514c8255..ae7481183d51 100644 --- a/docs/build-native-modules.md +++ b/docs/use-native-modules.md @@ -1,15 +1,24 @@ -# Build native modules +# Use native modules Since atom-shell is using a different V8 version from the official node, you need to build native module against atom-shell's headers to use them. -You need to use node-gyp to compile native modules, you can install node-gyp -via npm if you hadn't: +The [apm](https://github.com/atom/apm) provided a easy way to do this, after +installing it you could use it to install dependencies just like using `npm`: ```bash -$ npm install -g node-gyp +$ cd /path/to/atom-shell/project/ +$ apm install . ``` +But you should notice that `apm install module` wont' work because it will +install a user package for [Atom](https://github.com/atom/atom) instead. + +Apart from `apm`, you can also use `node-gyp` and `npm` to manually build the +native modules. + +## The node-gyp way + First you need to check which node release atom-shell is carrying via `process.version` (at the time of writing it is v0.10.5), then you can configure and build native modules via following commands: @@ -23,13 +32,7 @@ The `HOME=~/.atom-shell-gyp` changes where to find development headers. The `--target=0.10.5` is specifying node's version. The `--dist-url=...` specifies where to download the headers. -## Use npm to build native modules - -Under most circumstances you would want to use npm to install modules, if -you're using npm >= v1.2.19 (because [a -patch](https://github.com/TooTallNate/node-gyp/commit/afbcdea1ffd25c02bc88d119b10337852c44d400) -is needed to make `npm_config_disturl` work) you can use following code to -download and build native modules against atom-shell's headers: +## The npm way ```bash export npm_config_disturl=https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist diff --git a/package.json b/package.json index 518cd8530702..668f5d145e8a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "mocha": "~1.13.0", "pathwatcher": "0.14.0", "q": "0.9.7", - "runas": "0.3.0", + "runas": "0.5.*", "temp": "~0.6.0", "walkdir": "~0.0.7" }, diff --git a/script/cpplint.py b/script/cpplint.py index e4314142b27b..aba6064908e4 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -13,6 +13,8 @@ IGNORE_FILES = [ 'browser/ui/cocoa/event_processing_window.h', 'browser/ui/cocoa/atom_menu_controller.h', 'browser/ui/cocoa/nsalert_synchronous_sheet.h', + 'browser/ui/gtk/gtk_custom_menu.cc', + 'browser/ui/gtk/gtk_custom_menu_item.cc', 'common/api/api_messages.cc', 'common/api/api_messages.h', 'common/atom_version.h', diff --git a/script/lib/config.py b/script/lib/config.py index 5c746e294144..02bd080cfd92 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -2,4 +2,4 @@ NODE_VERSION = 'v0.11.10' BASE_URL = 'https://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'b27290717c08f8c6a58067d3c3725d68b4e6a2e5' +LIBCHROMIUMCONTENT_COMMIT = 'fe05f53f3080889ced2696b2741d93953e654b49' diff --git a/script/test.py b/script/test.py index 54601a6e7216..c21e52ab548c 100755 --- a/script/test.py +++ b/script/test.py @@ -14,8 +14,10 @@ def main(): if sys.platform == 'darwin': atom_shell = os.path.join(SOURCE_ROOT, 'out', 'Debug', 'Atom.app', 'Contents', 'MacOS', 'Atom') - else: + elif sys.platform == 'win32': atom_shell = os.path.join(SOURCE_ROOT, 'out', 'Debug', 'atom.exe') + else: + atom_shell = os.path.join(SOURCE_ROOT, 'out', 'Debug', 'atom') subprocess.check_call([atom_shell, 'spec'] + sys.argv[1:]) diff --git a/script/update.py b/script/update.py index 8c92f361e78b..16e850ad4502 100755 --- a/script/update.py +++ b/script/update.py @@ -32,6 +32,7 @@ def update_gyp(): subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', 'atom.gyp', '-Icommon.gypi', '-Ivendor/brightray/brightray.gypi', + '-Dlinux_clang=0', # Disable brightray's clang setting '-Dtarget_arch={0}'.format(arch), '-Dlibrary=static_library']) diff --git a/vendor/apm b/vendor/apm index d976d63c8df4..a9e5498a838b 160000 --- a/vendor/apm +++ b/vendor/apm @@ -1 +1 @@ -Subproject commit d976d63c8df4ac87d65ab9bbbef042517179e31f +Subproject commit a9e5498a838bf228fca53c32f66e2e6d5adcf783