Home | History | Annotate | Download | only in mouse_lock
      1 // Copyright (c) 2012 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 <stdarg.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 #include <cmath>
      9 #include <cstdlib>
     10 
     11 #include <algorithm>
     12 
     13 #include "mouse_lock.h"
     14 
     15 #ifdef WIN32
     16 #undef min
     17 #undef max
     18 #undef PostMessage
     19 #endif
     20 
     21 // Indicate the direction of the mouse location relative to the center of the
     22 // view.  These values are used to determine which 2D quadrant the needle lies
     23 // in.
     24 typedef enum {
     25   kLeft = 0,
     26   kRight = 1,
     27   kUp = 2,
     28   kDown = 3
     29 } MouseDirection;
     30 
     31 namespace {
     32 const int kCentralSpotRadius = 5;
     33 const uint32_t kReturnKeyCode = 13;
     34 const uint32_t kBackgroundColor = 0xff606060;
     35 const uint32_t kForegroundColor = 0xfff08080;
     36 }  // namespace
     37 
     38 MouseLockInstance::~MouseLockInstance() {
     39   free(background_scanline_);
     40   background_scanline_ = NULL;
     41 }
     42 
     43 bool MouseLockInstance::Init(uint32_t argc,
     44                              const char* argn[],
     45                              const char* argv[]) {
     46   RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD);
     47   return true;
     48 }
     49 
     50 bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) {
     51   switch (event.GetType()) {
     52     case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
     53       if (mouse_locked_) {
     54         UnlockMouse();
     55       } else {
     56         LockMouse(
     57             callback_factory_.NewCallback(&MouseLockInstance::DidLockMouse));
     58       }
     59       return true;
     60     }
     61 
     62     case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
     63       pp::MouseInputEvent mouse_event(event);
     64       mouse_movement_ = mouse_event.GetMovement();
     65       Paint();
     66       return true;
     67     }
     68 
     69     case PP_INPUTEVENT_TYPE_KEYDOWN: {
     70       pp::KeyboardInputEvent key_event(event);
     71 
     72       // Switch in and out of fullscreen when 'Enter' is hit
     73       if (key_event.GetKeyCode() == kReturnKeyCode) {
     74         // Ignore switch if in transition
     75         if (!is_context_bound_)
     76           return true;
     77 
     78         if (fullscreen_.IsFullscreen()) {
     79           if (!fullscreen_.SetFullscreen(false)) {
     80             Log("Could not leave fullscreen mode\n");
     81           } else {
     82             is_context_bound_ = false;
     83           }
     84         } else {
     85           if (!fullscreen_.SetFullscreen(true)) {
     86             Log("Could not enter fullscreen mode\n");
     87           } else {
     88             is_context_bound_ = false;
     89           }
     90         }
     91       }
     92       return true;
     93     }
     94 
     95     case PP_INPUTEVENT_TYPE_MOUSEUP:
     96     case PP_INPUTEVENT_TYPE_MOUSEENTER:
     97     case PP_INPUTEVENT_TYPE_MOUSELEAVE:
     98     case PP_INPUTEVENT_TYPE_WHEEL:
     99     case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
    100     case PP_INPUTEVENT_TYPE_KEYUP:
    101     case PP_INPUTEVENT_TYPE_CHAR:
    102     case PP_INPUTEVENT_TYPE_CONTEXTMENU:
    103     case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
    104     case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
    105     case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
    106     case PP_INPUTEVENT_TYPE_IME_TEXT:
    107     case PP_INPUTEVENT_TYPE_UNDEFINED:
    108     case PP_INPUTEVENT_TYPE_TOUCHSTART:
    109     case PP_INPUTEVENT_TYPE_TOUCHMOVE:
    110     case PP_INPUTEVENT_TYPE_TOUCHEND:
    111     case PP_INPUTEVENT_TYPE_TOUCHCANCEL:
    112     default:
    113       return false;
    114   }
    115 }
    116 
    117 void MouseLockInstance::DidChangeView(const pp::View& view) {
    118   // DidChangeView can get called for many reasons, so we only want to
    119   // rebuild the device context if we really need to.
    120 
    121   if ((size_ == view.GetRect().size()) &&
    122       (was_fullscreen_ == view.IsFullscreen()) && is_context_bound_) {
    123     Log("DidChangeView SKIP %d,%d FULL=%s CTX Bound=%s",
    124         view.GetRect().width(),
    125         view.GetRect().height(),
    126         view.IsFullscreen() ? "true" : "false",
    127         is_context_bound_ ? "true" : "false");
    128     return;
    129   }
    130 
    131   Log("DidChangeView DO %d,%d FULL=%s CTX Bound=%s",
    132       view.GetRect().width(),
    133       view.GetRect().height(),
    134       view.IsFullscreen() ? "true" : "false",
    135       is_context_bound_ ? "true" : "false");
    136 
    137   size_ = view.GetRect().size();
    138   device_context_ = pp::Graphics2D(this, size_, false);
    139   waiting_for_flush_completion_ = false;
    140 
    141   is_context_bound_ = BindGraphics(device_context_);
    142   if (!is_context_bound_) {
    143     Log("Could not bind to 2D context\n.");
    144     return;
    145   } else {
    146     Log("Bound to 2D context size %d,%d.\n", size_.width(), size_.height());
    147   }
    148 
    149   // Create a scanline for fill.
    150   delete[] background_scanline_;
    151   background_scanline_ = new uint32_t[size_.width()];
    152   uint32_t* bg_pixel = background_scanline_;
    153   for (int x = 0; x < size_.width(); ++x) {
    154     *bg_pixel++ = kBackgroundColor;
    155   }
    156 
    157   // Remember if we are fullscreen or not
    158   was_fullscreen_ = view.IsFullscreen();
    159 
    160   // Paint this context
    161   Paint();
    162 }
    163 
    164 void MouseLockInstance::MouseLockLost() {
    165   if (mouse_locked_) {
    166     Log("Mouselock unlocked.\n");
    167     mouse_locked_ = false;
    168     Paint();
    169   }
    170 }
    171 
    172 void MouseLockInstance::DidLockMouse(int32_t result) {
    173   mouse_locked_ = result == PP_OK;
    174   if (result != PP_OK) {
    175     Log("Mouselock failed with failed with error number %d.\n", result);
    176   }
    177   mouse_movement_.set_x(0);
    178   mouse_movement_.set_y(0);
    179   Paint();
    180 }
    181 
    182 void MouseLockInstance::DidFlush(int32_t result) {
    183   if (result != 0)
    184     Log("Flushed failed with error number %d.\n", result);
    185   waiting_for_flush_completion_ = false;
    186 }
    187 
    188 void MouseLockInstance::Paint() {
    189   // If we are already waiting to paint...
    190   if (waiting_for_flush_completion_) {
    191     return;
    192   }
    193 
    194   pp::ImageData image = PaintImage(size_);
    195   if (image.is_null()) {
    196     Log("Could not create image data\n");
    197     return;
    198   }
    199 
    200   device_context_.ReplaceContents(&image);
    201   waiting_for_flush_completion_ = true;
    202   device_context_.Flush(
    203       callback_factory_.NewCallback(&MouseLockInstance::DidFlush));
    204 }
    205 
    206 pp::ImageData MouseLockInstance::PaintImage(const pp::Size& size) {
    207   pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, false);
    208   if (image.is_null() || image.data() == NULL) {
    209     Log("Skipping image.\n");
    210     return image;
    211   }
    212 
    213   ClearToBackground(&image);
    214 
    215   DrawCenterSpot(&image, kForegroundColor);
    216   DrawNeedle(&image, kForegroundColor);
    217   return image;
    218 }
    219 
    220 void MouseLockInstance::ClearToBackground(pp::ImageData* image) {
    221   if (image == NULL) {
    222     Log("ClearToBackground with NULL image.");
    223     return;
    224   }
    225   if (background_scanline_ == NULL) {
    226     Log("ClearToBackground with no scanline.");
    227     return;
    228   }
    229   int image_height = image->size().height();
    230   int image_width = image->size().width();
    231 
    232   for (int y = 0; y < image_height; ++y) {
    233     uint32_t* scanline = image->GetAddr32(pp::Point(0, y));
    234     memcpy(scanline,
    235            background_scanline_,
    236            image_width * sizeof(*background_scanline_));
    237   }
    238 }
    239 
    240 void MouseLockInstance::DrawCenterSpot(pp::ImageData* image,
    241                                        uint32_t spot_color) {
    242   if (image == NULL) {
    243     Log("DrawCenterSpot with NULL image");
    244     return;
    245   }
    246   // Draw the center spot.  The ROI is bounded by the size of the spot, plus
    247   // one pixel.
    248   int center_x = image->size().width() / 2;
    249   int center_y = image->size().height() / 2;
    250   int region_of_interest_radius = kCentralSpotRadius + 1;
    251 
    252   pp::Point left_top(std::max(0, center_x - region_of_interest_radius),
    253                      std::max(0, center_y - region_of_interest_radius));
    254   pp::Point right_bottom(
    255       std::min(image->size().width(), center_x + region_of_interest_radius),
    256       std::min(image->size().height(), center_y + region_of_interest_radius));
    257   for (int y = left_top.y(); y < right_bottom.y(); ++y) {
    258     for (int x = left_top.x(); x < right_bottom.x(); ++x) {
    259       if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) {
    260         *image->GetAddr32(pp::Point(x, y)) = spot_color;
    261       }
    262     }
    263   }
    264 }
    265 
    266 void MouseLockInstance::DrawNeedle(pp::ImageData* image,
    267                                    uint32_t needle_color) {
    268   if (image == NULL) {
    269     Log("DrawNeedle with NULL image");
    270     return;
    271   }
    272   if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <=
    273       kCentralSpotRadius) {
    274     return;
    275   }
    276 
    277   int abs_mouse_x = std::abs(mouse_movement_.x());
    278   int abs_mouse_y = std::abs(mouse_movement_.y());
    279   int center_x = image->size().width() / 2;
    280   int center_y = image->size().height() / 2;
    281   pp::Point vertex(mouse_movement_.x() + center_x,
    282                    mouse_movement_.y() + center_y);
    283   pp::Point anchor_1;
    284   pp::Point anchor_2;
    285   MouseDirection direction = kLeft;
    286 
    287   if (abs_mouse_x >= abs_mouse_y) {
    288     anchor_1.set_x(center_x);
    289     anchor_1.set_y(center_y - kCentralSpotRadius);
    290     anchor_2.set_x(center_x);
    291     anchor_2.set_y(center_y + kCentralSpotRadius);
    292     direction = (mouse_movement_.x() < 0) ? kLeft : kRight;
    293     if (direction == kLeft)
    294       anchor_1.swap(anchor_2);
    295   } else {
    296     anchor_1.set_x(center_x + kCentralSpotRadius);
    297     anchor_1.set_y(center_y);
    298     anchor_2.set_x(center_x - kCentralSpotRadius);
    299     anchor_2.set_y(center_y);
    300     direction = (mouse_movement_.y() < 0) ? kUp : kDown;
    301     if (direction == kUp)
    302       anchor_1.swap(anchor_2);
    303   }
    304 
    305   pp::Point left_top(std::max(0, center_x - abs_mouse_x),
    306                      std::max(0, center_y - abs_mouse_y));
    307   pp::Point right_bottom(
    308       std::min(image->size().width(), center_x + abs_mouse_x),
    309       std::min(image->size().height(), center_y + abs_mouse_y));
    310   for (int y = left_top.y(); y < right_bottom.y(); ++y) {
    311     for (int x = left_top.x(); x < right_bottom.x(); ++x) {
    312       bool within_bound_1 = ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) >
    313                             ((vertex.y() - anchor_1.y()) * (x - anchor_1.x()));
    314       bool within_bound_2 = ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) <
    315                             ((vertex.y() - anchor_2.y()) * (x - anchor_2.x()));
    316       bool within_bound_3 = (direction == kUp && y < center_y) ||
    317                             (direction == kDown && y > center_y) ||
    318                             (direction == kLeft && x < center_x) ||
    319                             (direction == kRight && x > center_x);
    320 
    321       if (within_bound_1 && within_bound_2 && within_bound_3) {
    322         *image->GetAddr32(pp::Point(x, y)) = needle_color;
    323       }
    324     }
    325   }
    326 }
    327 
    328 void MouseLockInstance::Log(const char* format, ...) {
    329   static PPB_Console* console =
    330       (PPB_Console*)pp::Module::Get()->GetBrowserInterface(
    331           PPB_CONSOLE_INTERFACE);
    332 
    333   if (NULL == console)
    334     return;
    335   va_list args;
    336   va_start(args, format);
    337   char buf[512];
    338   vsnprintf(buf, sizeof(buf) - 1, format, args);
    339   buf[sizeof(buf) - 1] = '\0';
    340   va_end(args);
    341 
    342   pp::Var value(buf);
    343   console->Log(pp_instance(), PP_LOGLEVEL_ERROR, value.pp_var());
    344 }
    345 
    346 // This object is the global object representing this plugin library as long
    347 // as it is loaded.
    348 class MouseLockModule : public pp::Module {
    349  public:
    350   MouseLockModule() : pp::Module() {}
    351   virtual ~MouseLockModule() {}
    352 
    353   // Override CreateInstance to create your customized Instance object.
    354   virtual pp::Instance* CreateInstance(PP_Instance instance) {
    355     return new MouseLockInstance(instance);
    356   }
    357 };
    358 
    359 namespace pp {
    360 
    361 // Factory function for your specialization of the Module object.
    362 Module* CreateModule() { return new MouseLockModule(); }
    363 
    364 }  // namespace pp
    365