Upstream version 10.39.233.0
[platform/framework/web/crosswalk.git] / src / remoting / host / disconnect_window_win.cc
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