#include "remoting/host/event_executor.h"
#include <ApplicationServices/ApplicationServices.h>
#include <Carbon/Carbon.h>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/single_thread_task_runner.h"
#include "remoting/host/clipboard.h"
#include "remoting/proto/internal.pb.h"
#include "remoting/protocol/message_decoder.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRect.h"
namespace remoting {
namespace {
using protocol::ClipboardEvent;
using protocol::KeyEvent;
using protocol::MouseEvent;
#define USB_KEYMAP(usb, xkb, win, mac) {usb, mac}
#include "ui/base/keycodes/usb_keycode_map.h"
#undef USB_KEYMAP
SkIRect CGRectToSkIRect(const CGRect& rect) {
SkIRect sk_rect = {
SkScalarRound(rect.origin.x),
SkScalarRound(rect.origin.y),
SkScalarRound(rect.origin.x + rect.size.width),
SkScalarRound(rect.origin.y + rect.size.height)
};
return sk_rect;
}
class EventExecutorMac : public EventExecutor {
public:
EventExecutorMac(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
virtual ~EventExecutorMac() {}
virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
virtual void Start(
scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
virtual void StopAndDelete() OVERRIDE;
private:
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
SkIPoint mouse_pos_;
uint32 mouse_button_state_;
scoped_ptr<Clipboard> clipboard_;
DISALLOW_COPY_AND_ASSIGN(EventExecutorMac);
};
EventExecutorMac::EventExecutorMac(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner),
mouse_button_state_(0),
clipboard_(Clipboard::Create()) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGSetLocalEventsSuppressionInterval(0.0);
#pragma clang diagnostic pop
}
void EventExecutorMac::InjectClipboardEvent(const ClipboardEvent& event) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::Bind(&EventExecutorMac::InjectClipboardEvent,
base::Unretained(this),
event));
return;
}
clipboard_->InjectClipboardEvent(event);
}
void EventExecutorMac::InjectKeyEvent(const KeyEvent& event) {
DCHECK(event.has_pressed());
DCHECK(event.has_usb_keycode());
int keycode = UsbKeycodeToNativeKeycode(event.usb_keycode());
VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
<< " to keycode: " << keycode << std::dec;
if (keycode == kInvalidKeycode)
return;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGError error = CGPostKeyboardEvent(0, keycode, event.pressed());
#pragma clang diagnostic pop
if (error != kCGErrorSuccess) {
LOG(WARNING) << "CGPostKeyboardEvent error " << error;
}
}
void EventExecutorMac::InjectMouseEvent(const MouseEvent& event) {
if (event.has_x() && event.has_y()) {
mouse_pos_ = SkIPoint::Make(event.x(), event.y());
CGDisplayCount display_count;
CGError error = CGGetActiveDisplayList(0, NULL, &display_count);
CHECK_EQ(error, CGDisplayNoErr);
if (display_count > 1) {
std::vector<CGDirectDisplayID> display_ids(display_count);
error = CGGetActiveDisplayList(display_count, &display_ids[0],
&display_count);
CHECK_EQ(error, CGDisplayNoErr);
CHECK_EQ(display_count, display_ids.size());
SkIRect desktop_bounds = SkIRect::MakeEmpty();
for (unsigned int d = 0; d < display_count; ++d) {
CGRect display_bounds = CGDisplayBounds(display_ids[d]);
desktop_bounds.join(CGRectToSkIRect(display_bounds));
}
mouse_pos_ += SkIPoint::Make(desktop_bounds.left(), desktop_bounds.top());
}
VLOG(3) << "Moving mouse to " << event.x() << "," << event.y();
}
if (event.has_button() && event.has_button_down()) {
if (event.button() >= 1 && event.button() <= 3) {
VLOG(2) << "Button " << event.button()
<< (event.button_down() ? " down" : " up");
int button_change = 1 << (event.button() - 1);
if (event.button_down())
mouse_button_state_ |= button_change;
else
mouse_button_state_ &= ~button_change;
} else {
VLOG(1) << "Unknown mouse button: " << event.button();
}
}
CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y());
enum {
LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1),
MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1),
RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1)
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGError error = CGPostMouseEvent(position, true, 3,
(mouse_button_state_ & LeftBit) != 0,
(mouse_button_state_ & RightBit) != 0,
(mouse_button_state_ & MiddleBit) != 0);
#pragma clang diagnostic pop
if (error != kCGErrorSuccess) {
LOG(WARNING) << "CGPostMouseEvent error " << error;
}
if (event.has_wheel_offset_x() && event.has_wheel_offset_y()) {
int dx = event.wheel_offset_x();
int dy = event.wheel_offset_y();
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
error = CGPostScrollWheelEvent(2, dy, dx);
#pragma clang diagnostic pop
if (error != kCGErrorSuccess) {
LOG(WARNING) << "CGPostScrollWheelEvent error " << error;
}
}
}
void EventExecutorMac::Start(
scoped_ptr<protocol::ClipboardStub> client_clipboard) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::Bind(&EventExecutorMac::Start,
base::Unretained(this),
base::Passed(&client_clipboard)));
return;
}
clipboard_->Start(client_clipboard.Pass());
}
void EventExecutorMac::StopAndDelete() {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::Bind(&EventExecutorMac::StopAndDelete,
base::Unretained(this)));
return;
}
clipboard_->Stop();
delete this;
}
}
scoped_ptr<EventExecutor> EventExecutor::Create(
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
return scoped_ptr<EventExecutor>(new EventExecutorMac(main_task_runner));
}
}