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. 4 5 #include "stdafx.h" 6 #include "win8/metro_driver/file_picker.h" 7 8 #include <windows.storage.pickers.h> 9 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" 20 21 namespace { 22 23 namespace winstorage = ABI::Windows::Storage; 24 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf; 25 26 // TODO(siggi): Complete this implementation and move it to a common place. 27 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> { 28 public: 29 ~StringVectorImpl() { 30 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString); 31 } 32 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])); 36 37 return S_OK; 38 } 39 40 // IVector<HSTRING> implementation. 41 STDMETHOD(GetAt)(unsigned index, HSTRING* item) { 42 if (index >= strings_.size()) 43 return E_INVALIDARG; 44 45 return ::WindowsDuplicateString(strings_[index], item); 46 } 47 STDMETHOD(get_Size)(unsigned *size) { 48 if (strings_.size() > UINT_MAX) 49 return E_UNEXPECTED; 50 *size = static_cast<unsigned>(strings_.size()); 51 return S_OK; 52 } 53 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) { 54 return E_NOTIMPL; 55 } 56 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) { 57 return E_NOTIMPL; 58 } 59 60 // write methods 61 STDMETHOD(SetAt)(unsigned index, HSTRING item) { 62 return E_NOTIMPL; 63 } 64 STDMETHOD(InsertAt)(unsigned index, HSTRING item) { 65 return E_NOTIMPL; 66 } 67 STDMETHOD(RemoveAt)(unsigned index) { 68 return E_NOTIMPL; 69 } 70 STDMETHOD(Append)(HSTRING item) { 71 return E_NOTIMPL; 72 } 73 STDMETHOD(RemoveAtEnd)() { 74 return E_NOTIMPL; 75 } 76 STDMETHOD(Clear)() { 77 return E_NOTIMPL; 78 } 79 80 private: 81 std::vector<HSTRING> strings_; 82 }; 83 84 class FilePickerSessionBase { 85 public: 86 // Creates a file picker for open_file_name. 87 explicit FilePickerSessionBase(OPENFILENAME* open_file_name); 88 89 // Runs the picker, returns true on success. 90 bool Run(); 91 92 protected: 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; 97 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. 103 bool success_; 104 105 private: 106 // Initiate a file picker, must be called on the metro dispatcher's thread. 107 void DoFilePicker(); 108 109 DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase); 110 }; 111 112 class OpenFilePickerSession : public FilePickerSessionBase { 113 public: 114 explicit OpenFilePickerSession(OPENFILENAME* open_file_name); 115 116 private: 117 virtual HRESULT StartFilePicker() OVERRIDE; 118 119 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> 120 SingleFileAsyncOp; 121 typedef winfoundtn::Collections::IVectorView< 122 winstorage::StorageFile*> StorageFileVectorCollection; 123 typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*> 124 MultiFileAsyncOp; 125 126 // Called asynchronously when a single file picker is done. 127 HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status); 128 129 // Called asynchronously when a multi file picker is done. 130 HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status); 131 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); 136 private: 137 DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession); 138 }; 139 140 class SaveFilePickerSession : public FilePickerSessionBase { 141 public: 142 explicit SaveFilePickerSession(OPENFILENAME* open_file_name); 143 144 private: 145 virtual HRESULT StartFilePicker() OVERRIDE; 146 147 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*> 148 SaveFileAsyncOp; 149 150 // Called asynchronously when the save file picker is done. 151 HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status); 152 }; 153 154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name) 155 : open_file_name_(open_file_name), 156 event_(true, false), 157 success_(false) { 158 } 159 160 bool FilePickerSessionBase::Run() { 161 DCHECK(globals.appview_msg_loop != NULL); 162 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))); 166 if (!posted) 167 return false; 168 169 // Wait for the file picker to complete. 170 event_.Wait(); 171 172 return success_; 173 } 174 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(); 179 if (FAILED(hr)) { 180 LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr; 181 } 182 183 if (SUCCEEDED(hr)) 184 hr = StartFilePicker(); 185 186 if (FAILED(hr)) { 187 LOG(ERROR) << "Failed to start file picker, error 0x" 188 << std::hex << hr; 189 190 event_.Signal(); 191 } 192 } 193 194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name) 195 : FilePickerSessionBase(open_file_name) { 196 } 197 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()); 203 204 if (file) { 205 mswr::ComPtr<winstorage::IStorageItem> storage_item; 206 if (SUCCEEDED(hr)) 207 hr = file.As(&storage_item); 208 209 mswrw::HString file_path; 210 if (SUCCEEDED(hr)) 211 hr = storage_item->get_Path(file_path.GetAddressOf()); 212 213 if (SUCCEEDED(hr)) { 214 UINT32 path_len = 0; 215 const wchar_t* path_str = 216 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len); 217 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, 222 path_str, 223 open_file_name_->nMaxFile); 224 success_ = true; 225 } 226 } 227 } else { 228 LOG(ERROR) << "NULL IStorageItem"; 229 } 230 } else { 231 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); 232 } 233 234 event_.Signal(); 235 236 return S_OK; 237 } 238 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()); 244 245 if (files) { 246 base::string16 result; 247 if (SUCCEEDED(hr)) 248 hr = ComposeMultiFileResult(files.Get(), &result); 249 250 if (SUCCEEDED(hr)) { 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, 254 result.c_str(), 255 (result.size() + 1) * sizeof(result[0])); 256 success_ = true; 257 } 258 } 259 } else { 260 LOG(ERROR) << "NULL StorageFileVectorCollection"; 261 } 262 } else { 263 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); 264 } 265 266 event_.Signal(); 267 268 return S_OK; 269 } 270 271 HRESULT OpenFilePickerSession::StartFilePicker() { 272 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); 273 DCHECK(open_file_name_ != NULL); 274 275 mswrw::HStringReference class_name( 276 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker); 277 278 // Create the file picker. 279 mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker; 280 HRESULT hr = ::Windows::Foundation::ActivateInstance( 281 class_name.Get(), picker.GetAddressOf()); 282 CheckHR(hr); 283 284 // Set the file type filter 285 mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter; 286 hr = picker->get_FileTypeFilter(filter.GetAddressOf()); 287 if (FAILED(hr)) 288 return hr; 289 290 if (open_file_name_->lpstrFilter == NULL) { 291 hr = filter->Append(mswrw::HStringReference(L"*").Get()); 292 if (FAILED(hr)) 293 return hr; 294 } else { 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; 302 303 // We should have an extension, but bail on malformed filters. 304 if (*walk == L'\0') 305 break; 306 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()); 311 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"*"); 319 } else { 320 // Metro wants suffixes only, not patterns. 321 base::string16 ext = 322 base::FilePath(extensions_win32_style[i]).Extension(); 323 if ((ext.size() < 2) || 324 (ext.find_first_of(L"*?") != base::string16::npos)) { 325 continue; 326 } 327 hr = extension.Set(ext.c_str()); 328 } 329 if (SUCCEEDED(hr)) 330 hr = filter->Append(extension.Get()); 331 if (FAILED(hr)) 332 return hr; 333 } 334 335 // Walk past the extension. 336 walk += wcslen(walk) + 1; 337 } 338 } 339 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); 344 if (FAILED(hr)) 345 return hr; 346 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()); 354 355 return hr; 356 } else { 357 mswr::ComPtr<SingleFileAsyncOp> completion; 358 hr = picker->PickSingleFileAsync(&completion); 359 if (FAILED(hr)) 360 return hr; 361 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()); 369 370 return hr; 371 } 372 } 373 374 HRESULT OpenFilePickerSession::ComposeMultiFileResult( 375 StorageFileVectorCollection* files, base::string16* result) { 376 DCHECK(files != NULL); 377 DCHECK(result != NULL); 378 379 // Empty the output string. 380 result->clear(); 381 382 unsigned int num_files = 0; 383 HRESULT hr = files->get_Size(&num_files); 384 if (FAILED(hr)) 385 return hr; 386 387 // Make sure we return an error on an empty collection. 388 if (num_files == 0) { 389 DLOG(ERROR) << "Empty collection on input."; 390 return E_UNEXPECTED; 391 } 392 393 // This stores the base path that should be the parent of all the files. 394 base::FilePath base_path; 395 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()); 400 if (FAILED(hr)) 401 return hr; 402 403 mswr::ComPtr<winstorage::IStorageItem> storage_item; 404 hr = file.As(&storage_item); 405 if (FAILED(hr)) 406 return hr; 407 408 mswrw::HString file_path_str; 409 hr = storage_item->get_Path(file_path_str.GetAddressOf()); 410 if (FAILED(hr)) 411 return hr; 412 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(); 417 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); 421 } 422 DCHECK(!result->empty()); 423 DCHECK(!base_path.empty()); 424 DCHECK(base_path == file_path.DirName()); 425 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); 429 } 430 431 DCHECK(!result->empty()); 432 433 return S_OK; 434 } 435 436 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name) 437 : FilePickerSessionBase(open_file_name) { 438 } 439 440 HRESULT SaveFilePickerSession::StartFilePicker() { 441 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); 442 DCHECK(open_file_name_ != NULL); 443 444 mswrw::HStringReference class_name( 445 RuntimeClass_Windows_Storage_Pickers_FileSavePicker); 446 447 // Create the file picker. 448 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker; 449 HRESULT hr = ::Windows::Foundation::ActivateInstance( 450 class_name.Get(), picker.GetAddressOf()); 451 CheckHR(hr); 452 453 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*> 454 StringVectorMap; 455 mswr::ComPtr<StringVectorMap> choices; 456 hr = picker->get_FileTypeChoices(choices.GetAddressOf()); 457 if (FAILED(hr)) 458 return hr; 459 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); 468 if (FAILED(hr)) 469 return hr; 470 471 // Walk past the description. 472 walk += wcslen(walk) + 1; 473 474 // We should have an extension, but bail on malformed filters. 475 if (*walk == L'\0') 476 break; 477 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()); 482 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) { 487 base::string16 ext = 488 base::FilePath(extensions_win32_style[i]).Extension(); 489 if ((ext.size() < 2) || 490 (ext.find_first_of(L"*?") != base::string16::npos)) 491 continue; 492 extensions.push_back(ext); 493 } 494 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); 500 if (FAILED(hr)) 501 return hr; 502 503 // Finally set the filter. 504 boolean replaced = FALSE; 505 hr = choices->Insert(description.Get(), list.Get(), &replaced); 506 if (FAILED(hr)) 507 return hr; 508 DCHECK_EQ(FALSE, replaced); 509 } 510 511 // Walk past the extension(s). 512 walk += wcslen(walk) + 1; 513 } 514 } 515 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); 520 if (FAILED(hr)) 521 return hr; 522 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"); 530 if (FAILED(hr)) 531 return hr; 532 533 mswr::ComPtr<StringVectorItf> list; 534 hr = mswr::MakeAndInitialize<StringVectorImpl>( 535 list.GetAddressOf(), std::vector<base::string16>(1, L".dat")); 536 if (FAILED(hr)) 537 return hr; 538 539 boolean replaced = FALSE; 540 hr = choices->Insert(description.Get(), list.Get(), &replaced); 541 if (FAILED(hr)) 542 return hr; 543 DCHECK_EQ(FALSE, replaced); 544 } 545 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()); 550 if (FAILED(hr)) 551 return hr; 552 } 553 554 mswr::ComPtr<SaveFileAsyncOp> completion; 555 hr = picker->PickSaveFileAsync(&completion); 556 if (FAILED(hr)) 557 return hr; 558 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()); 566 567 return hr; 568 } 569 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()); 575 576 if (file) { 577 mswr::ComPtr<winstorage::IStorageItem> storage_item; 578 if (SUCCEEDED(hr)) 579 hr = file.As(&storage_item); 580 581 mswrw::HString file_path; 582 if (SUCCEEDED(hr)) 583 hr = storage_item->get_Path(file_path.GetAddressOf()); 584 585 if (SUCCEEDED(hr)) { 586 base::string16 path_str = MakeStdWString(file_path.Get()); 587 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, 592 path_str.c_str(), 593 open_file_name_->nMaxFile); 594 success_ = true; 595 } 596 } 597 } else { 598 LOG(ERROR) << "NULL IStorageItem"; 599 } 600 } else { 601 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); 602 } 603 604 event_.Signal(); 605 606 return S_OK; 607 } 608 609 } // namespace 610 611 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) { 612 OpenFilePickerSession session(open_file_name); 613 614 return session.Run(); 615 } 616 617 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) { 618 SaveFilePickerSession session(open_file_name); 619 620 return session.Run(); 621 } 622