[Chromoting] Fix mouse when single display selected in multi-mon

Previous code would always translate the mouse coords relative to the
upper-left corner of the entire display, instead of relative to the
currently selected display.

This cl changes the mouse input scaler to be a DesktopRect instead of
a DesktopSize because we need to keep track of the origin for proper
mouse event scaling.

Change-Id: I397c51280187c7a8e023e8a41940b6f37b39eb7f
Reviewed-on: https://chromium-review.googlesource.com/c/1429339
Commit-Queue: Gary Kacmarcik <garykac@chromium.org>
Reviewed-by: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625325}
diff --git a/remoting/client/chromoting_client.cc b/remoting/client/chromoting_client.cc
index 9e93163..30ff69b 100644
--- a/remoting/client/chromoting_client.cc
+++ b/remoting/client/chromoting_client.cc
@@ -184,11 +184,12 @@
   user_interface_->SetDesktopSize(size_pixels,
                                   webrtc::DesktopVector(x_dpi, y_dpi));
 
-  mouse_input_scaler_.set_input_size(size_pixels);
+  mouse_input_scaler_.set_input_size(
+      webrtc::DesktopRect::MakeSize(size_pixels));
   mouse_input_scaler_.set_output_size(
       connection_->config().protocol() == protocol::SessionConfig::Protocol::ICE
-          ? size_pixels
-          : size_dips);
+          ? webrtc::DesktopRect::MakeSize(size_pixels)
+          : webrtc::DesktopRect::MakeSize(size_dips));
 }
 
 void ChromotingClient::InjectClipboardEvent(
diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc
index f454ab5..59a5390 100644
--- a/remoting/client/plugin/chromoting_instance.cc
+++ b/remoting/client/plugin/chromoting_instance.cc
@@ -326,7 +326,7 @@
   plugin_view_ = view;
   webrtc::DesktopSize size(
       webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
-  mouse_input_filter_.set_input_size(size);
+  mouse_input_filter_.set_input_size(webrtc::DesktopRect::MakeSize(size));
   touch_input_scaler_.set_input_size(size);
 
   if (video_renderer_)
@@ -503,7 +503,7 @@
                                         const webrtc::DesktopVector& dpi) {
   DCHECK(!dpi.is_zero());
 
-  mouse_input_filter_.set_output_size(size);
+  mouse_input_filter_.set_output_size(webrtc::DesktopRect::MakeSize(size));
   touch_input_scaler_.set_output_size(size);
 
   std::unique_ptr<base::DictionaryValue> data(new base::DictionaryValue());
@@ -738,7 +738,7 @@
   if (!plugin_view_.is_null()) {
     webrtc::DesktopSize size(plugin_view_.GetRect().width(),
                              plugin_view_.GetRect().height());
-    mouse_input_filter_.set_input_size(size);
+    mouse_input_filter_.set_input_size(webrtc::DesktopRect::MakeSize(size));
     touch_input_scaler_.set_input_size(size);
   }
 
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index 4e921d36..7c2912e 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -246,8 +246,13 @@
       // Default to fullscreen if unable to parse id.
       id = webrtc::kFullDesktopScreenId;
     }
+    // Invalid display index defaults to showing all displays.
+    if (!desktop_display_info_.GetDisplayInfo(id)) {
+      id = webrtc::kFullDesktopScreenId;
+    }
   }
   video_stream_->SelectSource(id);
