| // 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 <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <cmath> |
| #include <cstdlib> |
| |
| #include <algorithm> |
| |
| #include "mouse_lock.h" |
| |
| #ifdef WIN32 |
| #undef min |
| #undef max |
| #undef PostMessage |
| #endif |
| |
| // Indicate the direction of the mouse location relative to the center of the |
| // view. These values are used to determine which 2D quadrant the needle lies |
| // in. |
| typedef enum { |
| kLeft = 0, |
| kRight = 1, |
| kUp = 2, |
| kDown = 3 |
| } MouseDirection; |
| |
| namespace { |
| const int kCentralSpotRadius = 5; |
| const uint32_t kReturnKeyCode = 13; |
| const uint32_t kBackgroundColor = 0xff606060; |
| const uint32_t kForegroundColor = 0xfff08080; |
| } // namespace |
| |
| MouseLockInstance::~MouseLockInstance() { |
| free(background_scanline_); |
| background_scanline_ = NULL; |
| } |
| |
| bool MouseLockInstance::Init(uint32_t argc, |
| const char* argn[], |
| const char* argv[]) { |
| RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD); |
| return true; |
| } |
| |
| bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) { |
| switch (event.GetType()) { |
| case PP_INPUTEVENT_TYPE_MOUSEDOWN: { |
| if (mouse_locked_) { |
| UnlockMouse(); |
| } else { |
| LockMouse( |
| callback_factory_.NewCallback(&MouseLockInstance::DidLockMouse)); |
| } |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_MOUSEMOVE: { |
| pp::MouseInputEvent mouse_event(event); |
| mouse_movement_ = mouse_event.GetMovement(); |
| Paint(); |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_KEYDOWN: { |
| pp::KeyboardInputEvent key_event(event); |
| |
| // Switch in and out of fullscreen when 'Enter' is hit |
| if (key_event.GetKeyCode() == kReturnKeyCode) { |
| // Ignore switch if in transition |
| if (!is_context_bound_) |
| return true; |
| |
| if (fullscreen_.IsFullscreen()) { |
| if (!fullscreen_.SetFullscreen(false)) { |
| Log("Could not leave fullscreen mode\n"); |
| } else { |
| is_context_bound_ = false; |
| } |
| } else { |
| if (!fullscreen_.SetFullscreen(true)) { |
| Log("Could not enter fullscreen mode\n"); |
| } else { |
| is_context_bound_ = false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_MOUSEUP: |
| case PP_INPUTEVENT_TYPE_MOUSEENTER: |
| case PP_INPUTEVENT_TYPE_MOUSELEAVE: |
| case PP_INPUTEVENT_TYPE_WHEEL: |
| case PP_INPUTEVENT_TYPE_RAWKEYDOWN: |
| case PP_INPUTEVENT_TYPE_KEYUP: |
| case PP_INPUTEVENT_TYPE_CHAR: |
| case PP_INPUTEVENT_TYPE_CONTEXTMENU: |
| case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: |
| case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: |
| case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: |
| case PP_INPUTEVENT_TYPE_IME_TEXT: |
| case PP_INPUTEVENT_TYPE_UNDEFINED: |
| case PP_INPUTEVENT_TYPE_TOUCHSTART: |
| case PP_INPUTEVENT_TYPE_TOUCHMOVE: |
| case PP_INPUTEVENT_TYPE_TOUCHEND: |
| case PP_INPUTEVENT_TYPE_TOUCHCANCEL: |
| default: |
| return false; |
| } |
| } |
| |
| void MouseLockInstance::DidChangeView(const pp::View& view) { |
| // DidChangeView can get called for many reasons, so we only want to |
| // rebuild the device context if we really need to. |
| |
| if ((size_ == view.GetRect().size()) && |
| (was_fullscreen_ == view.IsFullscreen()) && is_context_bound_) { |
| Log("DidChangeView SKIP %d,%d FULL=%s CTX Bound=%s", |
| view.GetRect().width(), |
| view.GetRect().height(), |
| view.IsFullscreen() ? "true" : "false", |
| is_context_bound_ ? "true" : "false"); |
| return; |
| } |
| |
| Log("DidChangeView DO %d,%d FULL=%s CTX Bound=%s", |
| view.GetRect().width(), |
| view.GetRect().height(), |
| view.IsFullscreen() ? "true" : "false", |
| is_context_bound_ ? "true" : "false"); |
| |
| size_ = view.GetRect().size(); |
| device_context_ = pp::Graphics2D(this, size_, false); |
| waiting_for_flush_completion_ = false; |
| |
| is_context_bound_ = BindGraphics(device_context_); |
| if (!is_context_bound_) { |
| Log("Could not bind to 2D context\n."); |
| return; |
| } else { |
| Log("Bound to 2D context size %d,%d.\n", size_.width(), size_.height()); |
| } |
| |
| // Create a scanline for fill. |
| delete[] background_scanline_; |
| background_scanline_ = new uint32_t[size_.width()]; |
| uint32_t* bg_pixel = background_scanline_; |
| for (int x = 0; x < size_.width(); ++x) { |
| *bg_pixel++ = kBackgroundColor; |
| } |
| |
| // Remember if we are fullscreen or not |
| was_fullscreen_ = view.IsFullscreen(); |
| |
| // Paint this context |
| Paint(); |
| } |
| |
| void MouseLockInstance::MouseLockLost() { |
| if (mouse_locked_) { |
| Log("Mouselock unlocked.\n"); |
| mouse_locked_ = false; |
| Paint(); |
| } |
| } |
| |
| void MouseLockInstance::DidLockMouse(int32_t result) { |
| mouse_locked_ = result == PP_OK; |
| if (result != PP_OK) { |
| Log("Mouselock failed with failed with error number %d.\n", result); |
| } |
| mouse_movement_.set_x(0); |
| mouse_movement_.set_y(0); |
| Paint(); |
| } |
| |
| void MouseLockInstance::DidFlush(int32_t result) { |
| if (result != 0) |
| Log("Flushed failed with error number %d.\n", result); |
| waiting_for_flush_completion_ = false; |
| } |
| |
| void MouseLockInstance::Paint() { |
| // If we are already waiting to paint... |
| if (waiting_for_flush_completion_) { |
| return; |
| } |
| |
| pp::ImageData image = PaintImage(size_); |
| if (image.is_null()) { |
| Log("Could not create image data\n"); |
| return; |
| } |
| |
| device_context_.ReplaceContents(&image); |
| waiting_for_flush_completion_ = true; |
| device_context_.Flush( |
| callback_factory_.NewCallback(&MouseLockInstance::DidFlush)); |
| } |
| |
| pp::ImageData MouseLockInstance::PaintImage(const pp::Size& size) { |
| pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, false); |
| if (image.is_null() || image.data() == NULL) { |
| Log("Skipping image.\n"); |
| return image; |
| } |
| |
| ClearToBackground(&image); |
| |
| DrawCenterSpot(&image, kForegroundColor); |
| DrawNeedle(&image, kForegroundColor); |
| return image; |
| } |
| |
| void MouseLockInstance::ClearToBackground(pp::ImageData* image) { |
| if (image == NULL) { |
| Log("ClearToBackground with NULL image."); |
| return; |
| } |
| if (background_scanline_ == NULL) { |
| Log("ClearToBackground with no scanline."); |
| return; |
| } |
| int image_height = image->size().height(); |
| int image_width = image->size().width(); |
| |
| for (int y = 0; y < image_height; ++y) { |
| uint32_t* scanline = image->GetAddr32(pp::Point(0, y)); |
| memcpy(scanline, |
| background_scanline_, |
| image_width * sizeof(*background_scanline_)); |
| } |
| } |
| |
| void MouseLockInstance::DrawCenterSpot(pp::ImageData* image, |
| uint32_t spot_color) { |
| if (image == NULL) { |
| Log("DrawCenterSpot with NULL image"); |
| return; |
| } |
| // Draw the center spot. The ROI is bounded by the size of the spot, plus |
| // one pixel. |
| int center_x = image->size().width() / 2; |
| int center_y = image->size().height() / 2; |
| int region_of_interest_radius = kCentralSpotRadius + 1; |
| |
| pp::Point left_top(std::max(0, center_x - region_of_interest_radius), |
| std::max(0, center_y - region_of_interest_radius)); |
| pp::Point right_bottom( |
| std::min(image->size().width(), center_x + region_of_interest_radius), |
| std::min(image->size().height(), center_y + region_of_interest_radius)); |
| for (int y = left_top.y(); y < right_bottom.y(); ++y) { |
| for (int x = left_top.x(); x < right_bottom.x(); ++x) { |
| if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) { |
| *image->GetAddr32(pp::Point(x, y)) = spot_color; |
| } |
| } |
| } |
| } |
| |
| void MouseLockInstance::DrawNeedle(pp::ImageData* image, |
| uint32_t needle_color) { |
| if (image == NULL) { |
| Log("DrawNeedle with NULL image"); |
| return; |
| } |
| if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <= |
| kCentralSpotRadius) { |
| return; |
| } |
| |
| int abs_mouse_x = std::abs(mouse_movement_.x()); |
| int abs_mouse_y = std::abs(mouse_movement_.y()); |
| int center_x = image->size().width() / 2; |
| int center_y = image->size().height() / 2; |
| pp::Point vertex(mouse_movement_.x() + center_x, |
| mouse_movement_.y() + center_y); |
| pp::Point anchor_1; |
| pp::Point anchor_2; |
| MouseDirection direction = kLeft; |
| |
| if (abs_mouse_x >= abs_mouse_y) { |
| anchor_1.set_x(center_x); |
| anchor_1.set_y(center_y - kCentralSpotRadius); |
| anchor_2.set_x(center_x); |
| anchor_2.set_y(center_y + kCentralSpotRadius); |
| direction = (mouse_movement_.x() < 0) ? kLeft : kRight; |
| if (direction == kLeft) |
| anchor_1.swap(anchor_2); |
| } else { |
| anchor_1.set_x(center_x + kCentralSpotRadius); |
| anchor_1.set_y(center_y); |
| anchor_2.set_x(center_x - kCentralSpotRadius); |
| anchor_2.set_y(center_y); |
| direction = (mouse_movement_.y() < 0) ? kUp : kDown; |
| if (direction == kUp) |
| anchor_1.swap(anchor_2); |
| } |
| |
| pp::Point left_top(std::max(0, center_x - abs_mouse_x), |
| std::max(0, center_y - abs_mouse_y)); |
| pp::Point right_bottom( |
| std::min(image->size().width(), center_x + abs_mouse_x), |
| std::min(image->size().height(), center_y + abs_mouse_y)); |
| for (int y = left_top.y(); y < right_bottom.y(); ++y) { |
| for (int x = left_top.x(); x < right_bottom.x(); ++x) { |
| bool within_bound_1 = ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) > |
| ((vertex.y() - anchor_1.y()) * (x - anchor_1.x())); |
| bool within_bound_2 = ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) < |
| ((vertex.y() - anchor_2.y()) * (x - anchor_2.x())); |
| bool within_bound_3 = (direction == kUp && y < center_y) || |
| (direction == kDown && y > center_y) || |
| (direction == kLeft && x < center_x) || |
| (direction == kRight && x > center_x); |
| |
| if (within_bound_1 && within_bound_2 && within_bound_3) { |
| *image->GetAddr32(pp::Point(x, y)) = needle_color; |
| } |
| } |
| } |
| } |
| |
| void MouseLockInstance::Log(const char* format, ...) { |
| static PPB_Console* console = |
| (PPB_Console*)pp::Module::Get()->GetBrowserInterface( |
| PPB_CONSOLE_INTERFACE); |
| |
| if (NULL == console) |
| return; |
| va_list args; |
| va_start(args, format); |
| char buf[512]; |
| vsnprintf(buf, sizeof(buf) - 1, format, args); |
| buf[sizeof(buf) - 1] = '\0'; |
| va_end(args); |
| |
| pp::Var value(buf); |
| console->Log(pp_instance(), PP_LOGLEVEL_ERROR, value.pp_var()); |
| } |
| |
| // This object is the global object representing this plugin library as long |
| // as it is loaded. |
| class MouseLockModule : public pp::Module { |
| public: |
| MouseLockModule() : pp::Module() {} |
| virtual ~MouseLockModule() {} |
| |
| // Override CreateInstance to create your customized Instance object. |
| virtual pp::Instance* CreateInstance(PP_Instance instance) { |
| return new MouseLockInstance(instance); |
| } |
| }; |
| |
| namespace pp { |
| |
| // Factory function for your specialization of the Module object. |
| Module* CreateModule() { return new MouseLockModule(); } |
| |
| } // namespace pp |