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