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
23namespace {
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.
28class 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
94typedef BOOL (WINAPI AddClipboardFormatListenerFn)(HWND);
95typedef BOOL (WINAPI RemoveClipboardFormatListenerFn)(HWND);
96
97}  // namespace
98
99namespace remoting {
100
101class 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
130ClipboardWin::ClipboardWin()
131    : add_clipboard_format_listener_(NULL),
132      remove_clipboard_format_listener_(NULL) {
133}
134
135void 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
176void 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
185void 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
224void 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
263bool 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
274scoped_ptr<Clipboard> Clipboard::Create() {
275  return scoped_ptr<Clipboard>(new ClipboardWin());
276}
277
278}  // namespace remoting
279