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.
5 #include "remoting/host/clipboard.h"
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"
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
{
30 ScopedClipboard() : opened_(false) {
35 // CloseClipboard() must be called with anonymous access token. See
37 BOOL result
= ::ImpersonateAnonymousToken(::GetCurrentThread());
40 result
= ::RevertToSelf();
45 bool Init(HWND owner
) {
46 const int kMaxAttemptsToOpenClipboard
= 5;
47 const base::TimeDelta kSleepTimeBetweenAttempts
=
48 base::TimeDelta::FromMilliseconds(5);
55 // This code runs on the UI thread, so we can block only very briefly.
56 for (int attempt
= 0; attempt
< kMaxAttemptsToOpenClipboard
; ++attempt
) {
58 base::PlatformThread::Sleep(kSleepTimeBetweenAttempts
);
60 if (::OpenClipboard(owner
)) {
73 return ::EmptyClipboard();
76 void SetData(UINT uFormat
, HANDLE hMem
) {
81 // The caller must not close the handle that ::SetClipboardData returns.
82 ::SetClipboardData(uFormat
, hMem
);
85 // The caller must not free the handle. The caller should lock the handle,
86 // copy the clipboard data, and unlock the handle. All this must be done
87 // before this ScopedClipboard is destroyed.
88 HANDLE
GetData(UINT format
) {
93 return ::GetClipboardData(format
);
100 typedef BOOL (WINAPI AddClipboardFormatListenerFn
)(HWND
);
101 typedef BOOL (WINAPI RemoveClipboardFormatListenerFn
)(HWND
);
107 class ClipboardWin
: public Clipboard
{
112 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) override
;
113 virtual void InjectClipboardEvent(
114 const protocol::ClipboardEvent
& event
) override
;
115 virtual void Stop() override
;
118 void OnClipboardUpdate();
120 // Handles messages received by |window_|.
121 bool HandleMessage(UINT message
,
126 scoped_ptr
<protocol::ClipboardStub
> client_clipboard_
;
127 AddClipboardFormatListenerFn
* add_clipboard_format_listener_
;
128 RemoveClipboardFormatListenerFn
* remove_clipboard_format_listener_
;
130 // Used to subscribe to WM_CLIPBOARDUPDATE messages.
131 scoped_ptr
<base::win::MessageWindow
> window_
;
133 DISALLOW_COPY_AND_ASSIGN(ClipboardWin
);
136 ClipboardWin::ClipboardWin()
137 : add_clipboard_format_listener_(nullptr),
138 remove_clipboard_format_listener_(nullptr) {
141 void ClipboardWin::Start(
142 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
143 DCHECK(!add_clipboard_format_listener_
);
144 DCHECK(!remove_clipboard_format_listener_
);
147 client_clipboard_
.swap(client_clipboard
);
149 // user32.dll is statically linked.
150 HMODULE user32
= GetModuleHandle(L
"user32.dll");
153 add_clipboard_format_listener_
=
154 reinterpret_cast<AddClipboardFormatListenerFn
*>(
155 GetProcAddress(user32
, "AddClipboardFormatListener"));
156 if (add_clipboard_format_listener_
) {
157 remove_clipboard_format_listener_
=
158 reinterpret_cast<RemoveClipboardFormatListenerFn
*>(
159 GetProcAddress(user32
, "RemoveClipboardFormatListener"));
160 // If AddClipboardFormatListener() present, RemoveClipboardFormatListener()
161 // should be available too.
162 CHECK(remove_clipboard_format_listener_
);
164 LOG(WARNING
) << "AddClipboardFormatListener() is not available.";
167 window_
.reset(new base::win::MessageWindow());
168 if (!window_
->Create(base::Bind(&ClipboardWin::HandleMessage
,
169 base::Unretained(this)))) {
170 LOG(ERROR
) << "Couldn't create clipboard window.";
175 if (add_clipboard_format_listener_
) {
176 if (!(*add_clipboard_format_listener_
)(window_
->hwnd())) {
177 LOG(WARNING
) << "AddClipboardFormatListener() failed: " << GetLastError();
182 void ClipboardWin::Stop() {
183 client_clipboard_
.reset();
185 if (window_
&& remove_clipboard_format_listener_
)
186 (*remove_clipboard_format_listener_
)(window_
->hwnd());
191 void ClipboardWin::InjectClipboardEvent(
192 const protocol::ClipboardEvent
& event
) {
196 // Currently we only handle UTF-8 text.
197 if (event
.mime_type().compare(kMimeTypeTextUtf8
) != 0)
199 if (!StringIsUtf8(event
.data().c_str(), event
.data().length())) {
200 LOG(ERROR
) << "ClipboardEvent: data is not UTF-8 encoded.";
204 base::string16 text
= base::UTF8ToUTF16(ReplaceLfByCrLf(event
.data()));
206 ScopedClipboard clipboard
;
207 if (!clipboard
.Init(window_
->hwnd())) {
208 LOG(WARNING
) << "Couldn't open the clipboard.";
214 HGLOBAL text_global
=
215 ::GlobalAlloc(GMEM_MOVEABLE
, (text
.size() + 1) * sizeof(WCHAR
));
217 LOG(WARNING
) << "Couldn't allocate global memory.";
221 LPWSTR text_global_locked
=
222 reinterpret_cast<LPWSTR
>(::GlobalLock(text_global
));
223 memcpy(text_global_locked
, text
.data(), text
.size() * sizeof(WCHAR
));
224 text_global_locked
[text
.size()] = (WCHAR
)0;
225 ::GlobalUnlock(text_global
);
227 clipboard
.SetData(CF_UNICODETEXT
, text_global
);
230 void ClipboardWin::OnClipboardUpdate() {
233 if (::IsClipboardFormatAvailable(CF_UNICODETEXT
)) {
235 // Add a scope, so that we keep the clipboard open for as short a time as
238 ScopedClipboard clipboard
;
239 if (!clipboard
.Init(window_
->hwnd())) {
240 LOG(WARNING
) << "Couldn't open the clipboard." << GetLastError();
244 HGLOBAL text_global
= clipboard
.GetData(CF_UNICODETEXT
);
246 LOG(WARNING
) << "Couldn't get data from the clipboard: "
251 base::win::ScopedHGlobal
<WCHAR
*> text_lock(text_global
);
252 if (!text_lock
.get()) {
253 LOG(WARNING
) << "Couldn't lock clipboard data: " << GetLastError();
256 text
.assign(text_lock
.get());
259 protocol::ClipboardEvent event
;
260 event
.set_mime_type(kMimeTypeTextUtf8
);
261 event
.set_data(ReplaceCrLfByLf(base::UTF16ToUTF8(text
)));
263 if (client_clipboard_
.get()) {
264 client_clipboard_
->InjectClipboardEvent(event
);
269 bool ClipboardWin::HandleMessage(
270 UINT message
, WPARAM wparam
, LPARAM lparam
, LRESULT
* result
) {
271 if (message
== WM_CLIPBOARDUPDATE
) {
280 scoped_ptr
<Clipboard
> Clipboard::Create() {
281 return make_scoped_ptr(new ClipboardWin());
284 } // namespace remoting