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 <windows.h>
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/logging.h"
      9 #include "base/process/memory.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/win/scoped_gdi_object.h"
     13 #include "base/win/scoped_hdc.h"
     14 #include "base/win/scoped_select_object.h"
     15 #include "remoting/host/client_session_control.h"
     16 #include "remoting/host/host_window.h"
     17 #include "remoting/host/win/core_resource.h"
     18 
     19 namespace remoting {
     20 
     21 namespace {
     22 
     23 const int DISCONNECT_HOTKEY_ID = 1000;
     24 
     25 // Maximum length of "Your desktop is shared with ..." message in UTF-16
     26 // characters.
     27 const size_t kMaxSharingWithTextLength = 100;
     28 
     29 const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
     30 const int kWindowBorderRadius = 14;
     31 
     32 // Margin between dialog controls (in dialog units).
     33 const int kWindowTextMargin = 8;
     34 
     35 class DisconnectWindowWin : public HostWindow {
     36  public:
     37   DisconnectWindowWin();
     38   virtual ~DisconnectWindowWin();
     39 
     40   // HostWindow overrides.
     41   virtual void Start(
     42       const base::WeakPtr<ClientSessionControl>& client_session_control)
     43       OVERRIDE;
     44 
     45  protected:
     46   static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
     47                                      LPARAM lparam);
     48 
     49   BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
     50 
     51   // Creates the dialog window and registers the disconnect hot key.
     52   bool BeginDialog();
     53 
     54   // Closes the dialog, unregisters the hot key and invokes the disconnect
     55   // callback, if set.
     56   void EndDialog();
     57 
     58   // Returns |control| rectangle in the dialog coordinates.
     59   bool GetControlRect(HWND control, RECT* rect);
     60 
     61   // Trys to position the dialog window above the taskbar.
     62   void SetDialogPosition();
     63 
     64   // Applies localization string and resizes the dialog.
     65   bool SetStrings();
     66 
     67   // Used to disconnect the client session.
     68   base::WeakPtr<ClientSessionControl> client_session_control_;
     69 
     70   // Specifies the remote user name.
     71   std::string username_;
     72 
     73   HWND hwnd_;
     74   bool has_hotkey_;
     75   base::win::ScopedGDIObject<HPEN> border_pen_;
     76 
     77   DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin);
     78 };
     79 
     80 // Returns the text for the given dialog control window.
     81 bool GetControlText(HWND control, string16* text) {
     82   // GetWindowText truncates the text if it is longer than can fit into
     83   // the buffer.
     84   WCHAR buffer[256];
     85   int result = GetWindowText(control, buffer, arraysize(buffer));
     86   if (!result)
     87     return false;
     88 
     89   text->assign(buffer);
     90   return true;
     91 }
     92 
     93 // Returns width |text| rendered in |control| window.
     94 bool GetControlTextWidth(HWND control, const string16& text, LONG* width) {
     95   RECT rect = {0, 0, 0, 0};
     96   base::win::ScopedGetDC dc(control);
     97   base::win::ScopedSelectObject font(
     98       dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0));
     99   if (!DrawText(dc, text.c_str(), -1, &rect, DT_CALCRECT | DT_SINGLELINE))
    100     return false;
    101 
    102   *width = rect.right;
    103   return true;
    104 }
    105 
    106 DisconnectWindowWin::DisconnectWindowWin()
    107     : hwnd_(NULL),
    108       has_hotkey_(false),
    109       border_pen_(CreatePen(PS_SOLID, 5,
    110                             RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
    111 }
    112 
    113 DisconnectWindowWin::~DisconnectWindowWin() {
    114   EndDialog();
    115 }
    116 
    117 void DisconnectWindowWin::Start(
    118     const base::WeakPtr<ClientSessionControl>& client_session_control) {
    119   DCHECK(CalledOnValidThread());
    120   DCHECK(!client_session_control_);
    121   DCHECK(client_session_control);
    122 
    123   client_session_control_ = client_session_control;
    124 
    125   std::string client_jid = client_session_control_->client_jid();
    126   username_ = client_jid.substr(0, client_jid.find('/'));
    127   if (!BeginDialog())
    128     EndDialog();
    129 }
    130 
    131 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
    132                                                  UINT message,
    133                                                  WPARAM wparam,
    134                                                  LPARAM lparam) {
    135   LONG_PTR self = NULL;
    136   if (message == WM_INITDIALOG) {
    137     self = lparam;
    138 
    139     // Store |this| to the window's user data.
    140     SetLastError(ERROR_SUCCESS);
    141     LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self);
    142     if (result == 0 && GetLastError() != ERROR_SUCCESS)
    143       reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog();
    144   } else {
    145     self = GetWindowLongPtr(hwnd, DWLP_USER);
    146   }
    147 
    148   if (self) {
    149     return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
    150         hwnd, message, wparam, lparam);
    151   }
    152   return FALSE;
    153 }
    154 
    155 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
    156                                           UINT message,
    157                                           WPARAM wparam,
    158                                           LPARAM lparam) {
    159   DCHECK(CalledOnValidThread());
    160 
    161   switch (message) {
    162     // Ignore close messages.
    163     case WM_CLOSE:
    164       return TRUE;
    165 
    166     // Handle the Disconnect button.
    167     case WM_COMMAND:
    168       switch (LOWORD(wparam)) {
    169         case IDC_DISCONNECT:
    170           EndDialog();
    171           return TRUE;
    172       }
    173       return FALSE;
    174 
    175     // Ensure we don't try to use the HWND anymore.
    176     case WM_DESTROY:
    177       hwnd_ = NULL;
    178 
    179       // Ensure that the disconnect callback is invoked even if somehow our
    180       // window gets destroyed.
    181       EndDialog();
    182 
    183       return TRUE;
    184 
    185     // Ensure the dialog stays visible if the work area dimensions change.
    186     case WM_SETTINGCHANGE:
    187       if (wparam == SPI_SETWORKAREA)
    188         SetDialogPosition();
    189       return TRUE;
    190 
    191     // Ensure the dialog stays visible if the display dimensions change.
    192     case WM_DISPLAYCHANGE:
    193       SetDialogPosition();
    194       return TRUE;
    195 
    196     // Handle the disconnect hot-key.
    197     case WM_HOTKEY:
    198       EndDialog();
    199       return TRUE;
    200 
    201     // Let the window be draggable by its client area by responding
    202     // that the entire window is the title bar.
    203     case WM_NCHITTEST:
    204       SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION);
    205       return TRUE;
    206 
    207     case WM_PAINT: {
    208       PAINTSTRUCT ps;
    209       HDC hdc = BeginPaint(hwnd_, &ps);
    210       RECT rect;
    211       GetClientRect(hwnd_, &rect);
    212       {
    213         base::win::ScopedSelectObject border(hdc, border_pen_);
    214         base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH));
    215         RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1,
    216                   kWindowBorderRadius, kWindowBorderRadius);
    217       }
    218       EndPaint(hwnd_, &ps);
    219       return TRUE;
    220     }
    221   }
    222   return FALSE;
    223 }
    224 
    225 bool DisconnectWindowWin::BeginDialog() {
    226   DCHECK(CalledOnValidThread());
    227   DCHECK(!hwnd_);
    228 
    229   HMODULE module = base::GetModuleFromAddress(&DialogProc);
    230   hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), NULL,
    231                             DialogProc, reinterpret_cast<LPARAM>(this));
    232   if (!hwnd_)
    233     return false;
    234 
    235   // Set up handler for Ctrl-Alt-Esc shortcut.
    236   if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID,
    237                                      MOD_ALT | MOD_CONTROL, VK_ESCAPE)) {
    238     has_hotkey_ = true;
    239   }
    240 
    241   if (!SetStrings())
    242     return false;
    243 
    244   SetDialogPosition();
    245   ShowWindow(hwnd_, SW_SHOW);
    246   return IsWindowVisible(hwnd_) != FALSE;
    247 }
    248 
    249 void DisconnectWindowWin::EndDialog() {
    250   DCHECK(CalledOnValidThread());
    251 
    252   if (has_hotkey_) {
    253     UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
    254     has_hotkey_ = false;
    255   }
    256 
    257   if (hwnd_) {
    258     DestroyWindow(hwnd_);
    259     hwnd_ = NULL;
    260   }
    261 
    262   if (client_session_control_)
    263     client_session_control_->DisconnectSession();
    264 }
    265 
    266 // Returns |control| rectangle in the dialog coordinates.
    267 bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) {
    268   if (!GetWindowRect(control, rect))
    269     return false;
    270   SetLastError(ERROR_SUCCESS);
    271   int result = MapWindowPoints(HWND_DESKTOP, hwnd_,
    272                                reinterpret_cast<LPPOINT>(rect), 2);
    273   if (!result && GetLastError() != ERROR_SUCCESS)
    274     return false;
    275 
    276   return true;
    277 }
    278 
    279 void DisconnectWindowWin::SetDialogPosition() {
    280   DCHECK(CalledOnValidThread());
    281 
    282   // Try to center the window above the task-bar. If that fails, use the
    283   // primary monitor. If that fails (very unlikely), use the default position.
    284   HWND taskbar = FindWindow(kShellTrayWindowName, NULL);
    285   HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
    286   MONITORINFO monitor_info = {sizeof(monitor_info)};
    287   RECT window_rect;
    288   if (GetMonitorInfo(monitor, &monitor_info) &&
    289       GetWindowRect(hwnd_, &window_rect)) {
    290     int window_width = window_rect.right - window_rect.left;
    291     int window_height = window_rect.bottom - window_rect.top;
    292     int top = monitor_info.rcWork.bottom - window_height;
    293     int left = (monitor_info.rcWork.right + monitor_info.rcWork.left -
    294         window_width) / 2;
    295     SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
    296   }
    297 }
    298 
    299 bool DisconnectWindowWin::SetStrings() {
    300   DCHECK(CalledOnValidThread());
    301 
    302   // Localize the disconnect button text and measure length of the old and new
    303   // labels.
    304   HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
    305   HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
    306   if (!hwnd_button || !hwnd_message)
    307     return false;
    308 
    309   string16 button_text;
    310   string16 message_text;
    311   if (!GetControlText(hwnd_button, &button_text) ||
    312       !GetControlText(hwnd_message, &message_text)) {
    313     return false;
    314   }
    315 
    316   // Format and truncate "Your desktop is shared with ..." message.
    317   message_text = ReplaceStringPlaceholders(message_text, UTF8ToUTF16(username_),
    318                                            NULL);
    319   if (message_text.length() > kMaxSharingWithTextLength)
    320     message_text.erase(kMaxSharingWithTextLength);
    321 
    322   if (!SetWindowText(hwnd_message, message_text.c_str()))
    323     return false;
    324 
    325   // Calculate the margin between controls in pixels.
    326   RECT rect = {0};
    327   rect.right = kWindowTextMargin;
    328   if (!MapDialogRect(hwnd_, &rect))
    329     return false;
    330   int margin = rect.right;
    331 
    332   // Resize |hwnd_message| so that the text is not clipped.
    333   RECT message_rect;
    334   if (!GetControlRect(hwnd_message, &message_rect))
    335     return false;
    336 
    337   LONG control_width;
    338   if (!GetControlTextWidth(hwnd_message, message_text, &control_width))
    339     return false;
    340   message_rect.right = message_rect.left + control_width + margin;
    341 
    342   if (!SetWindowPos(hwnd_message, NULL,
    343                     message_rect.left, message_rect.top,
    344                     message_rect.right - message_rect.left,
    345                     message_rect.bottom - message_rect.top,
    346                     SWP_NOZORDER)) {
    347     return false;
    348   }
    349 
    350   // Reposition and resize |hwnd_button| as well.
    351   RECT button_rect;
    352   if (!GetControlRect(hwnd_button, &button_rect))
    353     return false;
    354 
    355   if (!GetControlTextWidth(hwnd_button, button_text, &control_width))
    356     return false;
    357 
    358   button_rect.left = message_rect.right;
    359   button_rect.right = button_rect.left + control_width + margin * 2;
    360   if (!SetWindowPos(hwnd_button, NULL,
    361                     button_rect.left, button_rect.top,
    362                     button_rect.right - button_rect.left,
    363                     button_rect.bottom - button_rect.top,
    364                     SWP_NOZORDER)) {
    365     return false;
    366   }
    367 
    368   // Resize the whole window to fit the resized controls.
    369   RECT window_rect;
    370   if (!GetWindowRect(hwnd_, &window_rect))
    371     return false;
    372   int width = button_rect.right + margin;
    373   int height = window_rect.bottom - window_rect.top;
    374   if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height,
    375                     SWP_NOMOVE | SWP_NOZORDER)) {
    376     return false;
    377   }
    378 
    379   // Make the corners of the disconnect window rounded.
    380   HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
    381                                 kWindowBorderRadius);
    382   if (!rgn)
    383     return false;
    384   if (!SetWindowRgn(hwnd_, rgn, TRUE))
    385     return false;
    386 
    387   return true;
    388 }
    389 
    390 } // namespace
    391 
    392 // static
    393 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
    394   return scoped_ptr<HostWindow>(new DisconnectWindowWin());
    395 }
    396 
    397 }  // namespace remoting
    398