blob: 45f7389ec6a7413361e3f6883e07ea34f23115ba [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/file_transfer/file_chooser.h"
#import <Cocoa/Cocoa.h>
#include <utility>
#include "base/bind.h"
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_nsobject.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequence_bound.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "remoting/base/string_resources.h"
#include "ui/base/l10n/l10n_util_mac.h"
@interface FileTransferOpenPanelDelegate : NSObject <NSOpenSavePanelDelegate> {
}
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL*)url;
- (BOOL)panel:(id)sender validateURL:(NSURL*)url error:(NSError**)outError;
@end
@implementation FileTransferOpenPanelDelegate
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL*)url {
return [url isFileURL];
}
- (BOOL)panel:(id)sender validateURL:(NSURL*)url error:(NSError**)outError {
// Refuse to accept users closing the dialog with a key repeat, since the key
// may have been first pressed while the user was looking at something else.
if ([[NSApp currentEvent] type] == NSKeyDown &&
[[NSApp currentEvent] isARepeat]) {
return NO;
}
return YES;
}
@end
namespace remoting {
namespace {
class FileChooserMac;
class MacFileChooserOnUiThread {
public:
MacFileChooserOnUiThread(
scoped_refptr<base::SequencedTaskRunner> caller_task_runner,
base::WeakPtr<FileChooserMac> file_chooser_mac);
~MacFileChooserOnUiThread();
void Show();
private:
void RunCallback(FileChooser::Result result);
base::scoped_nsobject<FileTransferOpenPanelDelegate> delegate_;
base::scoped_nsobject<NSOpenPanel> open_panel_;
scoped_refptr<base::SequencedTaskRunner> caller_task_runner_;
base::WeakPtr<FileChooserMac> file_chooser_mac_;
DISALLOW_COPY_AND_ASSIGN(MacFileChooserOnUiThread);
};
class FileChooserMac : public FileChooser {
public:
FileChooserMac(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
ResultCallback callback);
~FileChooserMac() override;
// FileChooser implementation.
void Show() override;
void RunCallback(FileChooser::Result result);
private:
FileChooser::ResultCallback callback_;
base::SequenceBound<MacFileChooserOnUiThread> mac_file_chooser_on_ui_thread_;
base::WeakPtrFactory<FileChooserMac> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(FileChooserMac);
};
MacFileChooserOnUiThread::MacFileChooserOnUiThread(
scoped_refptr<base::SequencedTaskRunner> caller_task_runner,
base::WeakPtr<FileChooserMac> file_chooser_mac)
: delegate_([FileTransferOpenPanelDelegate new]),
caller_task_runner_(std::move(caller_task_runner)),
file_chooser_mac_(std::move(file_chooser_mac)) {}
MacFileChooserOnUiThread::~MacFileChooserOnUiThread() {
if (open_panel_) {
// Will synchronously invoke completion handler.
[open_panel_ cancel:open_panel_];
}
}
void MacFileChooserOnUiThread::Show() {
DCHECK(!open_panel_);
open_panel_.reset([NSOpenPanel openPanel], base::scoped_policy::RETAIN);
[open_panel_
setMessage:l10n_util::GetNSString(IDS_DOWNLOAD_FILE_DIALOG_TITLE)];
[open_panel_ setAllowsMultipleSelection:NO];
[open_panel_ setCanChooseFiles:YES];
[open_panel_ setCanChooseDirectories:NO];
[open_panel_ setDelegate:delegate_];
[open_panel_ beginWithCompletionHandler:^(NSModalResponse result) {
if (result == NSFileHandlingPanelOKButton) {
NSURL* url = [[open_panel_ URLs] objectAtIndex:0];
if (![url isFileURL]) {
// Delegate should prevent this.
RunCallback(protocol::MakeFileTransferError(
FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
}
RunCallback(base::mac::NSStringToFilePath([url path]));
} else {
RunCallback(protocol::MakeFileTransferError(
FROM_HERE, protocol::FileTransfer_Error_Type_CANCELED));
}
open_panel_.reset();
}];
// Bring to front.
[NSApp activateIgnoringOtherApps:YES];
}
void MacFileChooserOnUiThread::RunCallback(FileChooser::Result result) {
caller_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileChooserMac::RunCallback, file_chooser_mac_,
std::move(result)));
}
FileChooserMac::FileChooserMac(
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
ResultCallback callback)
: callback_(std::move(callback)), weak_ptr_factory_(this) {
mac_file_chooser_on_ui_thread_ =
base::SequenceBound<MacFileChooserOnUiThread>(
ui_task_runner, base::SequencedTaskRunnerHandle::Get(),
weak_ptr_factory_.GetWeakPtr());
}
void FileChooserMac::Show() {
mac_file_chooser_on_ui_thread_.Post(FROM_HERE,
&MacFileChooserOnUiThread::Show);
}
void FileChooserMac::RunCallback(FileChooser::Result result) {
std::move(callback_).Run(std::move(result));
}
FileChooserMac::~FileChooserMac() = default;
} // namespace
std::unique_ptr<FileChooser> FileChooser::Create(
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
ResultCallback callback) {
return std::make_unique<FileChooserMac>(std::move(ui_task_runner),
std::move(callback));
}
} // namespace remoting