123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <stddef.h>
- #include <windows.h>
- #include <cstdlib>
- #include <memory>
- #include "base/bind.h"
- #include "base/compiler_specific.h"
- #include "base/location.h"
- #include "base/logging.h"
- #include "base/memory/weak_ptr.h"
- #include "base/strings/string_util.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/time/time.h"
- #include "base/timer/timer.h"
- #include "base/win/current_module.h"
- #include "base/win/scoped_gdi_object.h"
- #include "base/win/scoped_hdc.h"
- #include "base/win/scoped_select_object.h"
- #include "remoting/host/client_session_control.h"
- #include "remoting/host/host_window.h"
- #include "remoting/host/input_monitor/local_input_monitor.h"
- #include "remoting/host/win/core_resource.h"
- #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
- #include "ui/events/event.h"
- namespace remoting {
- namespace {
- constexpr int DISCONNECT_HOTKEY_ID = 1000;
- // Maximum length of "Your desktop is shared with ..." message in UTF-16
- // characters.
- constexpr size_t kMaxSharingWithTextLength = 100;
- constexpr wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
- constexpr int kWindowBorderRadius = 14;
- // Margin between dialog controls (in dialog units).
- constexpr int kWindowTextMargin = 8;
- // The amount of time to wait before hiding the disconnect window.
- constexpr base::TimeDelta kAutoHideTimeout = base::Seconds(10);
- // The length of the hide and show animations.
- constexpr DWORD kAnimationDurationMs = 200;
- class DisconnectWindowWin : public HostWindow {
- public:
- DisconnectWindowWin();
- DisconnectWindowWin(const DisconnectWindowWin&) = delete;
- DisconnectWindowWin& operator=(const DisconnectWindowWin&) = delete;
- ~DisconnectWindowWin() override;
- // Allow dialog to auto-hide after a period of time. The dialog will be
- // reshown when local user input is detected.
- void EnableAutoHide(std::unique_ptr<LocalInputMonitor> local_input_monitor);
- // HostWindow overrides.
- void Start(
- const base::WeakPtr<ClientSessionControl>& client_session_control)
- override;
- private:
- static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
- LPARAM lparam);
- BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
- // Creates the dialog window and registers the disconnect hot key.
- bool BeginDialog();
- // Closes the dialog, unregisters the hot key and invokes the disconnect
- // callback, if set.
- void EndDialog();
- // Returns |control| rectangle in the dialog coordinates.
- bool GetControlRect(HWND control, RECT* rect);
- // Positions the dialog window based on the current auto-hide state.
- // If auto-hide is enabled, the window is displayed near the center of the
- // display, otherwise it is displayed just above the taskbar.
- void SetDialogPosition();
- // Applies localization string and resizes the dialog.
- bool SetStrings();
- // Draws the border around the dialog window. Can be used to draw the initial
- // border or to redraw if when the dialog is reshown. |hwnd| is the window to
- // have the border applied. |hdc| is the device context to draw to.
- void DrawBorder(HWND hwnd, HDC hdc);
- // Shows a previously hidden dialog using an animation.
- void ShowDialog();
- // Hides the dialog using an animation.
- void HideDialog();
- // Prevent the dialog from being hidden if local input monitoring fails.
- void StopAutoHideBehavior();
- // Called when local mouse event is seen and shows the dialog (if hidden).
- void OnLocalMouseEvent(const webrtc::DesktopVector& mouse_position,
- ui::EventType type);
- // Called when local keyboard event is seen and shows the dialog (if hidden).
- void OnLocalKeyPressed(uint32_t usb_keycode);
- // Used to disconnect the client session.
- base::WeakPtr<ClientSessionControl> client_session_control_;
- // Used to watch for local input which will trigger the dialog to be reshown.
- std::unique_ptr<LocalInputMonitor> local_input_monitor_;
- // Specifies the remote user name.
- std::string username_;
- bool was_auto_hidden_ = false;
- bool local_input_seen_ = false;
- base::OneShotTimer auto_hide_timer_;
- HWND hwnd_ = nullptr;
- bool has_hotkey_ = false;
- base::win::ScopedGDIObject<HPEN> border_pen_;
- webrtc::DesktopVector mouse_position_;
- base::WeakPtrFactory<DisconnectWindowWin> weak_factory_{this};
- };
- // Returns the text for the given dialog control window.
- bool GetControlText(HWND control, std::wstring* text) {
- // GetWindowText truncates the text if it is longer than can fit into
- // the buffer.
- WCHAR buffer[256];
- int result = GetWindowText(control, buffer, std::size(buffer));
- if (!result)
- return false;
- text->assign(buffer);
- return true;
- }
- // Returns width |text| rendered in |control| window.
- bool GetControlTextWidth(HWND control, const std::wstring& text, LONG* width) {
- RECT rect = {0, 0, 0, 0};
- base::win::ScopedGetDC dc(control);
- base::win::ScopedSelectObject font(
- dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0));
- if (!DrawText(dc, text.c_str(), -1, &rect, DT_CALCRECT | DT_SINGLELINE))
- return false;
- *width = rect.right;
- return true;
- }
- DisconnectWindowWin::DisconnectWindowWin()
- : border_pen_(
- CreatePen(PS_SOLID, 5, RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {}
- DisconnectWindowWin::~DisconnectWindowWin() {
- EndDialog();
- }
- void DisconnectWindowWin::EnableAutoHide(
- std::unique_ptr<LocalInputMonitor> local_input_monitor) {
- local_input_monitor_ = std::move(local_input_monitor);
- }
- void DisconnectWindowWin::Start(
- const base::WeakPtr<ClientSessionControl>& client_session_control) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(!client_session_control_);
- DCHECK(client_session_control);
- client_session_control_ = client_session_control;
- std::string client_jid = client_session_control_->client_jid();
- username_ = client_jid.substr(0, client_jid.find('/'));
- if (!BeginDialog()) {
- EndDialog();
- return;
- }
- if (local_input_monitor_) {
- local_input_monitor_->StartMonitoring(
- base::BindRepeating(&DisconnectWindowWin::OnLocalMouseEvent,
- weak_factory_.GetWeakPtr()),
- base::BindRepeating(&DisconnectWindowWin::OnLocalKeyPressed,
- weak_factory_.GetWeakPtr()),
- base::BindRepeating(&DisconnectWindowWin::StopAutoHideBehavior,
- weak_factory_.GetWeakPtr()));
- auto_hide_timer_.Start(FROM_HERE, kAutoHideTimeout,
- base::BindOnce(&DisconnectWindowWin::HideDialog,
- base::Unretained(this)));
- }
- }
- INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
- UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- LONG_PTR self = 0;
- if (message == WM_INITDIALOG) {
- self = lparam;
- // Store |this| to the window's user data.
- SetLastError(ERROR_SUCCESS);
- LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self);
- if (result == 0 && GetLastError() != ERROR_SUCCESS)
- reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog();
- } else {
- self = GetWindowLongPtr(hwnd, DWLP_USER);
- }
- if (self) {
- return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
- hwnd, message, wparam, lparam);
- }
- return FALSE;
- }
- BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
- UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- switch (message) {
- // Ignore close messages.
- case WM_CLOSE:
- return TRUE;
- // Handle the Disconnect button.
- case WM_COMMAND:
- switch (LOWORD(wparam)) {
- case IDC_DISCONNECT:
- EndDialog();
- return TRUE;
- }
- return FALSE;
- // Ensure we don't try to use the HWND anymore.
- case WM_DESTROY:
- hwnd_ = nullptr;
- // Ensure that the disconnect callback is invoked even if somehow our
- // window gets destroyed.
- EndDialog();
- return TRUE;
- // Ensure the dialog stays visible if the work area dimensions change.
- case WM_SETTINGCHANGE:
- if (wparam == SPI_SETWORKAREA)
- SetDialogPosition();
- return TRUE;
- // Ensure the dialog stays visible if the display dimensions change.
- case WM_DISPLAYCHANGE:
- SetDialogPosition();
- return TRUE;
- // Handle the disconnect hot-key.
- case WM_HOTKEY:
- EndDialog();
- return TRUE;
- // Let the window be draggable by its client area by responding
- // that the entire window is the title bar.
- case WM_NCHITTEST:
- SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION);
- return TRUE;
- case WM_PAINT: {
- // Draw the client area after ShowWindow is used to make |hwnd_| visible.
- PAINTSTRUCT ps;
- HDC hdc = BeginPaint(hwnd_, &ps);
- DrawBorder(hwnd_, hdc);
- EndPaint(hwnd_, &ps);
- return TRUE;
- }
- case WM_PRINTCLIENT: {
- // Refresh the dialog client area. Called after AnimateWindow is used to
- // reshow the dialog.
- HDC hdc = reinterpret_cast<HDC>(wparam);
- DrawBorder(hwnd_, hdc);
- return TRUE;
- }
- }
- return FALSE;
- }
- bool DisconnectWindowWin::BeginDialog() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(!hwnd_);
- hwnd_ =
- CreateDialogParam(CURRENT_MODULE(), MAKEINTRESOURCE(IDD_DISCONNECT),
- nullptr, DialogProc, reinterpret_cast<LPARAM>(this));
- if (!hwnd_)
- return false;
- // Set up handler for Ctrl-Alt-Esc shortcut.
- if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID,
- MOD_ALT | MOD_CONTROL, VK_ESCAPE)) {
- has_hotkey_ = true;
- }
- if (!SetStrings())
- return false;
- SetDialogPosition();
- ShowWindow(hwnd_, SW_SHOW);
- return IsWindowVisible(hwnd_) != FALSE;
- }
- void DisconnectWindowWin::EndDialog() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (has_hotkey_) {
- UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
- has_hotkey_ = false;
- }
- if (hwnd_) {
- DestroyWindow(hwnd_);
- hwnd_ = nullptr;
- }
- // Disable auto-hide events since the window has been destroyed.
- auto_hide_timer_.Stop();
- if (client_session_control_)
- client_session_control_->DisconnectSession(protocol::OK);
- }
- void DisconnectWindowWin::ShowDialog() {
- // Always reset the hide timer when this method is called.
- if (local_input_monitor_) {
- auto_hide_timer_.Start(FROM_HERE, kAutoHideTimeout,
- base::BindOnce(&DisconnectWindowWin::HideDialog,
- base::Unretained(this)));
- }
- if (!was_auto_hidden_)
- return;
- // Make sure the dialog is fully visible when it is reshown.
- if (!local_input_seen_)
- SetDialogPosition();
- if (!AnimateWindow(hwnd_, kAnimationDurationMs, AW_BLEND)) {
- PLOG(ERROR) << "AnimateWindow() failed to show dialog: ";
- ShowWindow(hwnd_, SW_SHOW);
- // If the window still isn't visible, then disconnect the session.
- if (!IsWindowVisible(hwnd_))
- client_session_control_->DisconnectSession(protocol::OK);
- }
- was_auto_hidden_ = false;
- }
- void DisconnectWindowWin::HideDialog() {
- if (was_auto_hidden_ || !local_input_monitor_ || !hwnd_)
- return;
- if (!AnimateWindow(hwnd_, kAnimationDurationMs, AW_BLEND | AW_HIDE))
- PLOG(ERROR) << "AnimateWindow() failed to hide dialog: ";
- else
- was_auto_hidden_ = true;
- }
- void DisconnectWindowWin::StopAutoHideBehavior() {
- auto_hide_timer_.Stop();
- local_input_monitor_.reset();
- ShowDialog();
- }
- void DisconnectWindowWin::OnLocalMouseEvent(
- const webrtc::DesktopVector& position,
- ui::EventType type) {
- // Don't show the dialog if the position changes by ~1px in any direction.
- // This will prevent the dialog from being reshown due to small movements
- // caused by hardware/software issues which cause cursor drift or small
- // vibrations in the environment around the remote host.
- if (std::abs(position.x() - mouse_position_.x()) > 1 ||
- std::abs(position.y() - mouse_position_.y()) > 1) {
- // Show the dialog before setting |local_input_seen_|. That way the dialog
- // will be shown in the center position and subsequent reshows will honor
- // the new position (if any) the dialog is moved to.
- ShowDialog();
- local_input_seen_ = true;
- }
- mouse_position_ = position;
- }
- void DisconnectWindowWin::OnLocalKeyPressed(uint32_t usb_keycode) {
- // Show the dialog before setting |local_input_seen_|. That way the dialog
- // will be shown in the center position and subsequent reshows will honor
- // the new position (if any) the dialog is moved to.
- ShowDialog();
- local_input_seen_ = true;
- }
- void DisconnectWindowWin::DrawBorder(HWND hwnd, HDC hdc) {
- RECT rect;
- GetClientRect(hwnd, &rect);
- base::win::ScopedSelectObject border(hdc, border_pen_.get());
- base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH));
- RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1,
- kWindowBorderRadius, kWindowBorderRadius);
- }
- // Returns |control| rectangle in the dialog coordinates.
- bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) {
- if (!GetWindowRect(control, rect))
- return false;
- SetLastError(ERROR_SUCCESS);
- int result = MapWindowPoints(HWND_DESKTOP, hwnd_,
- reinterpret_cast<LPPOINT>(rect), 2);
- if (!result && GetLastError() != ERROR_SUCCESS)
- return false;
- return true;
- }
- void DisconnectWindowWin::SetDialogPosition() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // Try to center the window above the task-bar. If that fails, use the
- // primary monitor. If that fails (very unlikely), use the default position.
- HWND taskbar = FindWindow(kShellTrayWindowName, nullptr);
- HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
- MONITORINFO monitor_info = {sizeof(monitor_info)};
- RECT window_rect;
- if (!GetMonitorInfo(monitor, &monitor_info) ||
- !GetWindowRect(hwnd_, &window_rect)) {
- return;
- }
- int window_width = window_rect.right - window_rect.left;
- int window_height = window_rect.bottom - window_rect.top;
- // Default settings will display the window above the taskbar and centered
- // along the x axis.
- int top = monitor_info.rcWork.bottom - window_height;
- int left =
- (monitor_info.rcWork.right + monitor_info.rcWork.left - window_width) / 2;
- // Adjust the top value if the window is in auto-hide mode and we have not
- // seen local input yet. We adjust the position to make the dialog a bit more
- // obtrusive so that a local user will notice it before it auto-hides.
- if (local_input_monitor_ && !local_input_seen_)
- top = top * 0.7;
- SetWindowPos(hwnd_, nullptr, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
- }
- bool DisconnectWindowWin::SetStrings() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // Localize the disconnect button text and measure length of the old and new
- // labels.
- HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
- HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
- if (!hwnd_button || !hwnd_message)
- return false;
- std::wstring button_text;
- std::wstring message_text;
- if (!GetControlText(hwnd_button, &button_text) ||
- !GetControlText(hwnd_message, &message_text)) {
- return false;
- }
- // Format and truncate "Your desktop is shared with ..." message.
- message_text = base::AsWString(base::ReplaceStringPlaceholders(
- base::AsString16(message_text), base::UTF8ToUTF16(username_), nullptr));
- if (message_text.length() > kMaxSharingWithTextLength)
- message_text.erase(kMaxSharingWithTextLength);
- if (!SetWindowText(hwnd_message, message_text.c_str()))
- return false;
- // Calculate the margin between controls in pixels.
- RECT rect = {0};
- rect.right = kWindowTextMargin;
- if (!MapDialogRect(hwnd_, &rect))
- return false;
- int margin = rect.right;
- // Resize |hwnd_message| so that the text is not clipped.
- RECT message_rect;
- if (!GetControlRect(hwnd_message, &message_rect))
- return false;
- LONG control_width;
- if (!GetControlTextWidth(hwnd_message, message_text, &control_width))
- return false;
- message_rect.right = message_rect.left + control_width + margin;
- if (!SetWindowPos(hwnd_message, nullptr,
- message_rect.left, message_rect.top,
- message_rect.right - message_rect.left,
- message_rect.bottom - message_rect.top,
- SWP_NOZORDER)) {
- return false;
- }
- // Reposition and resize |hwnd_button| as well.
- RECT button_rect;
- if (!GetControlRect(hwnd_button, &button_rect))
- return false;
- if (!GetControlTextWidth(hwnd_button, button_text, &control_width))
- return false;
- button_rect.left = message_rect.right;
- button_rect.right = button_rect.left + control_width + margin * 2;
- if (!SetWindowPos(hwnd_button, nullptr,
- button_rect.left, button_rect.top,
- button_rect.right - button_rect.left,
- button_rect.bottom - button_rect.top,
- SWP_NOZORDER)) {
- return false;
- }
- // Resize the whole window to fit the resized controls.
- RECT window_rect;
- if (!GetWindowRect(hwnd_, &window_rect))
- return false;
- int width = button_rect.right + margin;
- int height = window_rect.bottom - window_rect.top;
- if (!SetWindowPos(hwnd_, nullptr, 0, 0, width, height,
- SWP_NOMOVE | SWP_NOZORDER)) {
- return false;
- }
- // Make the corners of the disconnect window rounded.
- HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
- kWindowBorderRadius);
- if (!rgn)
- return false;
- if (!SetWindowRgn(hwnd_, rgn, TRUE))
- return false;
- return true;
- }
- } // namespace
- // static
- std::unique_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
- return std::make_unique<DisconnectWindowWin>();
- }
- std::unique_ptr<HostWindow> HostWindow::CreateAutoHidingDisconnectWindow(
- std::unique_ptr<LocalInputMonitor> local_input_monitor) {
- auto disconnect_window = std::make_unique<DisconnectWindowWin>();
- disconnect_window->EnableAutoHide(std::move(local_input_monitor));
- return disconnect_window;
- }
- } // namespace remoting
|