1// Copyright 2014 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 "remoting/host/single_window_input_injector.h"
6
7#include <ApplicationServices/ApplicationServices.h>
8#include <Carbon/Carbon.h>
9
10#include "base/mac/foundation_util.h"
11#include "base/mac/scoped_cftyperef.h"
12#include "remoting/proto/event.pb.h"
13#include "third_party/webrtc/modules/desktop_capture/mac/desktop_configuration.h"
14
15namespace remoting {
16
17using protocol::ClipboardEvent;
18using protocol::KeyEvent;
19using protocol::TextEvent;
20using protocol::MouseEvent;
21
22class SingleWindowInputInjectorMac : public SingleWindowInputInjector {
23 public:
24  SingleWindowInputInjectorMac(
25      webrtc::WindowId window_id,
26      scoped_ptr<InputInjector> input_injector);
27  virtual ~SingleWindowInputInjectorMac();
28
29  // InputInjector interface.
30  virtual void Start(
31      scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
32  virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
33  virtual void InjectTextEvent(const TextEvent& event) OVERRIDE;
34  virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
35  virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
36
37 private:
38  CGRect FindCGRectOfWindow();
39
40  CGWindowID window_id_;
41  scoped_ptr<InputInjector> input_injector_;
42
43  DISALLOW_COPY_AND_ASSIGN(SingleWindowInputInjectorMac);
44};
45
46SingleWindowInputInjectorMac::SingleWindowInputInjectorMac(
47    webrtc::WindowId window_id,
48    scoped_ptr<InputInjector> input_injector)
49    : window_id_(static_cast<CGWindowID>(window_id)),
50      input_injector_(input_injector.Pass()) {
51}
52
53SingleWindowInputInjectorMac::~SingleWindowInputInjectorMac() {
54}
55
56void SingleWindowInputInjectorMac::Start(
57    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
58  input_injector_->Start(client_clipboard.Pass());
59}
60
61void SingleWindowInputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
62  input_injector_->InjectKeyEvent(event);
63}
64
65void SingleWindowInputInjectorMac::InjectTextEvent(const TextEvent& event) {
66  input_injector_->InjectTextEvent(event);
67}
68
69void SingleWindowInputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
70  if (event.has_x() && event.has_y()) {
71    CGRect window_rect = FindCGRectOfWindow();
72    if (CGRectIsNull(window_rect)) {
73      LOG(ERROR) << "Window rect is null, so forwarding unmodified MouseEvent";
74      input_injector_->InjectMouseEvent(event);
75      return;
76    }
77
78    webrtc::MacDesktopConfiguration desktop_config =
79        webrtc::MacDesktopConfiguration::GetCurrent(
80            webrtc::MacDesktopConfiguration::TopLeftOrigin);
81
82    // Create a vector that has the origin of the window.
83    webrtc::DesktopVector window_pos(window_rect.origin.x,
84                                     window_rect.origin.y);
85
86    // The underlying InputInjector expects coordinates relative to the
87    // top-left of the top-left-most monitor, so translate the window origin
88    // to that coordinate scheme.
89    window_pos.subtract(
90        webrtc::DesktopVector(desktop_config.pixel_bounds.left(),
91                              desktop_config.pixel_bounds.top()));
92
93    // We must make sure we are taking into account the fact that when we
94    // find the window on the host it returns its coordinates in Density
95    // Independent coordinates. We have to convert to Density Dependent
96    // because InputInjector assumes Density Dependent coordinates in the
97    // MouseEvent.
98    window_pos.set(window_pos.x() * desktop_config.dip_to_pixel_scale,
99                   window_pos.y() * desktop_config.dip_to_pixel_scale);
100
101    // Create a new event with coordinates that are in respect to the window.
102    MouseEvent modified_event(event);
103    modified_event.set_x(event.x() + window_pos.x());
104    modified_event.set_y(event.y() + window_pos.y());
105    input_injector_->InjectMouseEvent(modified_event);
106  } else {
107    input_injector_->InjectMouseEvent(event);
108  }
109}
110
111void SingleWindowInputInjectorMac::InjectClipboardEvent(
112    const ClipboardEvent& event) {
113  input_injector_->InjectClipboardEvent(event);
114}
115
116// This method finds the rectangle of the window we are streaming using
117// |window_id_|. The InputInjector can then use this rectangle
118// to translate the input event to coordinates of the window rather
119// than the screen.
120CGRect SingleWindowInputInjectorMac::FindCGRectOfWindow() {
121  CGRect rect;
122  CGWindowID ids[1] = {window_id_};
123  base::ScopedCFTypeRef<CFArrayRef> window_id_array(
124      CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL));
125
126  base::ScopedCFTypeRef<CFArrayRef> window_array(
127      CGWindowListCreateDescriptionFromArray(window_id_array));
128
129  if (window_array == NULL || CFArrayGetCount(window_array) == 0) {
130    // Could not find the window. It might have been closed.
131    LOG(ERROR) << "Specified window to stream not found for id: "
132               << window_id_;
133    return CGRectNull;
134  }
135
136  // We don't use ScopedCFTypeRef for |window_array| because the
137  // CFDictionaryRef returned by CFArrayGetValueAtIndex is owned by
138  // window_array. The same is true of the |bounds|.
139  CFDictionaryRef window =
140      base::mac::CFCast<CFDictionaryRef>(
141          CFArrayGetValueAtIndex(window_array, 0));
142
143  if (CFDictionaryContainsKey(window, kCGWindowBounds)) {
144    CFDictionaryRef bounds =
145        base::mac::GetValueFromDictionary<CFDictionaryRef>(
146            window, kCGWindowBounds);
147
148    if (bounds) {
149      if (CGRectMakeWithDictionaryRepresentation(bounds, &rect)) {
150        return rect;
151      }
152    }
153  }
154
155  return CGRectNull;
156}
157
158scoped_ptr<InputInjector> SingleWindowInputInjector::CreateForWindow(
159    webrtc::WindowId window_id,
160    scoped_ptr<InputInjector> input_injector) {
161  scoped_ptr<SingleWindowInputInjectorMac> injector(
162      new SingleWindowInputInjectorMac(window_id, input_injector.Pass()));
163  return injector.PassAs<InputInjector>();
164}
165
166}  // namespace remoting
167