/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #ifdef _WIN32 #include #include #endif #include "controlconfig/controlsconfig.h" #include "graphics/2d.h" #include "io/mouse.h" #include "options/Option.h" #include "scripting/scripting.h" #include "scripting/global_hooks.h" #include "scripting/hook_api.h" #define THREADED // to use the proper set of macros #include "osapi/osapi.h" #include "cmdline/cmdline.h" #include "gamesequence/gamesequence.h" int mouse_inited = 0; LOCAL int Mouse_x; LOCAL int Mouse_y; LOCAL int Mouse_wheel_x; LOCAL int Mouse_wheel_y; SDL_Mutex* mouse_lock; int mouse_flags; int mouse_left_pressed = 0; int mouse_right_pressed = 0; int mouse_middle_pressed = 0; int mouse_x1_pressed = 0; int mouse_x2_pressed = 0; int mouse_left_up = 0; int mouse_right_up = 0; int mouse_middle_up = 0; int mouse_x1_up = 0; int mouse_x2_up = 0; int Mouse_dx = 0; int Mouse_dy = 0; int Mouse_dz = 0; // Mouse wheel delta tracking movement not position. Resets every frame. int Mouse_wheel_dx = 0; int Mouse_wheel_dy = 0; int Mouse_sensitivity = 4; // coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton static auto MouseSensitivityOption __UNUSED = options::OptionBuilder("Input.MouseSensitivity", std::pair{"Sensitivity", 1374}, std::pair{"The sensitivity of the mouse input", 1747}) .category(std::make_pair("Input", 1827)) .range(0, 9) .level(options::ExpertLevel::Beginner) .default_val(4) .bind_to(&Mouse_sensitivity) .importance(0) .flags({options::OptionFlags::RetailBuiltinOption}) .finish(); bool Use_mouse_to_fly = false; static SCP_string mouse_mode_display(bool mode) { return mode ? XSTR("Joy-0", 1699) : XSTR("Mouse", 1373); } // coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton static auto UseMouseOption __UNUSED = options::OptionBuilder("Input.UseMouse", std::pair{"Mouse", 1373}, std::pair{"Whether or not to use the mouse for flying", 1765}) .category(std::make_pair("Input", 1827)) .display(mouse_mode_display) .level(options::ExpertLevel::Beginner) .default_val(false) .bind_to(&Use_mouse_to_fly) .importance(1) .flags({options::OptionFlags::RetailBuiltinOption}) .finish(); const std::shared_ptr> OnMouseWheelHook = scripting::Hook<>::Factory( "On Mouse Wheel", "Called when the mouse wheel is moved in any direction.", { {"MouseWheelY", "number", "Positive if moved up, negative if moved down."}, {"MouseWheelX", "number", "Positive if moved right, negative if moved left."}, }); namespace { bool mouse_key_event_handler(const SDL_Event& e) { if (!os::events::isWindowEvent(e, os::getSDLMainWindow())) { return false; } switch (e.button.button) { case SDL_BUTTON_LEFT: mouse_mark_button(MOUSE_LEFT_BUTTON, e.button.down); break; case SDL_BUTTON_RIGHT: mouse_mark_button(MOUSE_RIGHT_BUTTON, e.button.down); break; case SDL_BUTTON_MIDDLE: mouse_mark_button(MOUSE_MIDDLE_BUTTON, e.button.down); break; case SDL_BUTTON_X1: mouse_mark_button(MOUSE_X1_BUTTON, e.button.down); break; case SDL_BUTTON_X2: mouse_mark_button(MOUSE_X2_BUTTON, e.button.down); break; default: // SDL gave us an unknown button. Just log it mprintf(("Unknown SDL button %i\n", e.button.button)); } return true; } bool mouse_motion_event_handler(const SDL_Event& e) { if (!os::events::isWindowEvent(e, os::getSDLMainWindow())) { return false; } // SDL reports these in window pixels, the rest of the engine works in render pixels float x = e.motion.x; float y = e.motion.y; gr_window_to_render_pos(x, y); float dx = e.motion.xrel; float dy = e.motion.yrel; gr_window_to_render_pos(dx, dy); mouse_event(x, y, dx, dy); return true; } bool mouse_wheel_event_handler(const SDL_Event& e) { if (!os::events::isWindowEvent(e, os::getSDLMainWindow())) { return false; } mousewheel_motion(e.wheel.x, e.wheel.y, e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED); return true; } } /** * @brief Decays the mousewheel position back to 0 and clears the appropriate flags when nuetral * @param[in] btn The button (wheel direction) to check against */ void mousewheel_decay(int btn); static bool Mouse_in_focus = true; void mouse_close() { if (!mouse_inited) return; mouse_inited = 0; SDL_DestroyMutex( mouse_lock ); } void mouse_init() { // Initialize queue if ( mouse_inited ) return; mouse_inited = 1; mouse_lock = SDL_CreateMutex(); SDL_LockMutex( mouse_lock ); mouse_flags = 0; Mouse_x = gr_screen.max_w / 2; Mouse_y = gr_screen.max_h / 2; Mouse_wheel_x = 0; Mouse_wheel_y = 0; // we do want to make sure that button presses go through event polling though // (should be on by default already, just here as a reminder) SDL_SetEventEnabled( SDL_EVENT_MOUSE_BUTTON_DOWN, true ); SDL_SetEventEnabled( SDL_EVENT_MOUSE_BUTTON_UP, true ); SDL_SetEventEnabled( SDL_EVENT_MOUSE_WHEEL, true ); SDL_UnlockMutex( mouse_lock ); os::events::addEventListener(SDL_EVENT_MOUSE_BUTTON_DOWN, os::events::DEFAULT_LISTENER_WEIGHT, mouse_key_event_handler); os::events::addEventListener(SDL_EVENT_MOUSE_BUTTON_UP, os::events::DEFAULT_LISTENER_WEIGHT, mouse_key_event_handler); os::events::addEventListener(SDL_EVENT_MOUSE_MOTION, os::events::DEFAULT_LISTENER_WEIGHT, mouse_motion_event_handler); os::events::addEventListener(SDL_EVENT_MOUSE_WHEEL, os::events::DEFAULT_LISTENER_WEIGHT, mouse_wheel_event_handler); atexit( mouse_close ); } /****************************************************************************** * @brief Marks a mouse button as up or down * * @param[in] flags Which button(s) are pressed/released * @param[in] set Direction of the button(s). 1 = pressed, 0 = released * * @note This function is extern'ed by osapi.cpp and freespace.cpp */ void mouse_mark_button( uint flags, int set) { if ( !mouse_inited ) return; SDL_LockMutex( mouse_lock ); if ( !(mouse_flags & MOUSE_LEFT_BUTTON) ) { if ( (flags & MOUSE_LEFT_BUTTON) && (set == 1) ) { mouse_left_pressed++; } } else { if ( (flags & MOUSE_LEFT_BUTTON) && (set == 0) ){ mouse_left_up++; } } if ( !(mouse_flags & MOUSE_RIGHT_BUTTON) ) { if ( (flags & MOUSE_RIGHT_BUTTON) && (set == 1) ){ mouse_right_pressed++; } } else { if ( (flags & MOUSE_RIGHT_BUTTON) && (set == 0) ){ mouse_right_up++; } } if ( !(mouse_flags & MOUSE_MIDDLE_BUTTON) ) { if ( (flags & MOUSE_MIDDLE_BUTTON) && (set == 1) ){ mouse_middle_pressed++; } } else { if ( (flags & MOUSE_MIDDLE_BUTTON) && (set == 0) ){ mouse_middle_up++; } } if (!(mouse_flags & MOUSE_X1_BUTTON)) { if ((flags & MOUSE_X1_BUTTON) && (set == 1)) { mouse_x1_pressed++; } } else { if ((flags & MOUSE_X1_BUTTON) && (set == 0)) { mouse_x1_up++; } } if (!(mouse_flags & MOUSE_X2_BUTTON)) { if ((flags & MOUSE_X2_BUTTON) && (set == 1)) { mouse_x2_pressed++; } } else { if ((flags & MOUSE_X2_BUTTON) && (set == 0)) { mouse_x2_up++; } } if ( set ){ mouse_flags |= flags; } else { mouse_flags &= ~flags; } SDL_UnlockMutex( mouse_lock ); Script_system.SetHookVar("MouseButton", 'i', flags); if (scripting::hooks::OnMousePressed->isActive() || scripting::hooks::OnMouseReleased->isActive()) { //WMC - On Mouse Pressed and On Mouse Released hooks if (set == 1) { scripting::hooks::OnMousePressed->run(); } else if (set == 0) { scripting::hooks::OnMouseReleased->run(); } } Script_system.RemHookVar("MouseButton"); } void mouse_flush() { if (!mouse_inited) return; mouse_reset_deltas(); Mouse_dx = Mouse_dy = Mouse_dz = 0; Mouse_wheel_x = Mouse_wheel_y = 0; SDL_LockMutex( mouse_lock ); mouse_left_pressed = 0; mouse_right_pressed = 0; mouse_middle_pressed = 0; mouse_x1_pressed = 0; mouse_x2_pressed = 0; mouse_flags = 0; SDL_UnlockMutex( mouse_lock ); } int mouse_down_count(const CC_bind &bind, int reset_count) { // Bail if the incoming bind is not the right CID according to mouse-fly mode auto CID = bind.get_cid(); if (Use_mouse_to_fly) { // Mouse is Joy0 in this mode if (CID != CID_JOY0) { return 0; } } else { // Mouse is Mouse in this mode if (CID != CID_MOUSE) { return 0; } } int btn = bind.get_btn(); if (btn > MOUSE_NUM_BUTTONS) { return 0; } btn = 1 << btn; return mouse_down_count(btn, reset_count); } int mouse_down_count(int n, int reset_count) { int tmp = 0; if ( !mouse_inited ) return 0; if ( (n < LOWEST_MOUSE_BUTTON) || (n > HIGHEST_MOUSE_BUTTON)) return 0; SDL_LockMutex( mouse_lock ); switch (n) { case MOUSE_LEFT_BUTTON: tmp = mouse_left_pressed; if ( reset_count ) { mouse_left_pressed = 0; } break; case MOUSE_RIGHT_BUTTON: tmp = mouse_right_pressed; if ( reset_count ) { mouse_right_pressed = 0; } break; case MOUSE_MIDDLE_BUTTON: tmp = mouse_middle_pressed; if ( reset_count ) { mouse_middle_pressed = 0; } break; case MOUSE_X1_BUTTON: tmp = mouse_x1_pressed; if (reset_count) { mouse_x1_pressed = 0; } break; case MOUSE_X2_BUTTON: tmp = mouse_x2_pressed; if (reset_count) { mouse_x2_pressed = 0; } break; } // end switch SDL_UnlockMutex( mouse_lock ); return tmp; } // mouse_up_count() returns the number of times button n has gone from down to up // since the last call // // parameters: n - button of mouse (see #define's in mouse.h) // int mouse_up_count(const CC_bind &bind) { if (bind.get_cid() != CID_MOUSE) { return 0; } int n = 1 << bind.get_btn(); return mouse_up_count(n); } int mouse_up_count(int n) { int tmp = 0; if ( !mouse_inited ) return 0; if ( (n < LOWEST_MOUSE_BUTTON) || (n > HIGHEST_MOUSE_BUTTON)) return 0; SDL_LockMutex( mouse_lock ); switch (n) { case MOUSE_LEFT_BUTTON: tmp = mouse_left_up; mouse_left_up = 0; break; case MOUSE_RIGHT_BUTTON: tmp = mouse_right_up; mouse_right_up = 0; break; case MOUSE_MIDDLE_BUTTON: tmp = mouse_middle_up; mouse_middle_up = 0; break; case MOUSE_X1_BUTTON: tmp = mouse_x1_up; mouse_x1_up = 0; break; case MOUSE_X2_BUTTON: tmp = mouse_x2_up; mouse_x2_up = 0; break; default: Assert(0); // can't happen break; } // end switch SDL_UnlockMutex( mouse_lock ); return tmp; } // returns 1 if mouse button btn is down, 0 otherwise int mouse_down(const CC_bind &bind, bool must_be_wheel) { // Bail if the incoming bind is not the right CID according to mouse-fly mode auto CID = bind.get_cid(); if (Use_mouse_to_fly) { // Mouse is Joy0 in this mode if (CID != CID_JOY0) { return 0; } } else { // Mouse is Mouse in this mode if (CID != CID_MOUSE) { return 0; } } int btn = bind.get_btn(); if (btn >= MOUSE_NUM_BUTTONS) { return 0; } btn = 1 << btn; return mouse_down(btn, must_be_wheel); } int mouse_down(int btn, bool must_be_wheel) { if ( !mouse_inited ) return 0; // Bail if not a button or wheel direction if ((btn < LOWEST_MOUSE_BUTTON) || (btn > HIGHEST_MOUSE_WHEEL)) return 0; // Bail if needs to be a mouse wheel and is not --wookieejedi if (must_be_wheel && (btn < LOWEST_MOUSE_WHEEL || btn > HIGHEST_MOUSE_WHEEL)) return 0; SDL_LockMutex( mouse_lock ); int tmp; if (mouse_flags & btn) { tmp = 1; if ((btn >= LOWEST_MOUSE_WHEEL) && (btn <= HIGHEST_MOUSE_WHEEL)) { mousewheel_decay(btn); } } else { tmp = 0; } SDL_UnlockMutex( mouse_lock ); return tmp; } void mouse_get_delta(int *dx, int *dy, int *dz) { if (dx) *dx = Mouse_dx; if (dy) *dy = Mouse_dy; if (dz) *dz = Mouse_dz; } void mouse_get_wheel_delta(int* dx, int* dy) { if (dx) *dx = Mouse_wheel_dx; if (dy) *dy = Mouse_wheel_dy; } // Forces the actual windows cursor to be at (x,y). This may be independent of our tracked (x,y) mouse pos. void mouse_force_pos(float x, float y) { if (os_foreground()) { // only mess with windows's mouse if we are in control of it gr_render_to_window_pos(x, y); SDL_WarpMouseInWindow(os::getSDLMainWindow(), x, y); } } // Reset deltas so we don't have duplicate mouse deltas void mouse_reset_deltas() { Mouse_dx = Mouse_dy = Mouse_dz = 0; Mouse_wheel_dx = Mouse_wheel_dy = 0; } void mouse_event(float x, float y, float dx, float dy) { Mouse_x = fl2i(x); Mouse_y = fl2i(y); // Add up these delta values so we don't overwrite previous events, // should be reset in gr_flip my mouse_reset_deltas() Mouse_dx += fl2i(dx); Mouse_dy += fl2i(dy); if ((Mouse_dx != 0 || Mouse_dy != 0) && scripting::hooks::OnMouseMoved->isActive()) { scripting::hooks::OnMouseMoved->run(); } } int mouse_get_pos(int *xpos, int *ypos) { int flags; if (!Mouse_in_focus) { if (xpos) *xpos = Mouse_x; if (ypos) *ypos = Mouse_y; return 0; } if (!mouse_inited) { *xpos = *ypos = 0; return 0; } flags = mouse_flags; if (Mouse_x < 0){ Mouse_x = 0; } if (Mouse_y < 0){ Mouse_y = 0; } if (Mouse_x >= gr_screen.max_w){ Mouse_x = gr_screen.max_w - 1; } if (Mouse_y >= gr_screen.max_h){ Mouse_y = gr_screen.max_h - 1; } if (xpos){ *xpos = Mouse_x; } if (ypos){ *ypos = Mouse_y; } return flags; } int mouse_get_pos_unscaled( int *xpos, int *ypos ) { int flags = mouse_get_pos( xpos, ypos ); gr_unsize_screen_pos( (xpos) ? xpos : NULL, (ypos) ? ypos : NULL, NULL, NULL, GR_RESIZE_MENU ); return flags; } void mouse_get_real_pos(int *mx, int *my) { float fx, fy; SDL_GetMouseState(&fx, &fy); gr_window_to_render_pos(fx, fy); if (mx) *mx = fl2i(fx); if (my) *my = fl2i(fy); } void mouse_set_pos(int xpos, int ypos) { mouse_force_pos(i2fl(xpos), i2fl(ypos)); } void mousewheel_motion(float x, float y, bool reversed) { if (reversed) { x = -x; y = -y; } Mouse_wheel_x += fl2i(x); Mouse_wheel_y += fl2i(y); // Used for tracking the actual movement of the wheel, not just the current position Mouse_wheel_dx += fl2i(x); Mouse_wheel_dy += fl2i(y); // These nested if's should take care of all edge cases. // Since x and y's magnitudes can be larger than 1, it is possible to ignore the idle state if (Mouse_wheel_y > 0) { // UP mouse_flags |= MOUSE_WHEEL_UP; mouse_flags &= ~MOUSE_WHEEL_DOWN; } else if (Mouse_wheel_y < 0) { // DOWN mouse_flags |= MOUSE_WHEEL_DOWN; mouse_flags &= ~MOUSE_WHEEL_UP; } else { mouse_flags &= ~(MOUSE_WHEEL_UP | MOUSE_WHEEL_DOWN); } if (Mouse_wheel_x > 0) { // RIGHT mouse_flags |= MOUSE_WHEEL_RIGHT; mouse_flags &= ~MOUSE_WHEEL_LEFT; } else if (Mouse_wheel_x < 0) { // LEFT mouse_flags |= MOUSE_WHEEL_LEFT; mouse_flags &= ~MOUSE_WHEEL_RIGHT; } else { mouse_flags &= ~(MOUSE_WHEEL_RIGHT | MOUSE_WHEEL_LEFT); } OnMouseWheelHook->run(scripting::hook_param_list(scripting::hook_param("MouseWheelY", 'i', static_cast(y)), scripting::hook_param("MouseWheelX", 'i', static_cast(x)))); } void mousewheel_decay(int btn) { if (btn & MOUSE_WHEEL_UP) { Mouse_wheel_y -= 1; } if (btn & MOUSE_WHEEL_DOWN) { Mouse_wheel_y += 1; } if (btn & MOUSE_WHEEL_LEFT) { Mouse_wheel_x -= 1; } if (btn & MOUSE_WHEEL_RIGHT) { Mouse_wheel_x += 1; } if (Mouse_wheel_x == 0) { mouse_flags &= ~(MOUSE_WHEEL_UP | MOUSE_WHEEL_DOWN); } if (Mouse_wheel_y == 0) { mouse_flags &= ~(MOUSE_WHEEL_RIGHT | MOUSE_WHEEL_LEFT); } } short bit_distance(short x) { short i; const short max_dist = sizeof(short) * 8; for (i = 0; i < max_dist; ++i, x >>= 1) { if (x & 0x01) { break; } } if (i >= max_dist) { return -1; } return i; }