diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 4ba87d9cfa7f..6b0d7e478ecb 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/api/atom_api_menu.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/browser.h" #include "atom/common/native_mate_converters/file_path_converter.h" @@ -32,6 +33,32 @@ using atom::Browser; +namespace mate { + +#if defined(OS_WIN) +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + Browser::UserTask* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!dict.Get("program", &(out->program)) || + !dict.Get("title", &(out->title))) + return false; + if (dict.Get("iconPath", &(out->icon_path)) && + !dict.Get("iconIndex", &(out->icon_index))) + return false; + dict.Get("arguments", &(out->arguments)); + dict.Get("description", &(out->description)); + return true; + } +}; +#endif + +} // namespace mate + + namespace atom { namespace api { @@ -157,6 +184,14 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("getName", base::Bind(&Browser::GetName, browser)) .SetMethod("setName", base::Bind(&Browser::SetName, browser)) .SetMethod("isReady", base::Bind(&Browser::is_ready, browser)) + .SetMethod("addRecentDocument", + base::Bind(&Browser::AddRecentDocument, browser)) + .SetMethod("clearRecentDocuments", + base::Bind(&Browser::ClearRecentDocuments, browser)) +#if defined(OS_WIN) + .SetMethod("setUserTasks", + base::Bind(&Browser::SetUserTasks, browser)) +#endif .SetMethod("getDataPath", &App::GetDataPath) .SetMethod("resolveProxy", &App::ResolveProxy) .SetMethod("setDesktopName", &App::SetDesktopName); @@ -191,12 +226,15 @@ int DockBounce(const std::string& type) { request_id = Browser::Get()->DockBounce(Browser::BOUNCE_INFORMATIONAL); return request_id; } + +void DockSetMenu(atom::api::Menu* menu) { + Browser::Get()->DockSetMenu(menu->model()); +} #endif void Initialize(v8::Handle exports, v8::Handle unused, v8::Handle context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); - Browser* browser = Browser::Get(); CommandLine* command_line = CommandLine::ForCurrentProcess(); mate::Dictionary dict(isolate, exports); @@ -206,20 +244,17 @@ void Initialize(v8::Handle exports, v8::Handle unused, base::Bind(&CommandLine::AppendArg, base::Unretained(command_line))); #if defined(OS_MACOSX) + auto browser = base::Unretained(Browser::Get()); dict.SetMethod("dockBounce", &DockBounce); dict.SetMethod("dockCancelBounce", - base::Bind(&Browser::DockCancelBounce, - base::Unretained(browser))); + base::Bind(&Browser::DockCancelBounce, browser)); dict.SetMethod("dockSetBadgeText", - base::Bind(&Browser::DockSetBadgeText, - base::Unretained(browser))); + base::Bind(&Browser::DockSetBadgeText, browser)); dict.SetMethod("dockGetBadgeText", - base::Bind(&Browser::DockGetBadgeText, - base::Unretained(browser))); - dict.SetMethod("dockHide", - base::Bind(&Browser::DockHide, base::Unretained(browser))); - dict.SetMethod("dockShow", - base::Bind(&Browser::DockShow, base::Unretained(browser))); + base::Bind(&Browser::DockGetBadgeText, browser)); + dict.SetMethod("dockHide", base::Bind(&Browser::DockHide, browser)); + dict.SetMethod("dockShow", base::Bind(&Browser::DockShow, browser)); + dict.SetMethod("dockSetMenu", &DockSetMenu); #endif } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 9be7b8aca817..a9e2a17a13dd 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -33,20 +33,19 @@ class App : public mate::EventEmitter, App(); virtual ~App(); - // BrowserObserver implementations: - virtual void OnWillQuit(bool* prevent_default) OVERRIDE; - virtual void OnWindowAllClosed() OVERRIDE; - virtual void OnQuit() OVERRIDE; - virtual void OnOpenFile(bool* prevent_default, - const std::string& file_path) OVERRIDE; - virtual void OnOpenURL(const std::string& url) OVERRIDE; - virtual void OnActivateWithNoOpenWindows() OVERRIDE; - virtual void OnWillFinishLaunching() OVERRIDE; - virtual void OnFinishLaunching() OVERRIDE; + // BrowserObserver: + void OnWillQuit(bool* prevent_default) override; + void OnWindowAllClosed() override; + void OnQuit() override; + void OnOpenFile(bool* prevent_default, const std::string& file_path) override; + void OnOpenURL(const std::string& url) override; + void OnActivateWithNoOpenWindows() override; + void OnWillFinishLaunching() override; + void OnFinishLaunching() override; - // mate::Wrappable implementations: - virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate); + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; private: base::FilePath GetDataPath(); diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index a9962ca9cf6a..51fd66098ac0 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -26,6 +26,7 @@ if process.platform is 'darwin' getBadge: bindings.dockGetBadgeText hide: bindings.dockHide show: bindings.dockShow + setMenu: bindings.dockSetMenu # Be compatible with old API. app.once 'ready', -> app.emit 'finish-launching' diff --git a/atom/browser/atom_browser_main_parts_mac.mm b/atom/browser/atom_browser_main_parts_mac.mm index 587c7a26a19e..d31dde15dd68 100644 --- a/atom/browser/atom_browser_main_parts_mac.mm +++ b/atom/browser/atom_browser_main_parts_mac.mm @@ -20,7 +20,7 @@ void AtomBrowserMainParts::PreMainMessageLoopStart() { // Force the NSApplication subclass to be used. NSApplication* application = [AtomApplication sharedApplication]; - AtomApplicationDelegate* delegate = [AtomApplicationDelegate alloc]; + AtomApplicationDelegate* delegate = [[AtomApplicationDelegate alloc] init]; [NSApp setDelegate:(id)delegate]; base::FilePath frameworkPath = brightray::MainApplicationBundlePath() @@ -41,7 +41,8 @@ void AtomBrowserMainParts::PreMainMessageLoopStart() { } void AtomBrowserMainParts::PostDestroyThreads() { - [[AtomApplication sharedApplication] setDelegate:nil]; + [[NSApp delegate] release]; + [NSApp setDelegate:nil]; } } // namespace atom diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 4761c7ca81f0..b658de410209 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -70,6 +70,10 @@ std::string Browser::GetName() const { void Browser::SetName(const std::string& name) { name_override_ = name; + +#if defined(OS_WIN) + SetAppUserModelID(name); +#endif } bool Browser::OpenFile(const std::string& file_path) { diff --git a/atom/browser/browser.h b/atom/browser/browser.h index d46c2922b610..d079d53a6dea 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_BROWSER_H_ #include +#include #include "base/basictypes.h" #include "base/compiler_specific.h" @@ -13,6 +14,19 @@ #include "atom/browser/browser_observer.h" #include "atom/browser/window_list_observer.h" +#if defined(OS_WIN) +#include "base/files/file_path.h" +#include "base/strings/string16.h" +#endif + +namespace base { +class FilePath; +} + +namespace ui { +class MenuModel; +} + namespace atom { // This class is used for control application-wide operations. @@ -44,6 +58,12 @@ class Browser : public WindowListObserver { // Overrides the application name. void SetName(const std::string& name); + // Add the |path| to recent documents list. + void AddRecentDocument(const base::FilePath& path); + + // Clear the recent documents list. + void ClearRecentDocuments(); + #if defined(OS_MACOSX) // Bounce the dock icon. enum BounceType { @@ -60,8 +80,28 @@ class Browser : public WindowListObserver { // Hide/Show dock. void DockHide(); void DockShow(); + + // Set docks' menu. + void DockSetMenu(ui::MenuModel* model); #endif // defined(OS_MACOSX) +#if defined(OS_WIN) + struct UserTask { + base::FilePath program; + base::string16 arguments; + base::string16 title; + base::string16 description; + base::FilePath icon_path; + int icon_index; + }; + + // Add a custom task to jump list. + void SetUserTasks(const std::vector& tasks); + + // Set the application user model ID, called when "SetName" is called. + void SetAppUserModelID(const std::string& name); +#endif + // Tell the application to open a file. bool OpenFile(const std::string& file_path); @@ -100,8 +140,8 @@ class Browser : public WindowListObserver { private: // WindowListObserver implementations: - virtual void OnWindowCloseCancelled(NativeWindow* window) OVERRIDE; - virtual void OnWindowAllClosed() OVERRIDE; + void OnWindowCloseCancelled(NativeWindow* window) override; + void OnWindowAllClosed() override; // Observers of the browser. ObserverList observers_; @@ -112,6 +152,10 @@ class Browser : public WindowListObserver { std::string version_override_; std::string name_override_; +#if defined(OS_WIN) + base::string16 app_user_model_id_; +#endif + DISALLOW_COPY_AND_ASSIGN(Browser); }; diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 5ede610813b8..7534aa2182d9 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -24,6 +24,12 @@ void Browser::Focus() { } } +void Browser::AddRecentDocument(const base::FilePath& path) { +} + +void Browser::ClearRecentDocuments() { +} + std::string Browser::GetExecutableFileVersion() const { return ATOM_VERSION_STRING; } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 0e5c46a37a78..797c560728fa 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -5,9 +5,11 @@ #include "atom/browser/browser.h" #import "atom/browser/mac/atom_application.h" +#import "atom/browser/mac/atom_application_delegate.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" #import "base/mac/bundle_locations.h" +#import "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" namespace atom { @@ -16,6 +18,14 @@ void Browser::Focus() { [[AtomApplication sharedApplication] activateIgnoringOtherApps:YES]; } +void Browser::AddRecentDocument(const base::FilePath& path) { + NSURL* u = [NSURL fileURLWithPath:base::mac::FilePathToNSString(path)]; + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:u]; +} + +void Browser::ClearRecentDocuments() { +} + std::string Browser::GetExecutableFileVersion() const { NSDictionary* infoDictionary = base::mac::OuterBundle().infoDictionary; NSString *version = [infoDictionary objectForKey:@"CFBundleVersion"]; @@ -60,4 +70,9 @@ void Browser::DockShow() { TransformProcessType(&psn, kProcessTransformToForegroundApplication); } +void Browser::DockSetMenu(ui::MenuModel* model) { + AtomApplicationDelegate* delegate = [NSApp delegate]; + [delegate setApplicationDockMenu:model]; +} + } // namespace atom diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index 4cbf0f9647dd..781d905792f0 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -4,14 +4,21 @@ #include "atom/browser/browser.h" +#include +#include #include +#include +#include #include "base/base_paths.h" #include "base/file_version_info.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" #include "atom/common/atom_version.h" namespace atom { @@ -39,6 +46,83 @@ void Browser::Focus() { EnumWindows(&WindowsEnumerationHandler, reinterpret_cast(&pid)); } +void Browser::AddRecentDocument(const base::FilePath& path) { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return; + + CComPtr item; + HRESULT hr = SHCreateItemFromParsingName( + path.value().c_str(), NULL, IID_PPV_ARGS(&item)); + if (SUCCEEDED(hr)) { + SHARDAPPIDINFO info; + info.psi = item; + info.pszAppID = app_user_model_id_.c_str(); + SHAddToRecentDocs(SHARD_APPIDINFO, &info); + } +} + +void Browser::ClearRecentDocuments() { + CComPtr destinations; + if (FAILED(destinations.CoCreateInstance(CLSID_ApplicationDestinations, + NULL, CLSCTX_INPROC_SERVER))) + return; + if (FAILED(destinations->SetAppID(app_user_model_id_.c_str()))) + return; + destinations->RemoveAllDestinations(); +} + +void Browser::SetUserTasks(const std::vector& tasks) { + CComPtr destinations; + if (FAILED(destinations.CoCreateInstance(CLSID_DestinationList))) + return; + if (FAILED(destinations->SetAppID(app_user_model_id_.c_str()))) + return; + + // Start a transaction that updates the JumpList of this application. + UINT max_slots; + CComPtr removed; + if (FAILED(destinations->BeginList(&max_slots, IID_PPV_ARGS(&removed)))) + return; + + CComPtr collection; + if (FAILED(collection.CoCreateInstance(CLSID_EnumerableObjectCollection))) + return; + + for (auto& task : tasks) { + CComPtr link; + if (FAILED(link.CoCreateInstance(CLSID_ShellLink)) || + FAILED(link->SetPath(task.program.value().c_str())) || + FAILED(link->SetArguments(task.arguments.c_str())) || + FAILED(link->SetDescription(task.description.c_str()))) + return; + + if (!task.icon_path.empty() && + FAILED(link->SetIconLocation(task.icon_path.value().c_str(), + task.icon_index))) + return; + + CComQIPtr property_store = link; + if (!base::win::SetStringValueForPropertyStore(property_store, PKEY_Title, + task.title.c_str())) + return; + + if (FAILED(collection->AddObject(link))) + return; + } + + // When the list is empty "AddUserTasks" could fail, so we don't check return + // value for it. + CComQIPtr task_array = collection; + destinations->AddUserTasks(task_array); + destinations->CommitList(); +} + +void Browser::SetAppUserModelID(const std::string& name) { + app_user_model_id_ = base::UTF8ToUTF16( + base::StringPrintf("atom-shell.app.%s", name)); + SetCurrentProcessExplicitAppUserModelID(app_user_model_id_.c_str()); +} + std::string Browser::GetExecutableFileVersion() const { base::FilePath path; if (PathService::Get(base::FILE_EXE, &path)) { diff --git a/atom/browser/mac/atom_application_delegate.h b/atom/browser/mac/atom_application_delegate.h index 437d76a772ca..3e5c59c3ff3d 100644 --- a/atom/browser/mac/atom_application_delegate.h +++ b/atom/browser/mac/atom_application_delegate.h @@ -4,7 +4,16 @@ #import +#import "atom/browser/ui/cocoa/atom_menu_controller.h" + @interface AtomApplicationDelegate : NSObject { + @private + base::scoped_nsobject menu_controller_; } +- (id)init; + +// Sets the menu that will be returned in "applicationDockMenu:". +- (void)setApplicationDockMenu:(ui::MenuModel*)model; + @end diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 3ffa4a7ae99c..16dcf6fd9523 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -10,6 +10,16 @@ @implementation AtomApplicationDelegate +- (id)init { + self = [super init]; + menu_controller_.reset([[AtomMenuController alloc] init]); + return self; +} + +- (void)setApplicationDockMenu:(ui::MenuModel*)model { + [menu_controller_ populateWithModel:model]; +} + - (void)applicationWillFinishLaunching:(NSNotification*)notify { atom::Browser::Get()->WillFinishLaunching(); } @@ -18,6 +28,10 @@ atom::Browser::Get()->DidFinishLaunching(); } +- (NSMenu*)applicationDockMenu:(NSApplication*)sender { + return [menu_controller_ menu]; +} + - (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename { std::string filename_str(base::SysNSStringToUTF8(filename)); diff --git a/atom/browser/ui/cocoa/atom_menu_controller.h b/atom/browser/ui/cocoa/atom_menu_controller.h index cc03f151e4b4..da10e1b0ba66 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.h +++ b/atom/browser/ui/cocoa/atom_menu_controller.h @@ -39,6 +39,9 @@ class MenuModel; // to the contents of the model after calling this will not be noticed. - (id)initWithModel:(ui::MenuModel*)model; +// Populate current NSMenu with |model|. +- (void)populateWithModel:(ui::MenuModel*)model; + // Programmatically close the constructed menu. - (void)cancel; diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 24f6a1f5d4af..176f2db7e145 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -70,7 +70,8 @@ int EventFlagsFromNSEvent(NSEvent* event) { @synthesize model = model_; - (id)init { - self = [super init]; + if ((self = [super init])) + [self menu]; return self; } @@ -93,6 +94,22 @@ int EventFlagsFromNSEvent(NSEvent* event) { [super dealloc]; } +- (void)populateWithModel:(ui::MenuModel*)model { + if (!menu_) + return; + + model_ = model; + [menu_ removeAllItems]; + + const int count = model->GetItemCount(); + for (int index = 0; index < count; index++) { + if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) + [self addSeparatorToMenu:menu_ atIndex:index]; + else + [self addItemToMenu:menu_ atIndex:index fromModel:model]; + } +} + - (void)cancel { if (isMenuOpen_) { [menu_ cancelTracking]; @@ -235,10 +252,13 @@ int EventFlagsFromNSEvent(NSEvent* event) { } - (NSMenu*)menu { - if (!menu_ && model_) { - menu_.reset([[self menuFromModel:model_] retain]); - [menu_ setDelegate:self]; - } + if (menu_) + return menu_.get(); + + menu_.reset([[NSMenu alloc] initWithTitle:@""]); + [menu_ setDelegate:self]; + if (model_) + [self populateWithModel:model_]; return menu_.get(); } diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index 69e76e98e155..ebf69ac3a65b 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -37,9 +37,11 @@ for arg in process.argv if location.protocol is 'chrome-devtools:' # Override some inspector APIs. require path.join(__dirname, 'inspector') + nodeIntegration = 'true' else if location.protocol is 'chrome-extension:' # Add implementations of chrome API. require path.join(__dirname, 'chrome-api') + nodeIntegration = 'true' else # Override default web functions. require path.join(__dirname, 'override') diff --git a/atom/renderer/lib/inspector.coffee b/atom/renderer/lib/inspector.coffee index 8b6f748e00e1..87906e69949f 100644 --- a/atom/renderer/lib/inspector.coffee +++ b/atom/renderer/lib/inspector.coffee @@ -1,7 +1,7 @@ window.onload = -> # Use menu API to show context menu. - InspectorFrontendHost.showContextMenu = (event, items) -> - createMenu items, event + InspectorFrontendHost.showContextMenuAtPoint = (x, y, items, document) -> + createMenu items # Use dialog API to override file chooser dialog. WebInspector.createFileSelectorElement = (callback) -> @@ -32,17 +32,19 @@ convertToMenuTemplate = (items) -> label: item.label enabled: item.enabled if item.id? - transformed.click = -> WebInspector.contextMenuItemSelected item.id + transformed.click = -> InspectorFrontendAPI.contextMenuItemSelected item.id template.push transformed template -createMenu = (items, event) -> +createMenu = (items) -> remote = require 'remote' Menu = remote.require 'menu' menu = Menu.buildFromTemplate convertToMenuTemplate(items) - menu.popup remote.getCurrentWindow() - event.consume true + # The menu is expected to show asynchronously. + setImmediate -> + menu.popup remote.getCurrentWindow() + InspectorFrontendAPI.contextMenuCleared() showFileChooserDialog = (callback) -> remote = require 'remote' diff --git a/docs/README.md b/docs/README.md index ed7d803b72da..0d00200def5c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ * [Application distribution](tutorial/application-distribution.md) * [Application packaging](tutorial/application-packaging.md) * [Using native node modules](tutorial/using-native-node-modules.md) +* [Desktop environment integration](tutorial/desktop-environment-integration.md) * [Debugging browser process](tutorial/debugging-browser-process.md) * [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) * [DevTools extension](tutorial/devtools-extension.md) diff --git a/docs/api/app.md b/docs/api/app.md index b9a837d77bb2..b7cb5f35aa08 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -123,6 +123,43 @@ preferred over `name` by atom-shell. Resolves the proxy information for `url`, the `callback` would be called with `callback(proxy)` when the request is done. +## app.addRecentDocument(path) + +* `path` String + +Adds `path` to recent documents list. + +This list is managed by the system, on Windows you can visit the list from task +bar, and on Mac you can visit it from dock menu. + +## app.clearRecentDocuments() + +Clears the recent documents list. + +## app.setUserTasks(tasks) + +* `tasks` Array - Array of `Task` objects + +Adds `tasks` to the [Tasks][tasks] category of JumpList on Windows. + +The `tasks` is an array of `Task` objects in following format: + +* `Task` Object + * `program` String - Path of the program to execute, usually you should + specify `process.execPath` which opens current program + * `arguments` String - The arguments of command line when `program` is + executed + * `title` String - The string to be displayed in a JumpList + * `description` String - Description of this task + * `iconPath` String - The absolute path to an icon to be displayed in a + JumpList, it can be arbitrary resource file that contains an icon, usually + you can specify `process.execPath` to show the icon of the program + * `iconIndex` Integer - The icon index in the icon file. If an icon file + consists of two or more icons, set this value to identify the icon. If an + icon file consists of one icon, this value is 0 + +**Note:** This API is only available on Windows. + ## app.commandLine.appendSwitch(switch, [value]) Append a switch [with optional value] to Chromium's command line. @@ -185,3 +222,14 @@ Hides the dock icon. Shows the dock icon. **Note:** This API is only available on Mac. + +## app.dock.setMenu(menu) + +* `menu` Menu + +Sets the application [dock menu][dock-menu]. + +**Note:** This API is only available on Mac. + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..2afeb92a92ac --- /dev/null +++ b/docs/tutorial/desktop-environment-integration.md @@ -0,0 +1,201 @@ +# Desktop environment integration + +Different operating systems provide different features on integrating desktop +applications into their desktop environments, for example, on Windows +applications can put shortcuts in the JumpList of task bar, and on Mac +applications can put a custom menu in the dock menu. + +This guide introduces how to integrate your application into those desktop +environments with atom-shell APIs. + +## Recent documents (Windows & OS X) + +Windows and OS X have provided easy access to recent documents opened by the +application via JumpList and dock menu. + +__JumpList:__ + +![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Application dock menu:__ + + + +To add a file to recent documents, you can use +[app.addRecentDocument][addrecentdocument] API: + +```javascript +var app = require('app'); +app.addRecentDocument('/Users/aryastark/github/atom-shell/README.md'); +``` + +And you can use [app.clearRecentDocuments](clearrecentdocuments) API to empty +the recent documents list: + +```javascript +app.clearRecentDocuments(); +``` + +### Windows notes + +In order to be able to use this feature on Windows, your application has to be +registered as handler of the file type of the document, otherwise the file won't +appear in JumpList even after you have added it. You can find everything on +registering your application in [Application Registration][app-registration]. + +When a user clicks a file from JumpList, a new instance of your application will +be started with the path of file appended in command line. + +### OS X notes + +When a file is requested from the recent documents menu, the `open-file` event +of `app` module would be emitted for it. + +## Custom dock menu (OS X) + +OS X enables developers to specify a custom menu for dock, which usually +contains some shortcuts for commonly used features of your application: + +__Dock menu of Terminal.app:__ + + + +To set your custom dock menu, you can use the `app.dock.setMenu` API, which is +only available on OS X: + +```javascript +var app = require('app'); +var Menu = require('menu'); +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'}, + ]}, + { label: 'New Command...'}, +]); +app.dock.setMenu(dockMenu); +``` + +## User tasks (Windows) + +On Windows you can specify custom actions in the `Tasks` category of JumpList, +as quoted from MSDN: + +> Applications define tasks based on both the program's features and the key +> things a user is expected to do with them. Tasks should be context-free, in +> that the application does not need to be running for them to work. They +> should also be the statistically most common actions that a normal user would +> perform in an application, such as compose an email message or open the +> calendar in a mail program, create a new document in a word processor, launch +> an application in a certain mode, or launch one of its subcommands. An +> application should not clutter the menu with advanced features that standard +> users won't need or one-time actions such as registration. Do not use tasks +> for promotional items such as upgrades or special offers. +> +> It is strongly recommended that the task list be static. It should remain the +> same regardless of the state or status of the application. While it is +> possible to vary the list dynamically, you should consider that this could +> confuse the user who does not expect that portion of the destination list to +> change. + +__Tasks of Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +Unlike the dock menu in OS X which is a real menu, user tasks in Windows work +like application shortcuts that when user clicks a task a program would be +executed with specified arguments. + +To set user tasks for your application, you can use +[app.setUserTasks][setusertaskstasks] API: + +```javascript +var app = require('app'); +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window' + description: 'Create a new winodw', + } +]); +``` + +To clean your tasks list, just call `app.setUserTasks` with empty array: + +```javascript +app.setUserTasks([]); +``` + +The user tasks will still show even after your application closes, so the icon +and program path specified for a task should exist until your application is +uninstalled. + +## Unity launcher shortcuts (Linux) + +In Unity, you can add custom entries to its launcher via modifying `.desktop` +file, see [Adding shortcuts to a launcher][unity-launcher]. + +__Launcher shortcuts of Audacious:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Progress bar in taskbar (Windows & Unity) + +On Windows, a taskbar button can be used to display a progress bar. This enables +a window to provide progress information to the user without that user having to +switch to the window itself. + +The Unity DE also has a simililar feature that allows you to specify progress +bar in the lancher. + +__Progress bar in taskbar button:__ + +![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +__Progress bar in Unity launcher:__ + +![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) + +To set the progress bar for a Window, you can use the +[BrowserWindow.setProgressBar][setprogressbar] API: + +```javascript +var window = new BrowserWindow({...}); +window.setProgresssBar(0.5); +``` + +## Represented file of window (OS X) + +On OS X a window can set its represented file, so the file's icon can show in +title bar, and when users Command-Click or Control-Click on the tile a path +popup will show. + +You can also set edited state of a window so the file icon can indicate whether +the document in this window has been modified. + +__Represented file popup menu:__ + + + +To set the represented file of window, you can use the +[BrowserWindow.setRepresentedFilename][setrepresentedfilename] and +[BrowserWindow.setDocumentEdited][setdocumentedited] APIs: + +```javascript +var window = new BrowserWindow({...}); +window.setRepresentedFilename('/etc/passwd'); +window.setDocumentEdited(true); +``` + +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks +[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress +[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename +[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher