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.
5 #include "remoting/host/clipboard.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/basictypes.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/timer/timer.h"
14 #include "remoting/base/constants.h"
15 #include "remoting/base/util.h"
16 #include "remoting/proto/event.pb.h"
17 #include "remoting/protocol/clipboard_stub.h"
21 // Clipboard polling interval in milliseconds.
22 const int64 kClipboardPollingIntervalMs = 500;
28 class ClipboardMac : public Clipboard {
31 virtual ~ClipboardMac();
33 // Must be called on the UI thread.
35 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
36 virtual void InjectClipboardEvent(
37 const protocol::ClipboardEvent& event) OVERRIDE;
38 virtual void Stop() OVERRIDE;
41 void CheckClipboardForChanges();
43 scoped_ptr<protocol::ClipboardStub> client_clipboard_;
44 scoped_ptr<base::RepeatingTimer<ClipboardMac> > clipboard_polling_timer_;
45 NSInteger current_change_count_;
47 DISALLOW_COPY_AND_ASSIGN(ClipboardMac);
50 ClipboardMac::ClipboardMac() : current_change_count_(0) {
53 ClipboardMac::~ClipboardMac() {
54 // In it2me the destructor is not called in the same thread that the timer is
55 // created. Thus the timer must have already been destroyed by now.
56 DCHECK(clipboard_polling_timer_.get() == NULL);
59 void ClipboardMac::Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) {
60 client_clipboard_.reset(client_clipboard.release());
62 // Synchronize local change-count with the pasteboard's. The change-count is
63 // used to detect clipboard changes.
64 current_change_count_ = [[NSPasteboard generalPasteboard] changeCount];
66 // OS X doesn't provide a clipboard-changed notification. The only way to
67 // detect clipboard changes is by polling.
68 clipboard_polling_timer_.reset(new base::RepeatingTimer<ClipboardMac>());
69 clipboard_polling_timer_->Start(FROM_HERE,
70 base::TimeDelta::FromMilliseconds(kClipboardPollingIntervalMs),
71 this, &ClipboardMac::CheckClipboardForChanges);
74 void ClipboardMac::InjectClipboardEvent(const protocol::ClipboardEvent& event) {
75 // Currently we only handle UTF-8 text.
76 if (event.mime_type().compare(kMimeTypeTextUtf8) != 0)
78 if (!StringIsUtf8(event.data().c_str(), event.data().length())) {
79 LOG(ERROR) << "ClipboardEvent data is not UTF-8 encoded.";
83 // Write UTF-8 text to clipboard.
84 NSString* text = base::SysUTF8ToNSString(event.data());
85 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
86 [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType]
88 [pasteboard setString:text forType:NSStringPboardType];
90 // Update local change-count to prevent this change from being picked up by
91 // CheckClipboardForChanges.
92 current_change_count_ = [[NSPasteboard generalPasteboard] changeCount];
95 void ClipboardMac::Stop() {
96 clipboard_polling_timer_.reset();
97 client_clipboard_.reset();
100 void ClipboardMac::CheckClipboardForChanges() {
101 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
102 NSInteger change_count = [pasteboard changeCount];
103 if (change_count == current_change_count_) {
106 current_change_count_ = change_count;
108 NSString* data = [pasteboard stringForType:NSStringPboardType];
113 protocol::ClipboardEvent event;
114 event.set_mime_type(kMimeTypeTextUtf8);
115 event.set_data(base::SysNSStringToUTF8(data));
116 client_clipboard_->InjectClipboardEvent(event);
119 scoped_ptr<Clipboard> Clipboard::Create() {
120 return scoped_ptr<Clipboard>(new ClipboardMac());
123 } // namespace remoting