1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "win8/metro_driver/file_picker.h"
8 #include <windows.storage.pickers.h>
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_util.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/win/metro.h"
17 #include "base/win/scoped_comptr.h"
18 #include "win8/metro_driver/chrome_app_view.h"
19 #include "win8/metro_driver/winrt_utils.h"
23 namespace winstorage
= ABI::Windows::Storage
;
24 typedef winfoundtn::Collections::IVector
<HSTRING
> StringVectorItf
;
26 // TODO(siggi): Complete this implementation and move it to a common place.
27 class StringVectorImpl
: public mswr::RuntimeClass
<StringVectorItf
> {
30 std::for_each(strings_
.begin(), strings_
.end(), ::WindowsDeleteString
);
33 HRESULT
RuntimeClassInitialize(const std::vector
<base::string16
>& list
) {
34 for (size_t i
= 0; i
< list
.size(); ++i
)
35 strings_
.push_back(MakeHString(list
[i
]));
40 // IVector<HSTRING> implementation.
41 STDMETHOD(GetAt
)(unsigned index
, HSTRING
* item
) {
42 if (index
>= strings_
.size())
45 return ::WindowsDuplicateString(strings_
[index
], item
);
47 STDMETHOD(get_Size
)(unsigned *size
) {
48 if (strings_
.size() > UINT_MAX
)
50 *size
= static_cast<unsigned>(strings_
.size());
53 STDMETHOD(GetView
)(winfoundtn::Collections::IVectorView
<HSTRING
> **view
) {
56 STDMETHOD(IndexOf
)(HSTRING value
, unsigned *index
, boolean
*found
) {
61 STDMETHOD(SetAt
)(unsigned index
, HSTRING item
) {
64 STDMETHOD(InsertAt
)(unsigned index
, HSTRING item
) {
67 STDMETHOD(RemoveAt
)(unsigned index
) {
70 STDMETHOD(Append
)(HSTRING item
) {
73 STDMETHOD(RemoveAtEnd
)() {
81 std::vector
<HSTRING
> strings_
;
84 class FilePickerSessionBase
{
86 // Creates a file picker for open_file_name.
87 explicit FilePickerSessionBase(OPENFILENAME
* open_file_name
);
89 // Runs the picker, returns true on success.
93 // Creates, configures and starts a file picker.
94 // If the HRESULT returned is a failure code the file picker has not started,
95 // so no callbacks should be expected.
96 virtual HRESULT
StartFilePicker() = 0;
98 // The parameters to our picker.
99 OPENFILENAME
* open_file_name_
;
100 // The event Run waits on.
101 base::WaitableEvent event_
;
102 // True iff a file picker has successfully finished.
106 // Initiate a file picker, must be called on the metro dispatcher's thread.
109 DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase
);
112 class OpenFilePickerSession
: public FilePickerSessionBase
{
114 explicit OpenFilePickerSession(OPENFILENAME
* open_file_name
);
117 HRESULT
StartFilePicker() override
;
119 typedef winfoundtn::IAsyncOperation
<winstorage::StorageFile
*>
121 typedef winfoundtn::Collections::IVectorView
<
122 winstorage::StorageFile
*> StorageFileVectorCollection
;
123 typedef winfoundtn::IAsyncOperation
<StorageFileVectorCollection
*>
126 // Called asynchronously when a single file picker is done.
127 HRESULT
SinglePickerDone(SingleFileAsyncOp
* async
, AsyncStatus status
);
129 // Called asynchronously when a multi file picker is done.
130 HRESULT
MultiPickerDone(MultiFileAsyncOp
* async
, AsyncStatus status
);
132 // Composes a multi-file result string suitable for returning to a
133 // from a storage file collection.
134 static HRESULT
ComposeMultiFileResult(StorageFileVectorCollection
* files
,
135 base::string16
* result
);
137 DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession
);
140 class SaveFilePickerSession
: public FilePickerSessionBase
{
142 explicit SaveFilePickerSession(OPENFILENAME
* open_file_name
);
145 HRESULT
StartFilePicker() override
;
147 typedef winfoundtn::IAsyncOperation
<winstorage::StorageFile
*>
150 // Called asynchronously when the save file picker is done.
151 HRESULT
FilePickerDone(SaveFileAsyncOp
* async
, AsyncStatus status
);
154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME
* open_file_name
)
155 : open_file_name_(open_file_name
),
160 bool FilePickerSessionBase::Run() {
161 DCHECK(globals
.appview_msg_loop
!= NULL
);
163 // Post the picker request over to the metro thread.
164 bool posted
= globals
.appview_msg_loop
->PostTask(FROM_HERE
,
165 base::Bind(&FilePickerSessionBase::DoFilePicker
, base::Unretained(this)));
169 // Wait for the file picker to complete.
175 void FilePickerSessionBase::DoFilePicker() {
176 // The file picker will fail if spawned from a snapped application,
177 // so let's attempt to unsnap first if we're in that state.
178 HRESULT hr
= ChromeAppView::Unsnap();
180 LOG(ERROR
) << "Failed to unsnap for file picker, error 0x" << hr
;
184 hr
= StartFilePicker();
187 LOG(ERROR
) << "Failed to start file picker, error 0x"
194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME
* open_file_name
)
195 : FilePickerSessionBase(open_file_name
) {
198 HRESULT
OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp
* async
,
199 AsyncStatus status
) {
200 if (status
== Completed
) {
201 mswr::ComPtr
<winstorage::IStorageFile
> file
;
202 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
205 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
207 hr
= file
.As(&storage_item
);
209 mswrw::HString file_path
;
211 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
215 const wchar_t* path_str
=
216 ::WindowsGetStringRawBuffer(file_path
.Get(), &path_len
);
218 // If the selected file name is longer than the supplied buffer,
219 // we return false as per GetOpenFileName documentation.
220 if (path_len
< open_file_name_
->nMaxFile
) {
221 base::wcslcpy(open_file_name_
->lpstrFile
,
223 open_file_name_
->nMaxFile
);
228 LOG(ERROR
) << "NULL IStorageItem";
231 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
239 HRESULT
OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp
* async
,
240 AsyncStatus status
) {
241 if (status
== Completed
) {
242 mswr::ComPtr
<StorageFileVectorCollection
> files
;
243 HRESULT hr
= async
->GetResults(files
.GetAddressOf());
246 base::string16 result
;
248 hr
= ComposeMultiFileResult(files
.Get(), &result
);
251 if (result
.size() + 1 < open_file_name_
->nMaxFile
) {
252 // Because the result has embedded nulls, we must memcpy.
253 memcpy(open_file_name_
->lpstrFile
,
255 (result
.size() + 1) * sizeof(result
[0]));
260 LOG(ERROR
) << "NULL StorageFileVectorCollection";
263 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
271 HRESULT
OpenFilePickerSession::StartFilePicker() {
272 DCHECK(globals
.appview_msg_loop
->BelongsToCurrentThread());
273 DCHECK(open_file_name_
!= NULL
);
275 mswrw::HStringReference
class_name(
276 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker
);
278 // Create the file picker.
279 mswr::ComPtr
<winstorage::Pickers::IFileOpenPicker
> picker
;
280 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
281 class_name
.Get(), picker
.GetAddressOf());
284 // Set the file type filter
285 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
286 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
290 if (open_file_name_
->lpstrFilter
== NULL
) {
291 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
295 // The filter is a concatenation of zero terminated string pairs,
296 // where each pair is {description, extension}. The concatenation ends
297 // with a zero length string - e.g. a double zero terminator.
298 const wchar_t* walk
= open_file_name_
->lpstrFilter
;
299 while (*walk
!= L
'\0') {
300 // Walk past the description.
301 walk
+= wcslen(walk
) + 1;
303 // We should have an extension, but bail on malformed filters.
307 // There can be a single extension, or a list of semicolon-separated ones.
308 std::vector
<base::string16
> extensions_win32_style
;
309 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
310 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
312 // Metro wants suffixes only, not patterns.
313 mswrw::HString extension
;
314 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
315 if (extensions_win32_style
[i
] == L
"*.*") {
316 // The wildcard filter is "*" for Metro. The string "*.*" produces
317 // an "invalid parameter" error.
318 hr
= extension
.Set(L
"*");
320 // Metro wants suffixes only, not patterns.
322 base::FilePath(extensions_win32_style
[i
]).Extension();
323 if ((ext
.size() < 2) ||
324 (ext
.find_first_of(L
"*?") != base::string16::npos
)) {
327 hr
= extension
.Set(ext
.c_str());
330 hr
= filter
->Append(extension
.Get());
335 // Walk past the extension.
336 walk
+= wcslen(walk
) + 1;
340 // Spin up a single or multi picker as appropriate.
341 if (open_file_name_
->Flags
& OFN_ALLOWMULTISELECT
) {
342 mswr::ComPtr
<MultiFileAsyncOp
> completion
;
343 hr
= picker
->PickMultipleFilesAsync(&completion
);
347 // Create the callback method.
348 typedef winfoundtn::IAsyncOperationCompletedHandler
<
349 StorageFileVectorCollection
*> HandlerDoneType
;
350 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
351 this, &OpenFilePickerSession::MultiPickerDone
));
352 DCHECK(handler
.Get() != NULL
);
353 hr
= completion
->put_Completed(handler
.Get());
357 mswr::ComPtr
<SingleFileAsyncOp
> completion
;
358 hr
= picker
->PickSingleFileAsync(&completion
);
362 // Create the callback method.
363 typedef winfoundtn::IAsyncOperationCompletedHandler
<
364 winstorage::StorageFile
*> HandlerDoneType
;
365 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
366 this, &OpenFilePickerSession::SinglePickerDone
));
367 DCHECK(handler
.Get() != NULL
);
368 hr
= completion
->put_Completed(handler
.Get());
374 HRESULT
OpenFilePickerSession::ComposeMultiFileResult(
375 StorageFileVectorCollection
* files
, base::string16
* result
) {
376 DCHECK(files
!= NULL
);
377 DCHECK(result
!= NULL
);
379 // Empty the output string.
382 unsigned int num_files
= 0;
383 HRESULT hr
= files
->get_Size(&num_files
);
387 // Make sure we return an error on an empty collection.
388 if (num_files
== 0) {
389 DLOG(ERROR
) << "Empty collection on input.";
393 // This stores the base path that should be the parent of all the files.
394 base::FilePath base_path
;
396 // Iterate through the collection and append the file paths to the result.
397 for (unsigned int i
= 0; i
< num_files
; ++i
) {
398 mswr::ComPtr
<winstorage::IStorageFile
> file
;
399 hr
= files
->GetAt(i
, file
.GetAddressOf());
403 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
404 hr
= file
.As(&storage_item
);
408 mswrw::HString file_path_str
;
409 hr
= storage_item
->get_Path(file_path_str
.GetAddressOf());
413 base::FilePath
file_path(MakeStdWString(file_path_str
.Get()));
414 if (base_path
.empty()) {
415 DCHECK(result
->empty());
416 base_path
= file_path
.DirName();
418 // Append the path, including the terminating zero.
419 // We do this only for the first file.
420 result
->append(base_path
.value().c_str(), base_path
.value().size() + 1);
422 DCHECK(!result
->empty());
423 DCHECK(!base_path
.empty());
424 DCHECK(base_path
== file_path
.DirName());
426 // Append the base name, including the terminating zero.
427 base::FilePath base_name
= file_path
.BaseName();
428 result
->append(base_name
.value().c_str(), base_name
.value().size() + 1);
431 DCHECK(!result
->empty());
436 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME
* open_file_name
)
437 : FilePickerSessionBase(open_file_name
) {
440 HRESULT
SaveFilePickerSession::StartFilePicker() {
441 DCHECK(globals
.appview_msg_loop
->BelongsToCurrentThread());
442 DCHECK(open_file_name_
!= NULL
);
444 mswrw::HStringReference
class_name(
445 RuntimeClass_Windows_Storage_Pickers_FileSavePicker
);
447 // Create the file picker.
448 mswr::ComPtr
<winstorage::Pickers::IFileSavePicker
> picker
;
449 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
450 class_name
.Get(), picker
.GetAddressOf());
453 typedef winfoundtn::Collections::IMap
<HSTRING
, StringVectorItf
*>
455 mswr::ComPtr
<StringVectorMap
> choices
;
456 hr
= picker
->get_FileTypeChoices(choices
.GetAddressOf());
460 if (open_file_name_
->lpstrFilter
) {
461 // The filter is a concatenation of zero terminated string pairs,
462 // where each pair is {description, extension list}. The concatenation ends
463 // with a zero length string - e.g. a double zero terminator.
464 const wchar_t* walk
= open_file_name_
->lpstrFilter
;
465 while (*walk
!= L
'\0') {
466 mswrw::HString description
;
467 hr
= description
.Set(walk
);
471 // Walk past the description.
472 walk
+= wcslen(walk
) + 1;
474 // We should have an extension, but bail on malformed filters.
478 // There can be a single extension, or a list of semicolon-separated ones.
479 std::vector
<base::string16
> extensions_win32_style
;
480 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
481 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
483 // Metro wants suffixes only, not patterns. Also, metro does not support
484 // the all files ("*") pattern in the save picker.
485 std::vector
<base::string16
> extensions
;
486 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
488 base::FilePath(extensions_win32_style
[i
]).Extension();
489 if ((ext
.size() < 2) ||
490 (ext
.find_first_of(L
"*?") != base::string16::npos
))
492 extensions
.push_back(ext
);
495 if (!extensions
.empty()) {
496 // Convert to a Metro collection class.
497 mswr::ComPtr
<StringVectorItf
> list
;
498 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
499 list
.GetAddressOf(), extensions
);
503 // Finally set the filter.
504 boolean replaced
= FALSE
;
505 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
508 DCHECK_EQ(FALSE
, replaced
);
511 // Walk past the extension(s).
512 walk
+= wcslen(walk
) + 1;
516 // The save picker requires at least one choice. Callers are strongly advised
517 // to provide sensible choices. If none were given, fallback to .dat.
518 uint32 num_choices
= 0;
519 hr
= choices
->get_Size(&num_choices
);
523 if (num_choices
== 0) {
524 mswrw::HString description
;
525 // TODO(grt): Get a properly translated string. This can't be done from
526 // within metro_driver. Consider preprocessing the filter list in Chrome
527 // land to ensure it has this entry if all others are patterns. In that
528 // case, this whole block of code can be removed.
529 hr
= description
.Set(L
"Data File");
533 mswr::ComPtr
<StringVectorItf
> list
;
534 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
535 list
.GetAddressOf(), std::vector
<base::string16
>(1, L
".dat"));
539 boolean replaced
= FALSE
;
540 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
543 DCHECK_EQ(FALSE
, replaced
);
546 if (open_file_name_
->lpstrFile
!= NULL
) {
547 hr
= picker
->put_SuggestedFileName(
548 mswrw::HStringReference(
549 const_cast<const wchar_t*>(open_file_name_
->lpstrFile
)).Get());
554 mswr::ComPtr
<SaveFileAsyncOp
> completion
;
555 hr
= picker
->PickSaveFileAsync(&completion
);
559 // Create the callback method.
560 typedef winfoundtn::IAsyncOperationCompletedHandler
<
561 winstorage::StorageFile
*> HandlerDoneType
;
562 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
563 this, &SaveFilePickerSession::FilePickerDone
));
564 DCHECK(handler
.Get() != NULL
);
565 hr
= completion
->put_Completed(handler
.Get());
570 HRESULT
SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp
* async
,
571 AsyncStatus status
) {
572 if (status
== Completed
) {
573 mswr::ComPtr
<winstorage::IStorageFile
> file
;
574 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
577 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
579 hr
= file
.As(&storage_item
);
581 mswrw::HString file_path
;
583 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
586 base::string16 path_str
= MakeStdWString(file_path
.Get());
588 // If the selected file name is longer than the supplied buffer,
589 // we return false as per GetOpenFileName documentation.
590 if (path_str
.size() < open_file_name_
->nMaxFile
) {
591 base::wcslcpy(open_file_name_
->lpstrFile
,
593 open_file_name_
->nMaxFile
);
598 LOG(ERROR
) << "NULL IStorageItem";
601 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
611 BOOL
MetroGetOpenFileName(OPENFILENAME
* open_file_name
) {
612 OpenFilePickerSession
session(open_file_name
);
614 return session
.Run();
617 BOOL
MetroGetSaveFileName(OPENFILENAME
* open_file_name
) {
618 SaveFilePickerSession
session(open_file_name
);
620 return session
.Run();