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