Home | History | Annotate | Download | only in host
      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 
     19 namespace {
     20 
     21 // Clipboard polling interval in milliseconds.
     22 const int64 kClipboardPollingIntervalMs = 500;
     23 
     24 } // namespace
     25 
     26 namespace remoting {
     27 
     28 class 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 
     50 ClipboardMac::ClipboardMac() : current_change_count_(0) {
     51 }
     52 
     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);
     57 }
     58 
     59 void 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 
     74 void 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 
     95 void ClipboardMac::Stop() {
     96   clipboard_polling_timer_.reset();
     97   client_clipboard_.reset();
     98 }
     99 
    100 void 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 
    119 scoped_ptr<Clipboard> Clipboard::Create() {
    120   return scoped_ptr<Clipboard>(new ClipboardMac());
    121 }
    122 
    123 }  // namespace remoting
    124