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