+  show_display_id_ = id;
 }
 
 void ClientSession::OnConnectionAuthenticating() {
@@ -264,6 +269,8 @@
 
   is_authenticated_ = true;
 
+  desktop_display_info_.Reset();
+
   if (max_duration_ > base::TimeDelta()) {
     max_duration_timer_.Start(
         FROM_HERE, max_duration_,
@@ -481,21 +488,31 @@
                                        const webrtc::DesktopSize& size,
                                        const webrtc::DesktopVector& dpi) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  mouse_clamping_filter_.set_output_size(size);
+  webrtc::DesktopVector origin;
+  if (show_display_id_ != webrtc::kFullDesktopScreenId) {
+    const DisplayGeometry* display_info =
+        desktop_display_info_.GetDisplayInfo(show_display_id_);
+    if (display_info) {
+      origin.set(display_info->x, display_info->y);
+    }
+  }
+  mouse_clamping_filter_.set_output_size(
+      webrtc::DesktopRect::MakeOriginSize(origin, size));
 
   switch (connection_->session()->config().protocol()) {
     case protocol::SessionConfig::Protocol::ICE:
-      mouse_clamping_filter_.set_input_size(size);
+      mouse_clamping_filter_.set_input_size(
+          webrtc::DesktopRect::MakeSize(size));
       break;
 
     case protocol::SessionConfig::Protocol::WEBRTC: {
       // When using WebRTC protocol the client sends mouse coordinates in DIPs,
       // while InputInjector expects them in physical pixels.
       // TODO(sergeyu): Fix InputInjector implementations to use DIPs as well.
-      webrtc::DesktopSize size_dips(size.width() * kDefaultDpi / dpi.x(),
-                                    size.height() * kDefaultDpi / dpi.y());
-      mouse_clamping_filter_.set_input_size(size_dips);
+      webrtc::DesktopSize size_dips =
+          DesktopDisplayInfo::CalcSizeDips(size, dpi.x(), dpi.y());
+      mouse_clamping_filter_.set_input_size(
+          webrtc::DesktopRect::MakeSize(size_dips));
 
       // Generate and send VideoLayout message.
       protocol::VideoLayout layout;
@@ -547,10 +564,9 @@
 
   // Calc desktop scaled geometry (in DIPs)
   // See comment in OnVideoSizeChanged() for details.
-  int scaled_width = (max_x - min_x) * kDefaultDpi / dpi_x;
-  int scaled_height = (max_y - min_y) * kDefaultDpi / dpi_y;
-  webrtc::DesktopSize size_dips(scaled_width, scaled_height);
-  mouse_clamping_filter_.set_input_size(size_dips);
+  const webrtc::DesktopSize size(max_x - min_x, max_y - min_y);
+  webrtc::DesktopSize size_dips =
+      DesktopDisplayInfo::CalcSizeDips(size, dpi_x, dpi_y);
 
   // Generate and send VideoLayout message.
   protocol::VideoLayout layout;
@@ -563,8 +579,8 @@
   video_track = layout.add_video_track();
   video_track->set_position_x(0);
   video_track->set_position_y(0);
-  video_track->set_width(scaled_width);
-  video_track->set_height(scaled_height);
+  video_track->set_width(size_dips.width());
+  video_track->set_height(size_dips.height());
   video_track->set_x_dpi(dpi_x);
   video_track->set_y_dpi(dpi_y);
 
@@ -578,10 +594,14 @@
   video_track->set_y_dpi(dpi_y);
 
   // Add a VideoTrackLayout entry for each separate display.
+  desktop_display_info_.Reset();
   for (int display_id = 0; display_id < displays->video_track_size();
        display_id++) {
+    protocol::VideoTrackLayout display = displays->video_track(display_id);
+    desktop_display_info_.AddDisplayFrom(display);
+
     protocol::VideoTrackLayout* video_track = layout.add_video_track();
-    video_track->CopyFrom(displays->video_track(display_id));
+    video_track->CopyFrom(display);
   }
 
   connection_->client_stub()->SetVideoLayout(layout);
diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h
index 283bb463..34f8aa2 100644
--- a/remoting/host/client_session.h
+++ b/remoting/host/client_session.h
@@ -18,6 +18,7 @@
 #include "base/timer/timer.h"
 #include "remoting/host/client_session_control.h"
 #include "remoting/host/client_session_details.h"
+#include "remoting/host/desktop_display_info.h"
 #include "remoting/host/desktop_environment_options.h"
 #include "remoting/host/host_experiment_session_plugin.h"
 #include "remoting/host/host_extension_session_manager.h"
@@ -35,6 +36,7 @@
 #include "remoting/protocol/mouse_input_filter.h"
 #include "remoting/protocol/pairing_registry.h"
 #include "remoting/protocol/video_stream.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 
 namespace remoting {
@@ -245,6 +247,13 @@
   // Used to apply client-requested changes in screen resolution.
   std::unique_ptr<ScreenControls> screen_controls_;
 
+  // Contains the most recently gathered info about the desktop displays;
+  DesktopDisplayInfo desktop_display_info_;
+
+  // The id of the desktop display to show to the user.
+  // Default is webrtc::kFullDesktopScreenId which shows all displays.
+  webrtc::ScreenId show_display_id_ = webrtc::kFullDesktopScreenId;
+
   // The pairing registry for PIN-less authentication.
   scoped_refptr<protocol::PairingRegistry> pairing_registry_;
 
diff --git a/remoting/host/client_session_unittest.cc b/remoting/host/client_session_unittest.cc
index 4910da9..e8e6866 100644
--- a/remoting/host/client_session_unittest.cc
+++ b/remoting/host/client_session_unittest.cc
@@ -107,6 +107,15 @@
   void TearDown() override;
 
  protected:
+  // Fake multi-monitor setup.
+  static const int kDisplay1Width =
+      protocol::FakeDesktopCapturer::kWidth;  // 800
+  static const int kDisplay1Height =
+      protocol::FakeDesktopCapturer::kHeight;  // 600
+  static const int kDisplay2Width = 1024;
+  static const int kDisplay2Height = 768;
+  static const int kDisplay2YOffset = 35;
+
   // Creates the client session from a FakeSession instance.
   void CreateClientSession(std::unique_ptr<protocol::FakeSession> session);
 
@@ -119,7 +128,34 @@
   void ConnectClientSession();
 
   // Fakes video size notification from the VideoStream.
-  void NotifyVideoSize();
+  void NotifyVideoSize(int width, int height, int dpi_x, int dpi_y);
+
+  // Add a fake display to the layout list. Used in conjunction with
+  // NotifyDesktopDisplaySize.
+  void AddDisplayToLayout(protocol::VideoLayout* displays,
+                          int x,
+                          int y,
+                          int width,
+                          int height,
+                          int dpi_x,
+                          int dpi_y);
+
+  // Fakes desktop display size notification from Webrtc.
+  void NotifyDesktopDisplaySize(
+      std::unique_ptr<protocol::VideoLayout> displays);
+
+  // Fakes display select request from user.
+  void NotifySelectDesktopDisplay(std::string id);
+
+  // Convenience methods to setup a single- or double-monitor setup.
+  void SetupSingleDisplay();
+  void SetupMultiDisplay();
+
+  // When using a multi-mon setup, this fakes the user selecting which displays
+  // to show.
+  void MultiMon_SelectFirstDisplay();
+  void MultiMon_SelectSecondDisplay();
+  void MultiMon_SelectAllDisplays();
 
   // Message loop that will process all ClientSession tasks.
   base::MessageLoop message_loop_;
@@ -211,18 +247,142 @@
   client_session_->OnConnectionChannelsConnected();
 }
 
-void ClientSessionTest::NotifyVideoSize() {
+void ClientSessionTest::NotifyVideoSize(int width,
+                                        int height,
+                                        int dpi_x,
+                                        int dpi_y) {
   connection_->last_video_stream()->observer()->OnVideoSizeChanged(
       connection_->last_video_stream().get(),
-      webrtc::DesktopSize(protocol::FakeDesktopCapturer::kWidth,
-                          protocol::FakeDesktopCapturer::kHeight),
-      webrtc::DesktopVector(kDefaultDpi, kDefaultDpi));
+      webrtc::DesktopSize(width, height), webrtc::DesktopVector(dpi_x, dpi_y));
+}
+
+void ClientSessionTest::AddDisplayToLayout(protocol::VideoLayout* displays,
+                                           int x,
+                                           int y,
+                                           int width,
+                                           int height,
+                                           int dpi_x,
+                                           int dpi_y) {
+  protocol::VideoTrackLayout* video_track = displays->add_video_track();
+  video_track->set_position_x(x);
+  video_track->set_position_y(y);
+  video_track->set_width(width);
+  video_track->set_height(height);
+  video_track->set_x_dpi(dpi_x);
+  video_track->set_y_dpi(dpi_y);
+}
+
+void ClientSessionTest::NotifyDesktopDisplaySize(
+    std::unique_ptr<protocol::VideoLayout> displays) {
+  client_session_->OnDesktopDisplayChanged(std::move(displays));
+}
+
+void ClientSessionTest::NotifySelectDesktopDisplay(std::string id) {
+  protocol::SelectDesktopDisplayRequest req;
+  req.set_id(id);
+  client_session_->SelectDesktopDisplay(req);
+}
+
+// Set up a single display (default size).
+void ClientSessionTest::SetupSingleDisplay() {
+  NotifyVideoSize(kDisplay1Width, kDisplay1Height, kDefaultDpi, kDefaultDpi);
+  auto displays = std::make_unique<protocol::VideoLayout>();
+  AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height,
+                     kDefaultDpi, kDefaultDpi);
+  NotifyDesktopDisplaySize(std::move(displays));
+}
+
+// Set up multiple displays:
+// +-----------+
+// |  800x600  |---------------+
+// |     0     |   1024x768    |
+// +-----------+       1       |
+//             |               |
+//             +---------------+
+void ClientSessionTest::SetupMultiDisplay() {
+  NotifyVideoSize(kDisplay1Width + kDisplay2Width, kDisplay2Height, kDefaultDpi,
+                  kDefaultDpi);
+  auto displays = std::make_unique<protocol::VideoLayout>();
+  AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height,
+                     kDefaultDpi, kDefaultDpi);
+  AddDisplayToLayout(displays.get(), kDisplay1Width, kDisplay2YOffset,
+                     kDisplay2Width, kDisplay2Height, kDefaultDpi, kDefaultDpi);
+  NotifyDesktopDisplaySize(std::move(displays));
+}
+
+void ClientSessionTest::MultiMon_SelectFirstDisplay() {
+  NotifySelectDesktopDisplay("0");
+  NotifyVideoSize(kDisplay1Width, kDisplay1Height, kDefaultDpi, kDefaultDpi);
+}
+
+void ClientSessionTest::MultiMon_SelectSecondDisplay() {
+  NotifySelectDesktopDisplay("1");
+  NotifyVideoSize(kDisplay2Width, kDisplay2Height, kDefaultDpi, kDefaultDpi);
+}
+
+void ClientSessionTest::MultiMon_SelectAllDisplays() {
+  NotifySelectDesktopDisplay("all");
+  NotifyVideoSize(kDisplay1Width + kDisplay2Width, kDisplay2Height, kDefaultDpi,
+                  kDefaultDpi);
+}
+
+TEST_F(ClientSessionTest, MultiMonMouseMove) {
+  CreateClientSession();
+  ConnectClientSession();
+  SetupMultiDisplay();
+
+  FakeInputInjector* input_injector =
+      desktop_environment_factory_->last_desktop_environment()
+          ->last_input_injector()
+          .get();
+  std::vector<protocol::MouseEvent> mouse_events;
+  input_injector->set_mouse_events(&mouse_events);
+
+  // These mouse events are in global (full desktop) coordinates.
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(70, 50));
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(1000, 650));
+
+  // Select second display: origin: 800,35 ; size: 1024x768
+  MultiMon_SelectSecondDisplay();
+  // This mouse event is injected relative to the second display.
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(1005, 625));
+  // Events should clamp to the selected display.
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(2000, 700));
+
+  // Select first display: origin: 0,0 ; size: 800x600
+  MultiMon_SelectFirstDisplay();
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(80, 60));
+  // Events should clamp to the selected display (800,600).
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(1000, 640));
+
+  // Select entire desktop again: origin: 0,0 ; size: 1824x768
+  MultiMon_SelectAllDisplays();
+  // Events should clamp to the entire desktop (800+1024, 768).
+  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(2000, 1000));
+
+  client_session_->DisconnectSession(protocol::OK);
+  client_session_.reset();
+
+  EXPECT_EQ(7U, mouse_events.size());
+  EXPECT_THAT(mouse_events[0], EqualsMouseMoveEvent(70, 50));
+  EXPECT_THAT(mouse_events[1], EqualsMouseMoveEvent(1000, 650));
+  EXPECT_THAT(mouse_events[2], EqualsMouseMoveEvent(1005 + kDisplay1Width,
+                                                    625 + kDisplay2YOffset));
+  EXPECT_THAT(mouse_events[3],
+              EqualsMouseMoveEvent(kDisplay1Width + kDisplay2Width - 1,
+                                   700 + kDisplay2YOffset));
+  EXPECT_THAT(mouse_events[4], EqualsMouseMoveEvent(80, 60));
+  EXPECT_THAT(mouse_events[5],
+              EqualsMouseMoveEvent(kDisplay1Width - 1, kDisplay1Height - 1));
+  EXPECT_THAT(mouse_events[6],
+              EqualsMouseMoveEvent(kDisplay1Width + kDisplay2Width - 1,
+                                   kDisplay2Height - 1));
 }
 
 TEST_F(ClientSessionTest, DisableInputs) {
   CreateClientSession();
   ConnectClientSession();
-  NotifyVideoSize();
+  SetupSingleDisplay();
 
   FakeInputInjector* input_injector =
       desktop_environment_factory_->last_desktop_environment()
@@ -278,8 +438,7 @@
 TEST_F(ClientSessionTest, LocalInputTest) {
   CreateClientSession();
   ConnectClientSession();
-  NotifyVideoSize();
-
+  SetupSingleDisplay();
 
   std::vector<protocol::MouseEvent> mouse_events;
   desktop_environment_factory_->last_desktop_environment()
@@ -314,7 +473,7 @@
 TEST_F(ClientSessionTest, RestoreEventState) {
   CreateClientSession();
   ConnectClientSession();
-  NotifyVideoSize();
+  SetupSingleDisplay();
 
   FakeInputInjector* input_injector =
       desktop_environment_factory_->last_desktop_environment()
@@ -352,7 +511,7 @@
 TEST_F(ClientSessionTest, ClampMouseEvents) {
   CreateClientSession();
   ConnectClientSession();
-  NotifyVideoSize();
+  SetupSingleDisplay();
 
   std::vector<protocol::MouseEvent> mouse_events;
   desktop_environment_factory_->last_desktop_environment()
diff --git a/remoting/host/desktop_display_info.cc b/remoting/host/desktop_display_info.cc
index 6b184be7..2529cee 100644
--- a/remoting/host/desktop_display_info.cc
+++ b/remoting/host/desktop_display_info.cc
@@ -5,6 +5,7 @@
 #include "remoting/host/desktop_display_info.h"
 
 #include "build/build_config.h"
+#include "remoting/base/constants.h"
 
 #if defined(OS_WIN)
 #include <windows.h>
@@ -40,6 +41,45 @@
   return !(*this == other);
 }
 
+/* static */
+webrtc::DesktopSize DesktopDisplayInfo::CalcSizeDips(webrtc::DesktopSize size,
+                                                     int dpi_x,
+                                                     int dpi_y) {
+  webrtc::DesktopSize size_dips(size.width() * kDefaultDpi / dpi_x,
+                                size.height() * kDefaultDpi / dpi_y);
+  return size_dips;
+}
+
+void DesktopDisplayInfo::Reset() {
+  displays_.clear();
+}
+
+int DesktopDisplayInfo::NumDisplays() {
+  return displays_.size();
+}
+
+const DisplayGeometry* DesktopDisplayInfo::GetDisplayInfo(unsigned int id) {
+  if (id >= displays_.size())
+    return nullptr;
+  return &displays_[id];
+}
+
+void DesktopDisplayInfo::AddDisplay(DisplayGeometry* display) {
+  displays_.push_back(*display);
+}
+
+void DesktopDisplayInfo::AddDisplayFrom(protocol::VideoTrackLayout track) {
+  auto* display = new DisplayGeometry();
+  display->x = track.position_x();
+  display->y = track.position_y();
+  display->width = track.width();
+  display->height = track.height();
+  display->dpi = track.x_dpi();
+  display->bpp = kDefaultDpi;
+  display->is_default = false;
+  displays_.push_back(*display);
+}
+
 void DesktopDisplayInfo::LoadCurrentDisplayInfo() {
   displays_.clear();
 
diff --git a/remoting/host/desktop_display_info.h b/remoting/host/desktop_display_info.h
index 766dbca..8f00da5 100644
--- a/remoting/host/desktop_display_info.h
+++ b/remoting/host/desktop_display_info.h
@@ -9,6 +9,7 @@
 
 #include "base/logging.h"
 #include "remoting/proto/control.pb.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 
 namespace remoting {
 
@@ -25,6 +26,21 @@
   DesktopDisplayInfo();
   ~DesktopDisplayInfo();
 
+  static webrtc::DesktopSize CalcSizeDips(webrtc::DesktopSize size,
+                                          int dpi_x,
+                                          int dpi_y);
+
+  // Clear out the display info.
+  void Reset();
+  int NumDisplays();
+  const DisplayGeometry* GetDisplayInfo(unsigned int id);
+
+  // Add a new display with the given info to the display list.
+  void AddDisplay(DisplayGeometry* display);
+
+  void AddDisplayFrom(protocol::VideoTrackLayout track);
+
+  // Query the OS for the set of currently active desktop displays.
   void LoadCurrentDisplayInfo();
 
   bool operator==(const DesktopDisplayInfo& other);
diff --git a/remoting/proto/control.proto b/remoting/proto/control.proto
index 1829b30..c020e6a 100644
--- a/remoting/proto/control.proto
+++ b/remoting/proto/control.proto
@@ -112,3 +112,25 @@
   // The "all" string is used to select the entire desktop.
   optional string id = 1;
 }
+
+message DesktopDisplayInfo {
+  // Unique display identifier.
+  optional int32 id = 1;
+
+  // Position of the top left corner of this display (in pixels).
+  optional int32 x = 2;
+  optional int32 y = 3;
+
+  // Size of the display (in pixels).
+  optional int32 width = 4;
+  optional int32 height = 5;
+
+  // DPI of the screen.
+  optional int32 dpi = 6;
+
+  // Bits per pixel.
+  optional int32 bpp = 7;
+
+  // True if this is the default display.
+  optional bool is_default = 8;
+}
diff --git a/remoting/protocol/mouse_input_filter.cc b/remoting/protocol/mouse_input_filter.cc
index 6080cc4b..b68a1f9 100644
--- a/remoting/protocol/mouse_input_filter.cc
+++ b/remoting/protocol/mouse_input_filter.cc
@@ -4,6 +4,9 @@
 
 #include "remoting/protocol/mouse_input_filter.h"
 
+#include <algorithm>
+
+#include "base/logging.h"
 #include "remoting/proto/event.pb.h"
 
 namespace remoting {
@@ -18,34 +21,40 @@
 MouseInputFilter::~MouseInputFilter() = default;
 
 void MouseInputFilter::InjectMouseEvent(const MouseEvent& event) {
-  if (input_max_.is_empty() || output_max_.is_empty())
+  if (input_rect_.is_empty() || output_rect_.is_empty())
     return;
 
   // We scale based on the maximum input & output coordinates, rather than the
   // input and output sizes, so that it's possible to reach the edge of the
   // output when up-scaling.  We also take care to round up or down correctly,
   // which is important when down-scaling.
+  // After scaling, we offset by the output rect origin. This is normally (0,0),
+  // but will be non-zero when we are showing a single display.
   MouseEvent out_event(event);
   if (out_event.has_x()) {
-    int x = out_event.x() * output_max_.width();
-    x = (x + input_max_.width() / 2) / input_max_.width();
-    out_event.set_x(std::max(0, std::min(output_max_.width(), x)));
+    int x = out_event.x() * output_rect_.width();
+    x = (x + input_rect_.width() / 2) / input_rect_.width();
+    out_event.set_x(output_rect_.left() +
+                    std::max(0, std::min(output_rect_.width(), x)));
   }
   if (out_event.has_y()) {
-    int y = out_event.y() * output_max_.height();
-    y = (y + input_max_.height() / 2) / input_max_.height();
-    out_event.set_y(std::max(0, std::min(output_max_.height(), y)));
+    int y = out_event.y() * output_rect_.height();
+    y = (y + input_rect_.height() / 2) / input_rect_.height();
+    out_event.set_y(output_rect_.top() +
+                    std::max(0, std::min(output_rect_.height(), y)));
   }
 
   InputFilter::InjectMouseEvent(out_event);
 }
 
-void MouseInputFilter::set_input_size(const webrtc::DesktopSize& size) {
-  input_max_.set(size.width() - 1, size.height() - 1);
+void MouseInputFilter::set_input_size(const webrtc::DesktopRect& rect) {
+  input_rect_ = webrtc::DesktopRect::MakeXYWH(
+      rect.left(), rect.top(), rect.width() - 1, rect.height() - 1);
 }
 
-void MouseInputFilter::set_output_size(const webrtc::DesktopSize& size) {
-  output_max_.set(size.width() - 1, size.height() - 1);
+void MouseInputFilter::set_output_size(const webrtc::DesktopRect& rect) {
+  output_rect_ = webrtc::DesktopRect::MakeXYWH(
+      rect.left(), rect.top(), rect.width() - 1, rect.height() - 1);
 }
 
 }  // namespace protocol
diff --git a/remoting/protocol/mouse_input_filter.h b/remoting/protocol/mouse_input_filter.h
index 1d4ffa13..21b943a 100644
--- a/remoting/protocol/mouse_input_filter.h
+++ b/remoting/protocol/mouse_input_filter.h
@@ -23,17 +23,18 @@
   ~MouseInputFilter() override;
 
   // Specify the input dimensions for mouse events.
-  void set_input_size(const webrtc::DesktopSize& size);
+  // This is specified in DIPs for WebRTC and pixels for ICE protocol.
+  void set_input_size(const webrtc::DesktopRect& r);
 
-  // Specify the output dimensions.
-  void set_output_size(const webrtc::DesktopSize& size);
+  // Specify the output dimensions (always in physical pixels).
+  void set_output_size(const webrtc::DesktopRect& r);
 
   // InputStub overrides.
   void InjectMouseEvent(const protocol::MouseEvent& event) override;
 
  private:
-  webrtc::DesktopSize input_max_;
-  webrtc::DesktopSize output_max_;
+  webrtc::DesktopRect input_rect_;
+  webrtc::DesktopRect output_rect_;
 
   DISALLOW_COPY_AND_ASSIGN(MouseInputFilter);
 };
diff --git a/remoting/protocol/mouse_input_filter_unittest.cc b/remoting/protocol/mouse_input_filter_unittest.cc
index f27eba70..19b5a93e 100644
--- a/remoting/protocol/mouse_input_filter_unittest.cc
+++ b/remoting/protocol/mouse_input_filter_unittest.cc
@@ -60,7 +60,7 @@
 TEST(MouseInputFilterTest, InputDimensionsZero) {
   MockInputStub mock_stub;
   MouseInputFilter mouse_filter(&mock_stub);
-  mouse_filter.set_output_size(webrtc::DesktopSize(50, 50));
+  mouse_filter.set_output_size(webrtc::DesktopRect::MakeWH(50, 50));
 
   EXPECT_CALL(mock_stub, InjectMouseEvent(_))
       .Times(0);
@@ -72,7 +72,7 @@
 TEST(MouseInputFilterTest, OutputDimensionsZero) {
   MockInputStub mock_stub;
   MouseInputFilter mouse_filter(&mock_stub);
-  mouse_filter.set_input_size(webrtc::DesktopSize(50, 50));
+  mouse_filter.set_input_size(webrtc::DesktopRect::MakeWH(50, 50));
 
   EXPECT_CALL(mock_stub, InjectMouseEvent(_))
       .Times(0);
@@ -84,8 +84,8 @@
 TEST(MouseInputFilterTest, NoScalingOrClipping) {
   MockInputStub mock_stub;
   MouseInputFilter mouse_filter(&mock_stub);
-  mouse_filter.set_output_size(webrtc::DesktopSize(40,40));
-  mouse_filter.set_input_size(webrtc::DesktopSize(40,40));
+  mouse_filter.set_output_size(webrtc::DesktopRect::MakeWH(40, 40));
+  mouse_filter.set_input_size(webrtc::DesktopRect::MakeWH(40, 40));
 
   {
     InSequence s;
@@ -112,8 +112,8 @@
 TEST(MouseInputFilterTest, UpScalingAndClamping) {
   MockInputStub mock_stub;
   MouseInputFilter mouse_filter(&mock_stub);
-  mouse_filter.set_output_size(webrtc::DesktopSize(80, 80));
-  mouse_filter.set_input_size(webrtc::DesktopSize(40, 40));
+  mouse_filter.set_output_size(webrtc::DesktopRect::MakeWH(80, 80));
+  mouse_filter.set_input_size(webrtc::DesktopRect::MakeWH(40, 40));
 
   {
     InSequence s;
@@ -140,8 +140,8 @@
 TEST(MouseInputFilterTest, DownScalingAndClamping) {
   MockInputStub mock_stub;
   MouseInputFilter mouse_filter(&mock_stub);
-  mouse_filter.set_output_size(webrtc::DesktopSize(30, 30));
-  mouse_filter.set_input_size(webrtc::DesktopSize(40, 40));
+  mouse_filter.set_output_size(webrtc::DesktopRect::MakeWH(30, 30));
+  mouse_filter.set_input_size(webrtc::DesktopRect::MakeWH(40, 40));
 
   {
     InSequence s;