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
= base::SplitString(
309 walk
, L
";", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
311 // Metro wants suffixes only, not patterns.
312 mswrw::HString extension
;
313 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
314 if (extensions_win32_style
[i
] == L
"*.*") {
315 // The wildcard filter is "*" for Metro. The string "*.*" produces
316 // an "invalid parameter" error.
317 hr
= extension
.Set(L
"*");
319 // Metro wants suffixes only, not patterns.
321 base::FilePath(extensions_win32_style
[i
]).Extension();
322 if ((ext
.size() < 2) ||
323 (ext
.find_first_of(L
"*?") != base::string16::npos
)) {
326 hr
= extension
.Set(ext
.c_str());
329 hr
= filter
->Append(extension
.Get());
334 // Walk past the extension.
335 walk
+= wcslen(walk
) + 1;
339 // Spin up a single or multi picker as appropriate.
340 if (open_file_name_
->Flags
& OFN_ALLOWMULTISELECT
) {
341 mswr::ComPtr
<MultiFileAsyncOp
> completion
;
342 hr
= picker
->PickMultipleFilesAsync(&completion
);
346 // Create the callback method.
347 typedef winfoundtn::IAsyncOperationCompletedHandler
<
348 StorageFileVectorCollection
*> HandlerDoneType
;
349 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
350 this, &OpenFilePickerSession::MultiPickerDone
));
351 DCHECK(handler
.Get() != NULL
);
352 hr
= completion
->put_Completed(handler
.Get());
356 mswr::ComPtr
<SingleFileAsyncOp
> completion
;
357 hr
= picker
->PickSingleFileAsync(&completion
);
361 // Create the callback method.
362 typedef winfoundtn::IAsyncOperationCompletedHandler
<
363 winstorage::StorageFile
*> HandlerDoneType
;
364 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
365 this, &OpenFilePickerSession::SinglePickerDone
));
366 DCHECK(handler
.Get() != NULL
);
367 hr
= completion
->put_Completed(handler
.Get());
373 HRESULT
OpenFilePickerSession::ComposeMultiFileResult(
374 StorageFileVectorCollection
* files
, base::string16
* result
) {
375 DCHECK(files
!= NULL
);
376 DCHECK(result
!= NULL
);
378 // Empty the output string.
381 unsigned int num_files
= 0;
382 HRESULT hr
= files
->get_Size(&num_files
);
386 // Make sure we return an error on an empty collection.
387 if (num_files
== 0) {
388 DLOG(ERROR
) << "Empty collection on input.";
392 // This stores the base path that should be the parent of all the files.
393 base::FilePath base_path
;
395 // Iterate through the collection and append the file paths to the result.
396 for (unsigned int i
= 0; i
< num_files
; ++i
) {
397 mswr::ComPtr
<winstorage::IStorageFile
> file
;
398 hr
= files
->GetAt(i
, file
.GetAddressOf());
402 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
403 hr
= file
.As(&storage_item
);
407 mswrw::HString file_path_str
;
408 hr
= storage_item
->get_Path(file_path_str
.GetAddressOf());
412 base::FilePath
file_path(MakeStdWString(file_path_str
.Get()));
413 if (base_path
.empty()) {
414 DCHECK(result
->empty());
415 base_path
= file_path
.DirName();
417 // Append the path, including the terminating zero.
418 // We do this only for the first file.
419 result
->append(base_path
.value().c_str(), base_path
.value().size() + 1);
421 DCHECK(!result
->empty());
422 DCHECK(!base_path
.empty());
423 DCHECK(base_path
== file_path
.DirName());
425 // Append the base name, including the terminating zero.
426 base::FilePath base_name
= file_path
.BaseName();
427 result
->append(base_name
.value().c_str(), base_name
.value().size() + 1);
430 DCHECK(!result
->empty());
435 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME
* open_file_name
)
436 : FilePickerSessionBase(open_file_name
) {
439 HRESULT
SaveFilePickerSession::StartFilePicker() {
440 DCHECK(globals
.appview_msg_loop
->BelongsToCurrentThread());
441 DCHECK(open_file_name_
!= NULL
);
443 mswrw::HStringReference
class_name(
444 RuntimeClass_Windows_Storage_Pickers_FileSavePicker
);
446 // Create the file picker.
447 mswr::ComPtr
<winstorage::Pickers::IFileSavePicker
> picker
;
448 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
449 class_name
.Get(), picker
.GetAddressOf());
452 typedef winfoundtn::Collections::IMap
<HSTRING
, StringVectorItf
*>
454 mswr::ComPtr
<StringVectorMap
> choices
;
455 hr
= picker
->get_FileTypeChoices(choices
.GetAddressOf());
459 if (open_file_name_
->lpstrFilter
) {
460 // The filter is a concatenation of zero terminated string pairs,
461 // where each pair is {description, extension list}. The concatenation ends
462 // with a zero length string - e.g. a double zero terminator.
463 const wchar_t* walk
= open_file_name_
->lpstrFilter
;
464 while (*walk
!= L
'\0') {
465 mswrw::HString description
;
466 hr
= description
.Set(walk
);
470 // Walk past the description.
471 walk
+= wcslen(walk
) + 1;
473 // We should have an extension, but bail on malformed filters.
477 // There can be a single extension, or a list of semicolon-separated ones.
478 std::vector
<base::string16
> extensions_win32_style
= base::SplitString(
479 walk
, L
";", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
481 // Metro wants suffixes only, not patterns. Also, metro does not support
482 // the all files ("*") pattern in the save picker.
483 std::vector
<base::string16
> extensions
;
484 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
486 base::FilePath(extensions_win32_style
[i
]).Extension();
487 if ((ext
.size() < 2) ||
488 (ext
.find_first_of(L
"*?") != base::string16::npos
))
490 extensions
.push_back(ext
);
493 if (!extensions
.empty()) {
494 // Convert to a Metro collection class.
495 mswr::ComPtr
<StringVectorItf
> list
;
496 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
497 list
.GetAddressOf(), extensions
);
501 // Finally set the filter.
502 boolean replaced
= FALSE
;
503 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
506 DCHECK_EQ(FALSE
, replaced
);
509 // Walk past the extension(s).
510 walk
+= wcslen(walk
) + 1;
514 // The save picker requires at least one choice. Callers are strongly advised
515 // to provide sensible choices. If none were given, fallback to .dat.
516 uint32 num_choices
= 0;
517 hr
= choices
->get_Size(&num_choices
);
521 if (num_choices
== 0) {
522 mswrw::HString description
;
523 // TODO(grt): Get a properly translated string. This can't be done from
524 // within metro_driver. Consider preprocessing the filter list in Chrome
525 // land to ensure it has this entry if all others are patterns. In that
526 // case, this whole block of code can be removed.
527 hr
= description
.Set(L
"Data File");
531 mswr::ComPtr
<StringVectorItf
> list
;
532 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
533 list
.GetAddressOf(), std::vector
<base::string16
>(1, L
".dat"));
537 boolean replaced
= FALSE
;
538 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
541 DCHECK_EQ(FALSE
, replaced
);
544 if (open_file_name_
->lpstrFile
!= NULL
) {
545 hr
= picker
->put_SuggestedFileName(
546 mswrw::HStringReference(
547 const_cast<const wchar_t*>(open_file_name_
->lpstrFile
)).Get());
552 mswr::ComPtr
<SaveFileAsyncOp
> completion
;
553 hr
= picker
->PickSaveFileAsync(&completion
);
557 // Create the callback method.
558 typedef winfoundtn::IAsyncOperationCompletedHandler
<
559 winstorage::StorageFile
*> HandlerDoneType
;
560 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
561 this, &SaveFilePickerSession::FilePickerDone
));
562 DCHECK(handler
.Get() != NULL
);
563 hr
= completion
->put_Completed(handler
.Get());
568 HRESULT
SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp
* async
,
569 AsyncStatus status
) {
570 if (status
== Completed
) {
571 mswr::ComPtr
<winstorage::IStorageFile
> file
;
572 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
575 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
577 hr
= file
.As(&storage_item
);
579 mswrw::HString file_path
;
581 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
584 base::string16 path_str
= MakeStdWString(file_path
.Get());
586 // If the selected file name is longer than the supplied buffer,
587 // we return false as per GetOpenFileName documentation.
588 if (path_str
.size() < open_file_name_
->nMaxFile
) {
589 base::wcslcpy(open_file_name_
->lpstrFile
,
591 open_file_name_
->nMaxFile
);
596 LOG(ERROR
) << "NULL IStorageItem";
599 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
609 BOOL
MetroGetOpenFileName(OPENFILENAME
* open_file_name
) {
610 OpenFilePickerSession
session(open_file_name
);
612 return session
.Run();
615 BOOL
MetroGetSaveFileName(OPENFILENAME
* open_file_name
) {
616 SaveFilePickerSession
session(open_file_name
);
618 return session
.Run();