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<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 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 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<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 string16 ext = base::FilePath(extensions_win32_style[i]).Extension(); 322 if ((ext.size() < 2) || 323 (ext.find_first_of(L"*?") != string16::npos)) { 324 continue; 325 } 326 hr = extension.Set(ext.c_str()); 327 } 328 if (SUCCEEDED(hr)) 329 hr = filter->Append(extension.Get()); 330 if (FAILED(hr)) 331 return hr; 332 } 333 334 // Walk past the extension. 335 walk += wcslen(walk) + 1; 336 } 337 } 338 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); 343 if (FAILED(hr)) 344 return hr; 345 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()); 353 354 return hr; 355 } else { 356 mswr::ComPtr<SingleFileAsyncOp> completion; 357 hr = picker->PickSingleFileAsync(&completion); 358 if (FAILED(hr)) 359 return hr; 360 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()); 368 369 return hr; 370 } 371 } 372 373 HRESULT OpenFilePickerSession::ComposeMultiFileResult( 374 StorageFileVectorCollection* files, string16* result) { 375 DCHECK(files != NULL); 376 DCHECK(result != NULL); 377 378 // Empty the output string. 379 result->clear(); 380 381 unsigned int num_files = 0; 382 HRESULT hr = files->get_Size(&num_files); 383 if (FAILED(hr)) 384 return hr; 385 386 // Make sure we return an error on an empty collection. 387 if (num_files == 0) { 388 DLOG(ERROR) << "Empty collection on input."; 389 return E_UNEXPECTED; 390 } 391 392 // This stores the base path that should be the parent of all the files. 393 base::FilePath base_path; 394 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()); 399 if (FAILED(hr)) 400 return hr; 401 402 mswr::ComPtr<winstorage::IStorageItem> storage_item; 403 hr = file.As(&storage_item); 404 if (FAILED(hr)) 405 return hr; 406 407 mswrw::HString file_path_str; 408 hr = storage_item->get_Path(file_path_str.GetAddressOf()); 409 if (FAILED(hr)) 410 return hr; 411 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(); 416 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); 420 } 421 DCHECK(!result->empty()); 422 DCHECK(!base_path.empty()); 423 DCHECK(base_path == file_path.DirName()); 424 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); 428 } 429 430 DCHECK(!result->empty()); 431 432 return S_OK; 433 } 434 435 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name) 436 : FilePickerSessionBase(open_file_name) { 437 } 438 439 HRESULT SaveFilePickerSession::StartFilePicker() { 440 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread()); 441 DCHECK(open_file_name_ != NULL); 442 443 mswrw::HStringReference class_name( 444 RuntimeClass_Windows_Storage_Pickers_FileSavePicker); 445 446 // Create the file picker. 447 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker; 448 HRESULT hr = ::Windows::Foundation::ActivateInstance( 449 class_name.Get(), picker.GetAddressOf()); 450 CheckHR(hr); 451 452 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*> 453 StringVectorMap; 454 mswr::ComPtr<StringVectorMap> choices; 455 hr = picker->get_FileTypeChoices(choices.GetAddressOf()); 456 if (FAILED(hr)) 457 return hr; 458 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); 467 if (FAILED(hr)) 468 return hr; 469 470 // Walk past the description. 471 walk += wcslen(walk) + 1; 472 473 // We should have an extension, but bail on malformed filters. 474 if (*walk == L'\0') 475 break; 476 477 // There can be a single extension, or a list of semicolon-separated ones. 478 std::vector<string16> extensions_win32_style; 479 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style); 480 DCHECK_EQ(extension_count, extensions_win32_style.size()); 481 482 // Metro wants suffixes only, not patterns. Also, metro does not support 483 // the all files ("*") pattern in the save picker. 484 std::vector<string16> extensions; 485 for (size_t i = 0; i < extensions_win32_style.size(); ++i) { 486 string16 ext = base::FilePath(extensions_win32_style[i]).Extension(); 487 if ((ext.size() < 2) || 488 (ext.find_first_of(L"*?") != string16::npos)) 489 continue; 490 extensions.push_back(ext); 491 } 492 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); 498 if (FAILED(hr)) 499 return hr; 500 501 // Finally set the filter. 502 boolean replaced = FALSE; 503 hr = choices->Insert(description.Get(), list.Get(), &replaced); 504 if (FAILED(hr)) 505 return hr; 506 DCHECK_EQ(FALSE, replaced); 507 } 508 509 // Walk past the extension(s). 510 walk += wcslen(walk) + 1; 511 } 512 } 513 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); 518 if (FAILED(hr)) 519 return hr; 520 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"); 528 if (FAILED(hr)) 529 return hr; 530 531 mswr::ComPtr<StringVectorItf> list; 532 hr = mswr::MakeAndInitialize<StringVectorImpl>( 533 list.GetAddressOf(), std::vector<string16>(1, L".dat")); 534 if (FAILED(hr)) 535 return hr; 536 537 boolean replaced = FALSE; 538 hr = choices->Insert(description.Get(), list.Get(), &replaced); 539 if (FAILED(hr)) 540 return hr; 541 DCHECK_EQ(FALSE, replaced); 542 } 543 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()); 548 if (FAILED(hr)) 549 return hr; 550 } 551 552 mswr::ComPtr<SaveFileAsyncOp> completion; 553 hr = picker->PickSaveFileAsync(&completion); 554 if (FAILED(hr)) 555 return hr; 556 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()); 564 565 return hr; 566 } 567 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()); 573 574 if (file) { 575 mswr::ComPtr<winstorage::IStorageItem> storage_item; 576 if (SUCCEEDED(hr)) 577 hr = file.As(&storage_item); 578 579 mswrw::HString file_path; 580 if (SUCCEEDED(hr)) 581 hr = storage_item->get_Path(file_path.GetAddressOf()); 582 583 if (SUCCEEDED(hr)) { 584 string16 path_str = MakeStdWString(file_path.Get()); 585 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, 590 path_str.c_str(), 591 open_file_name_->nMaxFile); 592 success_ = true; 593 } 594 } 595 } else { 596 LOG(ERROR) << "NULL IStorageItem"; 597 } 598 } else { 599 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status); 600 } 601 602 event_.Signal(); 603 604 return S_OK; 605 } 606 607 } // namespace 608 609 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) { 610 OpenFilePickerSession session(open_file_name); 611 612 return session.Run(); 613 } 614 615 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) { 616 SaveFilePickerSession session(open_file_name); 617 618 return session.Run(); 619 } 620