Home | History | Annotate | Download | only in host
      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   string16 text = 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     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(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