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 "remoting/host/clipboard.h"
6
7#import <Cocoa/Cocoa.h>
8
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"
18
19namespace {
20
21// Clipboard polling interval in milliseconds.
22const int64 kClipboardPollingIntervalMs = 500;
23
24} // namespace
25
26namespace remoting {
27
28class ClipboardMac : public Clipboard {
29 public:
30  ClipboardMac();
31  virtual ~ClipboardMac();
32
33  // Must be called on the UI thread.
34  virtual void Start(
35      scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
36  virtual void InjectClipboardEvent(
37      const protocol::ClipboardEvent& event) OVERRIDE;
38  virtual void Stop() OVERRIDE;
39
40 private:
41  void CheckClipboardForChanges();
42
43  scoped_ptr<protocol::ClipboardStub> client_clipboard_;
44  scoped_ptr<base::RepeatingTimer<ClipboardMac> > clipboard_polling_timer_;
45  NSInteger current_change_count_;
46
47  DISALLOW_COPY_AND_ASSIGN(ClipboardMac);
48};
49
50ClipboardMac::ClipboardMac() : current_change_count_(0) {
51}
52
53ClipboardMac::~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);
57}
58
59void ClipboardMac::Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) {
60  client_clipboard_.reset(client_clipboard.release());
61
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];
65
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);
72}
73
74void ClipboardMac::InjectClipboardEvent(const protocol::ClipboardEvent& event) {
75  // Currently we only handle UTF-8 text.
76  if (event.mime_type().compare(kMimeTypeTextUtf8) != 0)
77    return;
78  if (!StringIsUtf8(event.data().c_str(), event.data().length())) {
79    LOG(ERROR) << "ClipboardEvent data is not UTF-8 encoded.";
80    return;
81  }
82
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]
87                     owner:nil];
88  [pasteboard setString:text forType:NSStringPboardType];
89
90  // Update local change-count to prevent this change from being picked up by
91  // CheckClipboardForChanges.
92  current_change_count_ = [[NSPasteboard generalPasteboard] changeCount];
93}
94
95void ClipboardMac::Stop() {
96  clipboard_polling_timer_.reset();
97  client_clipboard_.reset();
98}
99
100void ClipboardMac::CheckClipboardForChanges() {
101  NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
102  NSInteger change_count = [pasteboard changeCount];
103  if (change_count == current_change_count_) {
104    return;
105  }
106  current_change_count_ = change_count;
107
108  NSString* data = [pasteboard stringForType:NSStringPboardType];
109  if (data == nil) {
110    return;
111  }
112
113  protocol::ClipboardEvent event;
114  event.set_mime_type(kMimeTypeTextUtf8);
115  event.set_data(base::SysNSStringToUTF8(data));
116  client_clipboard_->InjectClipboardEvent(event);
117}
118
119scoped_ptr<Clipboard> Clipboard::Create() {
120  return scoped_ptr<Clipboard>(new ClipboardMac());
121}
122
123}  // namespace remoting
124