blob: 2050d85425ceb0432b91eb4ac8e393c7c77f60f8 [file] [log] [blame]
// Copyright (c) 2012 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/clipboard.h"
#import <Cocoa/Cocoa.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/sys_string_conversions.h"
#include "base/timer/timer.h"
#include "remoting/base/constants.h"
#include "remoting/base/util.h"
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/clipboard_stub.h"
namespace {
// Clipboard polling interval in milliseconds.
const int64 kClipboardPollingIntervalMs = 500;
} // namespace
namespace remoting {
class ClipboardMac : public Clipboard {
public:
ClipboardMac();
virtual ~ClipboardMac();
// Must be called on the UI thread.
virtual void Start(
scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
virtual void InjectClipboardEvent(
const protocol::ClipboardEvent& event) OVERRIDE;
virtual void Stop() OVERRIDE;
private:
void CheckClipboardForChanges();
scoped_ptr<protocol::ClipboardStub> client_clipboard_;
scoped_ptr<base::RepeatingTimer<ClipboardMac> > clipboard_polling_timer_;
NSInteger current_change_count_;
DISALLOW_COPY_AND_ASSIGN(ClipboardMac);
};
ClipboardMac::ClipboardMac() : current_change_count_(0) {
}
ClipboardMac::~ClipboardMac() {
// In it2me the destructor is not called in the same thread that the timer is
// created. Thus the timer must have already been destroyed by now.
DCHECK(clipboard_polling_timer_.get() == NULL);
}
void ClipboardMac::Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) {
client_clipboard_.reset(client_clipboard.release());
// Synchronize local change-count with the pasteboard's. The change-count is
// used to detect clipboard changes.
current_change_count_ = [[NSPasteboard generalPasteboard] changeCount];
// OS X doesn't provide a clipboard-changed notification. The only way to
// detect clipboard changes is by polling.
clipboard_polling_timer_.reset(new base::RepeatingTimer<ClipboardMac>());
clipboard_polling_timer_->Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kClipboardPollingIntervalMs),
this, &ClipboardMac::CheckClipboardForChanges);
}
void ClipboardMac::InjectClipboardEvent(const protocol::ClipboardEvent& event) {
// Currently we only handle UTF-8 text.
if (event.mime_type().compare(kMimeTypeTextUtf8) != 0)
return;
if (!StringIsUtf8(event.data().c_str(), event.data().length())) {
LOG(ERROR) << "ClipboardEvent data is not UTF-8 encoded.";
return;
}
// Write UTF-8 text to clipboard.
NSString* text = base::SysUTF8ToNSString(event.data());
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType]
owner:nil];
[pasteboard setString:text forType:NSStringPboardType];
// Update local change-count to prevent this change from being picked up by
// CheckClipboardForChanges.
current_change_count_ = [[NSPasteboard generalPasteboard] changeCount];
}
void ClipboardMac::Stop() {
clipboard_polling_timer_.reset();
client_clipboard_.reset();
}
void ClipboardMac::CheckClipboardForChanges() {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSInteger change_count = [pasteboard changeCount];
if (change_count == current_change_count_) {
return;
}
current_change_count_ = change_count;
NSString* data = [pasteboard stringForType:NSStringPboardType];
if (data == nil) {
return;
}
protocol::ClipboardEvent event;
event.set_mime_type(kMimeTypeTextUtf8);
event.set_data(base::SysNSStringToUTF8(data));
client_clipboard_->InjectClipboardEvent(event);
}
scoped_ptr<Clipboard> Clipboard::Create() {
return scoped_ptr<Clipboard>(new ClipboardMac());
}
} // namespace remoting