From 6c5ea4ea329cd5e940ea9646cc2ccd03d38c4e70 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 8 Nov 2013 11:56:30 +0000 Subject: [PATCH 01/44] Compile coffee script on Linux. --- atom.gyp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/atom.gyp b/atom.gyp index 9aa29a7a5cf9..75076d980b24 100644 --- a/atom.gyp +++ b/atom.gyp @@ -392,8 +392,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', ], @@ -403,7 +402,7 @@ '<(RULE_INPUT_PATH)', '<(PRODUCT_DIR)/resources/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', ], - }], # OS=="win" + }], # OS=="win" or OS=="linux" ], }, ], From c97afdbdb35ae503dc1a889d0be5baec54d21410 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 08:33:17 +0000 Subject: [PATCH 02/44] Update apm. --- vendor/apm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/apm b/vendor/apm index 0ad4e65c8759..89f678cc3485 160000 --- a/vendor/apm +++ b/vendor/apm @@ -1 +1 @@ -Subproject commit 0ad4e65c8759a9be5fc55df576ff6c85e34cd4eb +Subproject commit 89f678cc34850ffa0163c4ac485336d40c72bb86 From 63852a8c826cf21fa5df9d7e23cbaac998e3f68f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 11:16:18 +0000 Subject: [PATCH 03/44] Update brightray for linux_clang flag. --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 6fa8c1c1fce5..ebac2dad8236 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 6fa8c1c1fce59577bc0a2c03378ad16c20aef4e7 +Subproject commit ebac2dad8236b5f9f81c7041e343c43bda44ec54 From c64a79336427a7c8f7496c045d100eb97519e0fc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 11:27:31 +0000 Subject: [PATCH 04/44] Build with clang under Linux. --- common.gypi | 6 ++++-- script/update.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common.gypi b/common.gypi index f4c44832fd6d..08f80b92ecf6 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")', { @@ -152,7 +152,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/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']) From 627f487b36ef51bb71e105a903fabfe933b46cef Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 11:40:19 +0000 Subject: [PATCH 05/44] Disable compilation errors in node under Linux. --- common.gypi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/common.gypi b/common.gypi index 08f80b92ecf6..fd6b90d345e4 100644 --- a/common.gypi +++ b/common.gypi @@ -75,6 +75,20 @@ '-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-deprecated-declarations', + '-Wno-return-type', + ], + }], + ], }], ['_target_name in ["node_lib", "atom_lib"]', { 'include_dirs': [ From 7afef0fcdbd4761a165d5f4247a5886a9ab37c29 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 11:51:17 +0000 Subject: [PATCH 06/44] Fix entry function under Linux. --- app/atom_main.cc | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) 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) From 66ac11ca5faf244e10a20773b0320343e02cac7c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 12:24:30 +0000 Subject: [PATCH 07/44] linux: Implement brightray's stub functions. --- atom.gyp | 1 + common/linux/application_info.cc | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/linux/application_info.cc diff --git a/atom.gyp b/atom.gyp index 75076d980b24..935938f17bb1 100644 --- a/atom.gyp +++ b/atom.gyp @@ -153,6 +153,7 @@ '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_mac.cc', 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 From f24ccd3841701c0e1ab61953d2d0f3cbd14f001c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 31 Dec 2013 12:59:14 +0000 Subject: [PATCH 08/44] linux: Implement platform_util. --- atom.gyp | 1 + common/platform_util_linux.cc | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 common/platform_util_linux.cc diff --git a/atom.gyp b/atom.gyp index 935938f17bb1..0bf9983115c6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -163,6 +163,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', 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 From 7ca152070a03a0cf08aec8f1abd1d86fb2458d6d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 1 Jan 2014 02:31:33 +0000 Subject: [PATCH 09/44] Update brightray. --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index ebac2dad8236..d7dd5547919e 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit ebac2dad8236b5f9f81c7041e343c43bda44ec54 +Subproject commit d7dd5547919eee15e5003c6d05e31183503418ed From b73a114f8f309fa585c6c11f64f2a4e53516ebf1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jan 2014 14:12:05 +0000 Subject: [PATCH 10/44] gtk: Implement accelerator_util. --- atom.gyp | 1 + browser/ui/accelerator_util_gtk.cc | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 browser/ui/accelerator_util_gtk.cc diff --git a/atom.gyp b/atom.gyp index 0bf9983115c6..5d379fe04dc6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -103,6 +103,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/atom_event_processing_window.h', 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 From 52b5f769f0983d137b5c62290fce927ee85c0563 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jan 2014 14:15:02 +0000 Subject: [PATCH 11/44] linux: Add empty implementation of auto updater. --- atom.gyp | 1 + browser/auto_updater_linux.cc | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 browser/auto_updater_linux.cc diff --git a/atom.gyp b/atom.gyp index 5d379fe04dc6..35137fc8d9a6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -66,6 +66,7 @@ 'browser/auto_updater.h', 'browser/auto_updater_delegate.cc', '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', diff --git a/browser/auto_updater_linux.cc b/browser/auto_updater_linux.cc new file mode 100644 index 000000000000..845eb45bb99e --- /dev/null +++ b/browser/auto_updater_linux.cc @@ -0,0 +1,33 @@ +// 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::Init() { +} + +// static +void AutoUpdater::SetFeedURL(const std::string& url) { +} + +// static +void AutoUpdater::SetAutomaticallyChecksForUpdates(bool yes) { +} + +// static +void AutoUpdater::SetAutomaticallyDownloadsUpdates(bool yes) { +} + +// static +void AutoUpdater::CheckForUpdates() { +} + +// static +void AutoUpdater::CheckForUpdatesInBackground() { +} + +} // namespace auto_updater From a4253e38995d8aa099394ebfcc2c067a766e83b9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Jan 2014 14:47:54 +0000 Subject: [PATCH 12/44] linux: Implemnt browser methods. --- atom.gyp | 1 + browser/browser_linux.cc | 44 ++++++++++++++++++++++++++++++++++++++++ browser/window_list.h | 5 ++++- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 browser/browser_linux.cc diff --git a/atom.gyp b/atom.gyp index 35137fc8d9a6..34acde3c8627 100644 --- a/atom.gyp +++ b/atom.gyp @@ -84,6 +84,7 @@ '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', 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/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(); } From 42dc9c1ec6ac0e987773f6c17c8641224b66a7e5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 12:01:03 +0000 Subject: [PATCH 13/44] Add dummy implementation of crash reporter. --- atom.gyp | 2 + common/crash_reporter/crash_reporter_linux.cc | 38 +++++++++++++++++++ common/crash_reporter/crash_reporter_linux.h | 38 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 common/crash_reporter/crash_reporter_linux.cc create mode 100644 common/crash_reporter/crash_reporter_linux.h diff --git a/atom.gyp b/atom.gyp index f75aac85c256..a9e1b37eb6ab 100644 --- a/atom.gyp +++ b/atom.gyp @@ -151,6 +151,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', 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_ From 0398577e9333b1e9564ed62252f03d12bf282227 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 13:28:00 +0000 Subject: [PATCH 14/44] gtk: Implement basic native window methods. --- atom.gyp | 2 + browser/native_window_gtk.cc | 239 +++++++++++++++++++++++++++++++++++ browser/native_window_gtk.h | 78 ++++++++++++ browser/native_window_win.h | 3 +- 4 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 browser/native_window_gtk.cc create mode 100644 browser/native_window_gtk.h diff --git a/atom.gyp b/atom.gyp index a9e1b37eb6ab..05bcdd10b9b2 100644 --- a/atom.gyp +++ b/atom.gyp @@ -91,6 +91,8 @@ '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', diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc new file mode 100644 index 000000000000..d063d1ee015d --- /dev/null +++ b/browser/native_window_gtk.cc @@ -0,0 +1,239 @@ +// 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 "common/options_switches.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "ui/gfx/rect.h" + +namespace atom { + +NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, + base::DictionaryValue* options) + : NativeWindow(web_contents, options), + window_(GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL))), + fullscreen_(false), + 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 (!has_frame_) + gtk_window_set_decorated(window_, false); + + if (!icon_.IsEmpty()) + gtk_window_set_icon(window_, icon_.ToGdkPixbuf()); + + g_signal_connect(window_, "delete-event", + G_CALLBACK(OnWindowDeleteEventThunk), this); +} + +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 (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) { + fullscreen_ = fullscreen; + if (fullscreen) + gtk_window_fullscreen(window_); + else + gtk_window_unfullscreen(window_); +} + +bool NativeWindowGtk::IsFullscreen() { + return 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) { +} + +gboolean NativeWindowGtk::OnWindowDeleteEvent(GtkWidget* widget, + GdkEvent* event) { + Close(); + return TRUE; +} + +// 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..70a855d03606 --- /dev/null +++ b/browser/native_window_gtk.h @@ -0,0 +1,78 @@ +// 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 "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: + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowDeleteEvent, + GdkEvent*); + + GtkWindow* window_; + + bool fullscreen_; + bool is_always_on_top_; + gfx::Size minimum_size_; + gfx::Size maximum_size_; + + 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 d7f58f55e14b..9d5d8af247b4 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" From 6912a0513a5c7ddbff34d77ff66f71ae79c22c23 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 14:31:26 +0000 Subject: [PATCH 15/44] gtk: Set WebKit's style from current theme. --- browser/native_window_gtk.cc | 41 ++++++++++++++++++++++++++++++++++++ browser/native_window_gtk.h | 3 +++ 2 files changed, 44 insertions(+) diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc index d063d1ee015d..9b7388202309 100644 --- a/browser/native_window_gtk.cc +++ b/browser/native_window_gtk.cc @@ -8,10 +8,20 @@ #include "common/options_switches.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" +#include "ui/gfx/gtk_util.h" #include "ui/gfx/rect.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), @@ -34,6 +44,8 @@ NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, g_signal_connect(window_, "delete-event", G_CALLBACK(OnWindowDeleteEventThunk), this); + + SetWebKitColorStyle(); } NativeWindowGtk::~NativeWindowGtk() { @@ -224,6 +236,35 @@ void NativeWindowGtk::UpdateDraggableRegions( const std::vector& regions) { } +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; +} + gboolean NativeWindowGtk::OnWindowDeleteEvent(GtkWidget* widget, GdkEvent* event) { Close(); diff --git a/browser/native_window_gtk.h b/browser/native_window_gtk.h index 70a855d03606..553d13116a41 100644 --- a/browser/native_window_gtk.h +++ b/browser/native_window_gtk.h @@ -60,6 +60,9 @@ class NativeWindowGtk : public NativeWindow { const std::vector& regions) OVERRIDE; private: + // Set WebKit's style from current theme. + void SetWebKitColorStyle(); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowDeleteEvent, GdkEvent*); From 406f0b7bc776c6a4c0684e143434b7d693ba2911 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 14:38:38 +0000 Subject: [PATCH 16/44] Implement "blur" window event. --- browser/native_window_gtk.cc | 9 +++++++++ browser/native_window_gtk.h | 1 + 2 files changed, 10 insertions(+) diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc index 9b7388202309..f749e19d095e 100644 --- a/browser/native_window_gtk.cc +++ b/browser/native_window_gtk.cc @@ -8,8 +8,10 @@ #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/gfx/gtk_util.h" #include "ui/gfx/rect.h" +#include "ui/gfx/skia_utils_gtk.h" namespace atom { @@ -44,6 +46,8 @@ NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, g_signal_connect(window_, "delete-event", G_CALLBACK(OnWindowDeleteEventThunk), this); + g_signal_connect(window_, "focus-out-event", + G_CALLBACK(OnFocusOutThunk), this); SetWebKitColorStyle(); } @@ -271,6 +275,11 @@ gboolean NativeWindowGtk::OnWindowDeleteEvent(GtkWidget* widget, return TRUE; } +gboolean NativeWindowGtk::OnFocusOut(GtkWidget* window, GdkEventFocus*) { + NotifyWindowBlur(); + return FALSE; +} + // static NativeWindow* NativeWindow::Create(content::WebContents* web_contents, base::DictionaryValue* options) { diff --git a/browser/native_window_gtk.h b/browser/native_window_gtk.h index 553d13116a41..96ca18edd4e1 100644 --- a/browser/native_window_gtk.h +++ b/browser/native_window_gtk.h @@ -65,6 +65,7 @@ class NativeWindowGtk : public NativeWindow { CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowDeleteEvent, GdkEvent*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnFocusOut, GdkEventFocus*); GtkWindow* window_; From 53a4f34433553d1e18a1cc1dba6d6d0a449d6600 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 14:42:47 +0000 Subject: [PATCH 17/44] :memo: Add docs on window events. --- docs/api/browser/browser-window.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/api/browser/browser-window.md b/docs/api/browser/browser-window.md index 3500b9c68040..e078fa77268b 100644 --- a/docs/api/browser/browser-window.md +++ b/docs/api/browser/browser-window.md @@ -99,6 +99,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. From 2c28725722867c594f34767afcad12865433ee2a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 15 Jan 2014 15:15:45 +0000 Subject: [PATCH 18/44] gtk: Support frameless window. --- atom.gyp | 2 + browser/native_window_gtk.cc | 149 ++++++++++++++- browser/native_window_gtk.h | 29 ++- browser/ui/gtk/gtk_window_util.cc | 295 ++++++++++++++++++++++++++++++ browser/ui/gtk/gtk_window_util.h | 74 ++++++++ 5 files changed, 542 insertions(+), 7 deletions(-) create mode 100644 browser/ui/gtk/gtk_window_util.cc create mode 100644 browser/ui/gtk/gtk_window_util.h diff --git a/atom.gyp b/atom.gyp index 05bcdd10b9b2..9cd9f29677b7 100644 --- a/atom.gyp +++ b/atom.gyp @@ -123,6 +123,8 @@ 'browser/ui/message_box_win.cc', 'browser/ui/nsalert_synchronous_sheet_mac.h', 'browser/ui/nsalert_synchronous_sheet_mac.mm', + 'browser/ui/gtk/gtk_window_util.cc', + 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/win/menu_2.cc', 'browser/ui/win/menu_2.h', 'browser/ui/win/native_menu_win.cc', diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc index f749e19d095e..711b8b9bc32c 100644 --- a/browser/native_window_gtk.cc +++ b/browser/native_window_gtk.cc @@ -5,10 +5,13 @@ #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" @@ -28,7 +31,7 @@ NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, base::DictionaryValue* options) : NativeWindow(web_contents, options), window_(GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL))), - fullscreen_(false), + state_(GDK_WINDOW_STATE_WITHDRAWN), is_always_on_top_(false) { gtk_container_add(GTK_CONTAINER(window_), GetWebContents()->GetView()->GetNativeView()); @@ -38,17 +41,30 @@ NativeWindowGtk::NativeWindowGtk(content::WebContents* web_contents, options->GetInteger(switches::kHeight, &height); gtk_window_set_default_size(window_, width, height); - if (!has_frame_) - gtk_window_set_decorated(window_, false); - 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(); } @@ -111,7 +127,6 @@ void NativeWindowGtk::Restore() { } void NativeWindowGtk::SetFullscreen(bool fullscreen) { - fullscreen_ = fullscreen; if (fullscreen) gtk_window_fullscreen(window_); else @@ -119,7 +134,7 @@ void NativeWindowGtk::SetFullscreen(bool fullscreen) { } bool NativeWindowGtk::IsFullscreen() { - return fullscreen_; + return state_ & GDK_WINDOW_STATE_FULLSCREEN; } void NativeWindowGtk::SetSize(const gfx::Size& size) { @@ -238,6 +253,27 @@ gfx::NativeWindow NativeWindowGtk::GetNativeWindow() { 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() { @@ -269,6 +305,20 @@ void NativeWindowGtk::SetWebKitColorStyle() { 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(); @@ -280,6 +330,93 @@ gboolean NativeWindowGtk::OnFocusOut(GtkWidget* window, GdkEventFocus*) { 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) { diff --git a/browser/native_window_gtk.h b/browser/native_window_gtk.h index 96ca18edd4e1..6fee4fc66103 100644 --- a/browser/native_window_gtk.h +++ b/browser/native_window_gtk.h @@ -8,6 +8,7 @@ #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" @@ -63,17 +64,43 @@ class NativeWindowGtk : public NativeWindow { // 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_; - bool fullscreen_; + 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); }; diff --git a/browser/ui/gtk/gtk_window_util.cc b/browser/ui/gtk/gtk_window_util.cc new file mode 100644 index 000000000000..7cd84b9506ce --- /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(event->type == GDK_BUTTON_PRESS); + DCHECK(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_ From 521fb7d54cd334237d4e5f3ab4ddfb69c6c7ffb5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 13:34:59 +0000 Subject: [PATCH 19/44] linux: Fix compilation error. --- browser/auto_updater_linux.cc | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/browser/auto_updater_linux.cc b/browser/auto_updater_linux.cc index 845eb45bb99e..ccdc4ed8e008 100644 --- a/browser/auto_updater_linux.cc +++ b/browser/auto_updater_linux.cc @@ -6,28 +6,12 @@ namespace auto_updater { -// static -void AutoUpdater::Init() { -} - // static void AutoUpdater::SetFeedURL(const std::string& url) { } -// static -void AutoUpdater::SetAutomaticallyChecksForUpdates(bool yes) { -} - -// static -void AutoUpdater::SetAutomaticallyDownloadsUpdates(bool yes) { -} - // static void AutoUpdater::CheckForUpdates() { } -// static -void AutoUpdater::CheckForUpdatesInBackground() { -} - } // namespace auto_updater From 426e7645bc3c783430a53891a288aed83098ff5b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 13:41:20 +0000 Subject: [PATCH 20/44] gtk: Add dummy implementation of Menu. --- atom.gyp | 2 ++ browser/api/atom_api_menu.h | 2 +- browser/api/atom_api_menu_gtk.cc | 32 ++++++++++++++++++++++++++++++++ browser/api/atom_api_menu_gtk.h | 30 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 browser/api/atom_api_menu_gtk.cc create mode 100644 browser/api/atom_api_menu_gtk.h diff --git a/atom.gyp b/atom.gyp index 4ff70a69bb95..6c795a258170 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', 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_ From 6bd56f2a5245287b55a3b634d92190b6d258dd69 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 13:59:41 +0000 Subject: [PATCH 21/44] gtk: Add utils imported from chrome. --- atom.gyp | 8 +- browser/native_window.h | 2 +- browser/ui/gtk/gtk_custom_menu.cc | 150 ++++++++ browser/ui/gtk/gtk_custom_menu.h | 51 +++ browser/ui/gtk/gtk_custom_menu_item.cc | 493 +++++++++++++++++++++++++ browser/ui/gtk/gtk_custom_menu_item.h | 139 +++++++ browser/ui/gtk/gtk_window_util.cc | 4 +- 7 files changed, 842 insertions(+), 5 deletions(-) create mode 100644 browser/ui/gtk/gtk_custom_menu.cc create mode 100644 browser/ui/gtk/gtk_custom_menu.h create mode 100644 browser/ui/gtk/gtk_custom_menu_item.cc create mode 100644 browser/ui/gtk/gtk_custom_menu_item.h diff --git a/atom.gyp b/atom.gyp index 6c795a258170..dd1506233f19 100644 --- a/atom.gyp +++ b/atom.gyp @@ -121,11 +121,15 @@ 'browser/ui/file_dialog.h', '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_mac.mm', 'browser/ui/message_box_win.cc', - 'browser/ui/gtk/gtk_window_util.cc', - 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/win/menu_2.cc', 'browser/ui/win/menu_2.h', 'browser/ui/win/native_menu_win.cc', diff --git a/browser/native_window.h b/browser/native_window.h index 0e802fd110d9..0b82b969c723 100644 --- a/browser/native_window.h +++ b/browser/native_window.h @@ -56,7 +56,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, class DialogScope { public: - DialogScope(NativeWindow* window) + explicit DialogScope(NativeWindow* window) : window_(window) { if (window_ != NULL) window_->set_has_dialog_attached(true); 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 index 7cd84b9506ce..c06e3302b373 100644 --- a/browser/ui/gtk/gtk_window_util.cc +++ b/browser/ui/gtk/gtk_window_util.cc @@ -143,8 +143,8 @@ bool HandleTitleBarLeftMousePress( // 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(event->type == GDK_BUTTON_PRESS); - DCHECK(event->button == 1); + DCHECK_EQ(event->type, GDK_BUTTON_PRESS); + DCHECK_EQ(event->button, 1); static GtkSettings* settings = gtk_settings_get_default(); gint double_click_time = 250; From e3d5b62000fbd8f98615aa81f53e6aa38e2dd3bd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 14:07:23 +0000 Subject: [PATCH 22/44] gtk: Add dummy implementation of dialog. --- atom.gyp | 2 ++ browser/ui/file_dialog_gtk.cc | 41 +++++++++++++++++++++++++++++++++++ browser/ui/message_box_gtk.cc | 30 +++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 browser/ui/file_dialog_gtk.cc create mode 100644 browser/ui/message_box_gtk.cc diff --git a/atom.gyp b/atom.gyp index dd1506233f19..190662789369 100644 --- a/atom.gyp +++ b/atom.gyp @@ -119,6 +119,7 @@ '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', @@ -128,6 +129,7 @@ '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', 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/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 From 526aaecc529f64fe1d1eb5a703df8e7abf5b813a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 14:39:57 +0000 Subject: [PATCH 23/44] linux: Add dummy implementation of node bindings. --- atom.gyp | 20 ++++++++++++++++++++ common/node_bindings_linux.cc | 24 ++++++++++++++++++++++++ common/node_bindings_linux.h | 26 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 common/node_bindings_linux.cc create mode 100644 common/node_bindings_linux.h diff --git a/atom.gyp b/atom.gyp index 190662789369..6a96bd840c3b 100644 --- a/atom.gyp +++ b/atom.gyp @@ -177,6 +177,8 @@ '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', @@ -325,6 +327,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) { diff --git a/common/node_bindings_linux.cc b/common/node_bindings_linux.cc new file mode 100644 index 000000000000..16e1e9b4e06b --- /dev/null +++ b/common/node_bindings_linux.cc @@ -0,0 +1,24 @@ +// 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" + +namespace atom { + +NodeBindingsLinux::NodeBindingsLinux(bool is_browser) + : NodeBindings(is_browser) { +} + +NodeBindingsLinux::~NodeBindingsLinux() { +} + +void NodeBindingsLinux::PollEvents() { +} + +// static +NodeBindings* NodeBindings::Create(bool is_browser) { + return new NodeBindingsLinux(is_browser); +} + +} // namespace at diff --git a/common/node_bindings_linux.h b/common/node_bindings_linux.h new file mode 100644 index 000000000000..b4a294144c4f --- /dev/null +++ b/common/node_bindings_linux.h @@ -0,0 +1,26 @@ +// 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(); + + private: + virtual void PollEvents() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(NodeBindingsLinux); +}; + +} // namespace atom + +#endif // ATOM_COMMON_NODE_BINDINGS_LINUX_H_ From 2b7b4a16f5e900a383049c360721c2aaf790f918 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 15:11:57 +0000 Subject: [PATCH 24/44] linux: Make binary search for libraries under current directory. --- atom.gyp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/atom.gyp b/atom.gyp index 6a96bd840c3b..191a44cd685c 100644 --- a/atom.gyp +++ b/atom.gyp @@ -392,12 +392,22 @@ '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': { + # 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 + 'ldflags': [ + '-rpath \$$ORIGIN', + ], + }, + }], # OS=="linux" ], }, # target <(product_name)_lib { From d698ecf017ba22893af4be57d071cdc4f5e44e2c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 14 Feb 2014 15:17:24 +0000 Subject: [PATCH 25/44] linux: Make test.py work. --- script/test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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:]) From 0a9c371ca28f9a99054e72caa57bc2e59719ffea Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 19 Feb 2014 11:39:01 +0000 Subject: [PATCH 26/44] linux: Fix crash when using protocol module early. --- browser/api/atom_api_protocol.cc | 6 ++++++ 1 file changed, 6 insertions(+) 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); From c340cac02cf4e17cd0eb27136ddc1de5df1d434e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 19 Feb 2014 11:39:51 +0000 Subject: [PATCH 27/44] Build with symbols on debug build. --- atom.gyp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/atom.gyp b/atom.gyp index 191a44cd685c..721f2aa22e32 100644 --- a/atom.gyp +++ b/atom.gyp @@ -230,9 +230,8 @@ ], 'configurations': { 'Debug': { - 'defines': [ - 'DEBUG', - ], + 'defines': [ 'DEBUG', '_DEBUG' ], + 'cflags': [ '-g', '-O0' ], }, }, }, From 27cd6688c112f73295d8f3773acce57586ee6e70 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 19 Feb 2014 13:06:45 +0000 Subject: [PATCH 28/44] BrowserWindow::setMenu is available on Linux. --- browser/api/lib/browser-window.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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' From 87b78a89fb6f5ad959e16fd1ae976c0b625ee97e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 19 Feb 2014 13:10:09 +0000 Subject: [PATCH 29/44] Menu::attachToWindow is available on Linux. --- browser/api/atom_api_menu.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ea8d349b1b9ef22e0985d3e5065ae75c4f474ad7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 19 Feb 2014 21:22:59 +0800 Subject: [PATCH 30/44] :lipstick: Fix cpplinting. --- script/cpplint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/script/cpplint.py b/script/cpplint.py index e4314142b27b..90b508d348a0 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -13,6 +13,7 @@ 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', 'common/api/api_messages.cc', 'common/api/api_messages.h', 'common/atom_version.h', From e9879b150e9be871e3ddc3b7b7b4aaf97f0c7b79 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 16:43:26 +0800 Subject: [PATCH 31/44] Update runas to 0.5.* --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" }, From c56480fd8905332e54649dac0ade95c825e8ba23 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 18:20:29 +0800 Subject: [PATCH 32/44] Update libchromiumcontent to use the thin version. --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From 94b3de557e51cf69de2811d7f19554dc86e99c4d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 18:39:24 +0800 Subject: [PATCH 33/44] :memo: Add docs on building for Linux. --- docs/README.md | 1 + docs/development/build-instructions-linux.md | 65 ++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 docs/development/build-instructions-linux.md diff --git a/docs/README.md b/docs/README.md index cf0cc9905ff2..60ac64d9c54a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md new file mode 100644 index 000000000000..73d7a0723f4d --- /dev/null +++ b/docs/development/build-instructions-linux.md @@ -0,0 +1,65 @@ +# Build instructions (Linux) + +## Prerequisites + +* [node.js](http://nodejs.org) +* clang + +## 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 +``` + From 2b82eafff4ebe9692b0ceb0e918ad47b2e22d6c3 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 18:51:57 +0800 Subject: [PATCH 34/44] :memo: Update docs on using native modules. --- docs/README.md | 2 +- docs/development/build-instructions-linux.md | 2 +- ...ative-modules.md => use-native-modules.md} | 25 +++++++++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) rename docs/{build-native-modules.md => use-native-modules.md} (63%) diff --git a/docs/README.md b/docs/README.md index 60ac64d9c54a..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 diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index 73d7a0723f4d..9556b56acb54 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -54,7 +54,7 @@ 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 +$ sudo apt-get install gcc-multilib g++-multilib ``` ## Tests 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 From 679aa431133f06b9f13751049cbd7a96d790fa20 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 18:56:59 +0800 Subject: [PATCH 35/44] :memo: List libraries required for Linux. --- docs/development/build-instructions-linux.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index 9556b56acb54..f4c5a6263b1a 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -3,7 +3,13 @@ ## Prerequisites * [node.js](http://nodejs.org) -* clang +* 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 From b4ee01d43dad172053cdb71525fb91b09bd32cec Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 20 Feb 2014 18:58:56 +0800 Subject: [PATCH 36/44] linux: Fix one compiler warning. --- common.gypi | 1 + 1 file changed, 1 insertion(+) diff --git a/common.gypi b/common.gypi index 64dd63d2c584..62cce1ef8cd0 100644 --- a/common.gypi +++ b/common.gypi @@ -85,6 +85,7 @@ '-Wno-pointer-sign', '-Wno-string-plus-int', '-Wno-unused-variable', + '-Wno-unused-value', '-Wno-deprecated-declarations', '-Wno-return-type', ], From 3576c6d2ffa5622d8d596caedd1805582966190a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 00:56:18 +0800 Subject: [PATCH 37/44] Fix race condition when initializing request context getter. Note that we are calling GetURLRequestContext() in the UI thread when using the protocol module, this should in fact not be allowed, but for now we just use the workaround of making sure the request context getter has been initialized before we use the protocol module. --- browser/net/atom_url_request_context_getter.cc | 1 + browser/net/atom_url_request_context_getter.h | 3 +++ 2 files changed, 4 insertions(+) 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_; From 18f8af78225dea2ef48aafde6abd183b5b6803c7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 01:04:27 +0800 Subject: [PATCH 38/44] Only append arguments for browser process. --- app/atom_main_delegate.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. From fbe963c7f3acf3a291afa3cb411f3d588559d244 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 01:07:47 +0800 Subject: [PATCH 39/44] :lipstick: Fix cpplint warnings. --- common/node_bindings_linux.cc | 2 +- script/cpplint.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/node_bindings_linux.cc b/common/node_bindings_linux.cc index 16e1e9b4e06b..4f8ad9903a31 100644 --- a/common/node_bindings_linux.cc +++ b/common/node_bindings_linux.cc @@ -21,4 +21,4 @@ NodeBindings* NodeBindings::Create(bool is_browser) { return new NodeBindingsLinux(is_browser); } -} // namespace at +} // namespace atom diff --git a/script/cpplint.py b/script/cpplint.py index 90b508d348a0..aba6064908e4 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -14,6 +14,7 @@ IGNORE_FILES = [ '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', From eb9673a1529348e76a4b14ea4c5fbb2f95659493 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 13:21:02 +0800 Subject: [PATCH 40/44] linux: Implement libuv message loop polling. --- common/node_bindings_linux.cc | 35 ++++++++++++++++++++++++++++++++++- common/node_bindings_linux.h | 8 ++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/common/node_bindings_linux.cc b/common/node_bindings_linux.cc index 4f8ad9903a31..035441c8dc37 100644 --- a/common/node_bindings_linux.cc +++ b/common/node_bindings_linux.cc @@ -4,16 +4,49 @@ #include "common/node_bindings_linux.h" +#include + namespace atom { NodeBindingsLinux::NodeBindingsLinux(bool is_browser) - : NodeBindings(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 diff --git a/common/node_bindings_linux.h b/common/node_bindings_linux.h index b4a294144c4f..83711543d80f 100644 --- a/common/node_bindings_linux.h +++ b/common/node_bindings_linux.h @@ -15,9 +15,17 @@ class NodeBindingsLinux : public NodeBindings { 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); }; From 6c36f7e5c96850fd65a11d52a4e523e9c605a542 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 15:34:38 +0800 Subject: [PATCH 41/44] gtk: Window.focus should not change visibility. --- browser/native_window_gtk.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/browser/native_window_gtk.cc b/browser/native_window_gtk.cc index 711b8b9bc32c..8b9ecc217c8b 100644 --- a/browser/native_window_gtk.cc +++ b/browser/native_window_gtk.cc @@ -88,6 +88,9 @@ void NativeWindowGtk::Move(const gfx::Rect& pos) { } void NativeWindowGtk::Focus(bool focus) { + if (!IsVisible()) + return; + if (focus) gtk_window_present(window_); else From c26a9b23a765ca45db472cad7ab150f7d0f5736f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 15:50:35 +0800 Subject: [PATCH 42/44] gtk: Should init gdk when using screen module. --- common/api/atom_api_screen.cc | 9 +++++++++ 1 file changed, 9 insertions(+) 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")); From 2b2a55d8702cd288b73ec02f1c5407f8082e7eaa Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 16:12:49 +0800 Subject: [PATCH 43/44] Update apm: fix node arch on Linux. --- vendor/apm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/apm b/vendor/apm index af40a0ed55a5..a9e5498a838b 160000 --- a/vendor/apm +++ b/vendor/apm @@ -1 +1 @@ -Subproject commit af40a0ed55a5df3e8f7a7707e17ab2493edb94a3 +Subproject commit a9e5498a838bf228fca53c32f66e2e6d5adcf783 From 1b7c308475472d48adffd16ef10cb9a037de9ce0 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 21 Feb 2014 17:22:05 +0800 Subject: [PATCH 44/44] linux: Make native modules work. --- atom.gyp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/atom.gyp b/atom.gyp index 721f2aa22e32..a17155bb3760 100644 --- a/atom.gyp +++ b/atom.gyp @@ -399,11 +399,13 @@ }], # OS=="mac" ['OS=="linux"', { 'link_settings': { - # 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 '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"