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 #include <windows.h> 8 9 #include "base/basictypes.h" 10 #include "base/bind.h" 11 #include "base/logging.h" 12 #include "base/strings/string16.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/threading/platform_thread.h" 15 #include "base/win/message_window.h" 16 #include "base/win/scoped_hglobal.h" 17 #include "base/win/windows_version.h" 18 #include "remoting/base/constants.h" 19 #include "remoting/base/util.h" 20 #include "remoting/proto/event.pb.h" 21 #include "remoting/protocol/clipboard_stub.h" 22 23 namespace { 24 25 // A scoper class that opens and closes the clipboard. 26 // This class was adapted from the ScopedClipboard class in 27 // ui/base/clipboard/clipboard_win.cc. 28 class ScopedClipboard { 29 public: 30 ScopedClipboard() : opened_(false) { 31 } 32 33 ~ScopedClipboard() { 34 if (opened_) { 35 ::CloseClipboard(); 36 } 37 } 38 39 bool Init(HWND owner) { 40 const int kMaxAttemptsToOpenClipboard = 5; 41 const base::TimeDelta kSleepTimeBetweenAttempts = 42 base::TimeDelta::FromMilliseconds(5); 43 44 if (opened_) { 45 NOTREACHED(); 46 return true; 47 } 48 49 // This code runs on the UI thread, so we can block only very briefly. 50 for (int attempt = 0; attempt < kMaxAttemptsToOpenClipboard; ++attempt) { 51 if (attempt > 0) { 52 base::PlatformThread::Sleep(kSleepTimeBetweenAttempts); 53 } 54 if (::OpenClipboard(owner)) { 55 opened_ = true; 56 return true; 57 } 58 } 59 return false; 60 } 61 62 BOOL Empty() { 63 if (!opened_) { 64 NOTREACHED(); 65 return false; 66 } 67 return ::EmptyClipboard(); 68 } 69 70 void SetData(UINT uFormat, HANDLE hMem) { 71 if (!opened_) { 72 NOTREACHED(); 73 return; 74 } 75 // The caller must not close the handle that ::SetClipboardData returns. 76 ::SetClipboardData(uFormat, hMem); 77 } 78 79 // The caller must not free the handle. The caller should lock the handle, 80 // copy the clipboard data, and unlock the handle. All this must be done 81 // before this ScopedClipboard is destroyed. 82 HANDLE GetData(UINT format) { 83 if (!opened_) { 84 NOTREACHED(); 85 return NULL; 86 } 87 return ::GetClipboardData(format); 88 } 89 90 private: 91 bool opened_; 92 }; 93 94 typedef BOOL (WINAPI AddClipboardFormatListenerFn)(HWND); 95 typedef BOOL (WINAPI RemoveClipboardFormatListenerFn)(HWND); 96 97 } // namespace 98 99 namespace remoting { 100 101 class ClipboardWin : public Clipboard { 102 public: 103 ClipboardWin(); 104 105 virtual void Start( 106 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; 107 virtual void InjectClipboardEvent( 108 const protocol::ClipboardEvent& event) OVERRIDE; 109 virtual void Stop() OVERRIDE; 110 111 private: 112 void OnClipboardUpdate(); 113 114 // Handles messages received by |window_|. 115 bool HandleMessage(UINT message, 116 WPARAM wparam, 117 LPARAM lparam, 118 LRESULT* result); 119 120 scoped_ptr<protocol::ClipboardStub> client_clipboard_; 121 AddClipboardFormatListenerFn* add_clipboard_format_listener_; 122 RemoveClipboardFormatListenerFn* remove_clipboard_format_listener_; 123 124 // Used to subscribe to WM_CLIPBOARDUPDATE messages. 125 scoped_ptr<base::win::MessageWindow> window_; 126 127 DISALLOW_COPY_AND_ASSIGN(ClipboardWin); 128 }; 129 130 ClipboardWin::ClipboardWin() 131 : add_clipboard_format_listener_(NULL), 132 remove_clipboard_format_listener_(NULL) { 133 } 134 135 void ClipboardWin::Start( 136 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 137 DCHECK(!add_clipboard_format_listener_); 138 DCHECK(!remove_clipboard_format_listener_); 139 DCHECK(!window_); 140 141 client_clipboard_.swap(client_clipboard); 142 143 // user32.dll is statically linked. 144 HMODULE user32 = GetModuleHandle(L"user32.dll"); 145 CHECK(user32); 146 147 add_clipboard_format_listener_ = 148 reinterpret_cast<AddClipboardFormatListenerFn*>( 149 GetProcAddress(user32, "AddClipboardFormatListener")); 150 if (add_clipboard_format_listener_) { 151 remove_clipboard_format_listener_ = 152 reinterpret_cast<RemoveClipboardFormatListenerFn*>( 153 GetProcAddress(user32, "RemoveClipboardFormatListener")); 154 // If AddClipboardFormatListener() present, RemoveClipboardFormatListener() 155 // should be available too. 156 CHECK(remove_clipboard_format_listener_); 157 } else { 158 LOG(WARNING) << "AddClipboardFormatListener() is not available."; 159 } 160 161 window_.reset(new base::win::MessageWindow()); 162 if (!window_->Create(base::Bind(&ClipboardWin::HandleMessage, 163 base::Unretained(this)))) { 164 LOG(ERROR) << "Couldn't create clipboard window."; 165 window_.reset(); 166 return; 167 } 168 169 if (add_clipboard_format_listener_) { 170 if (!(*add_clipboard_format_listener_)(window_->hwnd())) { 171 LOG(WARNING) << "AddClipboardFormatListener() failed: " << GetLastError(); 172 } 173 } 174 } 175 176 void ClipboardWin::Stop() { 177 client_clipboard_.reset(); 178 179 if (window_ && remove_clipboard_format_listener_) 180 (*remove_clipboard_format_listener_)(window_->hwnd()); 181 182 window_.reset(); 183 } 184 185 void ClipboardWin::InjectClipboardEvent( 186 const protocol::ClipboardEvent& event) { 187 if (!window_) 188 return; 189 190 // Currently we only handle UTF-8 text. 191 if (event.mime_type().compare(kMimeTypeTextUtf8) != 0) 192 return; 193 if (!StringIsUtf8(event.data().c_str(), event.data().length())) { 194 LOG(ERROR) << "ClipboardEvent: data is not UTF-8 encoded."; 195 return; 196 } 197 198 base::string16 text = base::UTF8ToUTF16(ReplaceLfByCrLf(event.data())); 199 200 ScopedClipboard clipboard; 201 if (!clipboard.Init(window_->hwnd())) { 202 LOG(WARNING) << "Couldn't open the clipboard."; 203 return; 204 } 205 206 clipboard.Empty(); 207 208 HGLOBAL text_global = 209 ::GlobalAlloc(GMEM_MOVEABLE, (text.size() + 1) * sizeof(WCHAR)); 210 if (!text_global) { 211 LOG(WARNING) << "Couldn't allocate global memory."; 212 return; 213 } 214 215 LPWSTR text_global_locked = 216 reinterpret_cast<LPWSTR>(::GlobalLock(text_global)); 217 memcpy(text_global_locked, text.data(), text.size() * sizeof(WCHAR)); 218 text_global_locked[text.size()] = (WCHAR)0; 219 ::GlobalUnlock(text_global); 220 221 clipboard.SetData(CF_UNICODETEXT, text_global); 222 } 223 224 void ClipboardWin::OnClipboardUpdate() { 225 DCHECK(window_); 226 227 if (::IsClipboardFormatAvailable(CF_UNICODETEXT)) { 228 base::string16 text; 229 // Add a scope, so that we keep the clipboard open for as short a time as 230 // possible. 231 { 232 ScopedClipboard clipboard; 233 if (!clipboard.Init(window_->hwnd())) { 234 LOG(WARNING) << "Couldn't open the clipboard." << GetLastError(); 235 return; 236 } 237 238 HGLOBAL text_global = clipboard.GetData(CF_UNICODETEXT); 239 if (!text_global) { 240 LOG(WARNING) << "Couldn't get data from the clipboard: " 241 << GetLastError(); 242 return; 243 } 244 245 base::win::ScopedHGlobal<WCHAR*> text_lock(text_global); 246 if (!text_lock.get()) { 247 LOG(WARNING) << "Couldn't lock clipboard data: " << GetLastError(); 248 return; 249 } 250 text.assign(text_lock.get()); 251 } 252 253 protocol::ClipboardEvent event; 254 event.set_mime_type(kMimeTypeTextUtf8); 255 event.set_data(ReplaceCrLfByLf(base::UTF16ToUTF8(text))); 256 257 if (client_clipboard_.get()) { 258 client_clipboard_->InjectClipboardEvent(event); 259 } 260 } 261 } 262 263 bool ClipboardWin::HandleMessage( 264 UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { 265 if (message == WM_CLIPBOARDUPDATE) { 266 OnClipboardUpdate(); 267 *result = 0; 268 return true; 269 } 270 271 return false; 272 } 273 274 scoped_ptr<Clipboard> Clipboard::Create() { 275 return scoped_ptr<Clipboard>(new ClipboardWin()); 276 } 277 278 } // namespace remoting 279