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, base::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,
     95                          const base::string16& text,
     96                          LONG* width) {
     97   RECT rect = {0, 0, 0, 0};
     98   base::win::ScopedGetDC dc(control);
     99   base::win::ScopedSelectObject font(
    100       dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0));
    101   if (!DrawText(dc, text.c_str(), -1, &rect, DT_CALCRECT | DT_SINGLELINE))
    102     return false;
    103 
    104   *width = rect.right;
    105   return true;
    106 }
    107 
    108 DisconnectWindowWin::DisconnectWindowWin()
    109     : hwnd_(NULL),
    110       has_hotkey_(false),
    111       border_pen_(CreatePen(PS_SOLID, 5,
    112                             RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
    113 }
    114 
    115 DisconnectWindowWin::~DisconnectWindowWin() {
    116   EndDialog();
    117 }
    118 
    119 void DisconnectWindowWin::Start(
    120     const base::WeakPtr<ClientSessionControl>& client_session_control) {
    121   DCHECK(CalledOnValidThread());
    122   DCHECK(!client_session_control_);
    123   DCHECK(client_session_control);
    124 
    125   client_session_control_ = client_session_control;
    126 
    127   std::string client_jid = client_session_control_->client_jid();
    128   username_ = client_jid.substr(0, client_jid.find('/'));
    129   if (!BeginDialog())
    130     EndDialog();
    131 }
    132 
    133 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
    134                                                  UINT message,
    135                                                  WPARAM wparam,
    136                                                  LPARAM lparam) {
    137   LONG_PTR self = NULL;
    138   if (message == WM_INITDIALOG) {
    139     self = lparam;
    140 
    141     // Store |this| to the window's user data.
    142     SetLastError(ERROR_SUCCESS);
    143     LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self);
    144     if (result == 0 && GetLastError() != ERROR_SUCCESS)
    145       reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog();
    146   } else {
    147     self = GetWindowLongPtr(hwnd, DWLP_USER);
    148   }
    149 
    150   if (self) {
    151     return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
    152         hwnd, message, wparam, lparam);
    153   }
    154   return FALSE;
    155 }
    156 
    157 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
    158                                           UINT message,
    159                                           WPARAM wparam,
    160                                           LPARAM lparam) {
    161   DCHECK(CalledOnValidThread());
    162 
    163   switch (message) {
    164     // Ignore close messages.
    165     case WM_CLOSE:
    166       return TRUE;
    167 
    168     // Handle the Disconnect button.
    169     case WM_COMMAND:
    170       switch (LOWORD(wparam)) {
    171         case IDC_DISCONNECT:
    172           EndDialog();
    173           return TRUE;
    174       }
    175       return FALSE;
    176 
    177     // Ensure we don't try to use the HWND anymore.
    178     case WM_DESTROY:
    179       hwnd_ = NULL;
    180 
    181       // Ensure that the disconnect callback is invoked even if somehow our
    182       // window gets destroyed.
    183       EndDialog();
    184 
    185       return TRUE;
    186 
    187     // Ensure the dialog stays visible if the work area dimensions change.
    188     case WM_SETTINGCHANGE:
    189       if (wparam == SPI_SETWORKAREA)
    190         SetDialogPosition();
    191       return TRUE;
    192 
    193     // Ensure the dialog stays visible if the display dimensions change.
    194     case WM_DISPLAYCHANGE:
    195       SetDialogPosition();
    196       return TRUE;
    197 
    198     // Handle the disconnect hot-key.
    199     case WM_HOTKEY:
    200       EndDialog();
    201       return TRUE;
    202 
    203     // Let the window be draggable by its client area by responding
    204     // that the entire window is the title bar.
    205     case WM_NCHITTEST:
    206       SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION);
    207       return TRUE;
    208 
    209     case WM_PAINT: {
    210       PAINTSTRUCT ps;
    211       HDC hdc = BeginPaint(hwnd_, &ps);
    212       RECT rect;
    213       GetClientRect(hwnd_, &rect);
    214       {
    215         base::win::ScopedSelectObject border(hdc, border_pen_);
    216         base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH));
    217         RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1,
    218                   kWindowBorderRadius, kWindowBorderRadius);
    219       }
    220       EndPaint(hwnd_, &ps);
    221       return TRUE;
    222     }
    223   }
    224   return FALSE;
    225 }
    226 
    227 bool DisconnectWindowWin::BeginDialog() {
    228   DCHECK(CalledOnValidThread());
    229   DCHECK(!hwnd_);
    230 
    231   HMODULE module = base::GetModuleFromAddress(&DialogProc);
    232   hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), NULL,
    233                             DialogProc, reinterpret_cast<LPARAM>(this));
    234   if (!hwnd_)
    235     return false;
    236 
    237   // Set up handler for Ctrl-Alt-Esc shortcut.
    238   if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID,
    239                                      MOD_ALT | MOD_CONTROL, VK_ESCAPE)) {
    240     has_hotkey_ = true;
    241   }
    242 
    243   if (!SetStrings())
    244     return false;
    245 
    246   SetDialogPosition();
    247   ShowWindow(hwnd_, SW_SHOW);
    248   return IsWindowVisible(hwnd_) != FALSE;
    249 }
    250 
    251 void DisconnectWindowWin::EndDialog() {
    252   DCHECK(CalledOnValidThread());
    253 
    254   if (has_hotkey_) {
    255     UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
    256     has_hotkey_ = false;
    257   }
    258 
    259   if (hwnd_) {
    260     DestroyWindow(hwnd_);
    261     hwnd_ = NULL;
    262   }
    263 
    264   if (client_session_control_)
    265     client_session_control_->DisconnectSession();
    266 }
    267 
    268 // Returns |control| rectangle in the dialog coordinates.
    269 bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) {
    270   if (!GetWindowRect(control, rect))
    271     return false;
    272   SetLastError(ERROR_SUCCESS);
    273   int result = MapWindowPoints(HWND_DESKTOP, hwnd_,
    274                                reinterpret_cast<LPPOINT>(rect), 2);
    275   if (!result && GetLastError() != ERROR_SUCCESS)
    276     return false;
    277 
    278   return true;
    279 }
    280 
    281 void DisconnectWindowWin::SetDialogPosition() {
    282   DCHECK(CalledOnValidThread());
    283 
    284   // Try to center the window above the task-bar. If that fails, use the
    285   // primary monitor. If that fails (very unlikely), use the default position.
    286   HWND taskbar = FindWindow(kShellTrayWindowName, NULL);
    287   HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
    288   MONITORINFO monitor_info = {sizeof(monitor_info)};
    289   RECT window_rect;
    290   if (GetMonitorInfo(monitor, &monitor_info) &&
    291       GetWindowRect(hwnd_, &window_rect)) {
    292     int window_width = window_rect.right - window_rect.left;
    293     int window_height = window_rect.bottom - window_rect.top;
    294     int top = monitor_info.rcWork.bottom - window_height;
    295     int left = (monitor_info.rcWork.right + monitor_info.rcWork.left -
    296         window_width) / 2;
    297     SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
    298   }
    299 }
    300 
    301 bool DisconnectWindowWin::SetStrings() {
    302   DCHECK(CalledOnValidThread());
    303 
    304   // Localize the disconnect button text and measure length of the old and new
    305   // labels.
    306   HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
    307   HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
    308   if (!hwnd_button || !hwnd_message)
    309     return false;
    310 
    311   base::string16 button_text;
    312   base::string16 message_text;
    313   if (!GetControlText(hwnd_button, &button_text) ||
    314       !GetControlText(hwnd_message, &message_text)) {
    315     return false;
    316   }
    317 
    318   // Format and truncate "Your desktop is shared with ..." message.
    319   message_text = ReplaceStringPlaceholders(message_text,
    320                                            base::UTF8ToUTF16(username_),
    321                                            NULL);
    322   if (message_text.length() > kMaxSharingWithTextLength)
    323     message_text.erase(kMaxSharingWithTextLength);
    324 
    325   if (!SetWindowText(hwnd_message, message_text.c_str()))
    326     return false;
    327 
    328   // Calculate the margin between controls in pixels.
    329   RECT rect = {0};
    330   rect.right = kWindowTextMargin;
    331   if (!MapDialogRect(hwnd_, &rect))
    332     return false;
    333   int margin = rect.right;
    334 
    335   // Resize |hwnd_message| so that the text is not clipped.
    336   RECT message_rect;
    337   if (!GetControlRect(hwnd_message, &message_rect))
    338     return false;
    339 
    340   LONG control_width;
    341   if (!GetControlTextWidth(hwnd_message, message_text, &control_width))
    342     return false;
    343   message_rect.right = message_rect.left + control_width + margin;
    344 
    345   if (!SetWindowPos(hwnd_message, NULL,
    346                     message_rect.left, message_rect.top,
    347                     message_rect.right - message_rect.left,
    348                     message_rect.bottom - message_rect.top,
    349                     SWP_NOZORDER)) {
    350     return false;
    351   }
    352 
    353   // Reposition and resize |hwnd_button| as well.
    354   RECT button_rect;
    355   if (!GetControlRect(hwnd_button, &button_rect))
    356     return false;
    357 
    358   if (!GetControlTextWidth(hwnd_button, button_text, &control_width))
    359     return false;
    360 
    361   button_rect.left = message_rect.right;
    362   button_rect.right = button_rect.left + control_width + margin * 2;
    363   if (!SetWindowPos(hwnd_button, NULL,
    364                     button_rect.left, button_rect.top,
    365                     button_rect.right - button_rect.left,
    366                     button_rect.bottom - button_rect.top,
    367                     SWP_NOZORDER)) {
    368     return false;
    369   }
    370 
    371   // Resize the whole window to fit the resized controls.
    372   RECT window_rect;
    373   if (!GetWindowRect(hwnd_, &window_rect))
    374     return false;
    375   int width = button_rect.right + margin;
    376   int height = window_rect.bottom - window_rect.top;
    377   if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height,
    378                     SWP_NOMOVE | SWP_NOZORDER)) {
    379     return false;
    380   }
    381 
    382   // Make the corners of the disconnect window rounded.
    383   HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
    384                                 kWindowBorderRadius);
    385   if (!rgn)
    386     return false;
    387   if (!SetWindowRgn(hwnd_, rgn, TRUE))
    388     return false;
    389 
    390   return true;
    391 }
    392 
    393 } // namespace
    394 
    395 // static
    396 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
    397   return scoped_ptr<HostWindow>(new DisconnectWindowWin());
    398 }
    399 
    400 }  // namespace remoting
    401