Mouse Wheel Scroll Message

Started by KK20, June 28, 2016, 12:08:34 am

Previous topic - Next topic

KK20

June 28, 2016, 12:08:34 am Last Edit: August 22, 2016, 02:05:44 pm by KK20
EDIT: Script: http://forum.chaos-project.com/index.php/topic,15450.0.html

Overall goal is to know, with a script call, how many times (in which direction) the mouse wheel has scrolled.

I started off with seeing if someone already made something in RPG Maker, and found this at the top of the list:
http://pastebin.com/raw/q4TcSE1h
GetMessage, from what I understand, waits until a message is received before processing any further. The script does work (with some missed inputs here and there), but FPS takes a massive hit.

I then found PeekMessage, which I thought functioned much like GetMessage but without the waiting. Turns out that was wrong; I wasn't getting any mouse wheel messages (unless I furiously moved the mouse around and scrolled at the same time, in which I received the message every now and then).

A callback sounds like the best solution, but I don't really have any experience with setting those up. I looked at this and am not sure if I can figure out how to incorporate it into a DLL.
http://stackoverflow.com/a/21961482

How difficult is this really?


EDIT:

Okay so the stackoverflow actually helped a lot. After fumbling around with it, I figured out how to put it into a separate thread, accumulate the number of times the wheel scrolled, and have another DLL method return the number of times (positive for scrolling up, negative for down). I do not know the elegance of this nor the practicality, so I'm hoping someone can give valuable input. Game runs at 60 FPS and is registering every instance of a mouse wheel scroll.

I guess the ONLY bad thing right now is it accepts the mouse scroll regardless of what the active window is. So if your game pauses when it's not active, you scroll the wheel a lot, and click back on the game, the first call could be a massive accumulation. I could just make it return only -1 or 1 instead, but whatever.
Spoiler: ShowHide


// Since I'm doing this in CodeBlocks, I found out you have to do this for RAWINPUTDEVICE to work.
// Visual Studios already has a high enough version that this is unnecessary if building in it.
#ifdef __MINGW32__
#   define _WIN32_WINNT 0x0501
#endif

#include "main.h"
#include <windows.h>
#include <stdio.h>

int delta = 0;

LRESULT CALLBACK MyWindowProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) {

    switch ( uiMsg ) {

        case WM_INPUT: {
            UINT dwSize;
            HRAWINPUT hRawInput = reinterpret_cast<HRAWINPUT>( lParam );
            UINT uiRetCode = GetRawInputData( hRawInput, RID_INPUT, NULL, &dwSize,
                                              static_cast<UINT>( sizeof( RAWINPUTHEADER ) ) );
            if ( uiRetCode != 0xffffffff ) {
                LPBYTE lpb = new BYTE[ dwSize ];
                uiRetCode = GetRawInputData( hRawInput, RID_INPUT, lpb, &dwSize,
                                             static_cast<UINT>( sizeof( RAWINPUTHEADER ) ) );
                if ( uiRetCode > 0 ) {
                    RAWINPUT* praw = reinterpret_cast<RAWINPUT*>( lpb );
                    if ( praw->header.dwType == RIM_TYPEMOUSE ) {
                        if ( praw->data.mouse.usButtonFlags & RI_MOUSE_WHEEL ) {
                            signed int siDelta = static_cast<SHORT>( praw->data.mouse.usButtonData );
                            delta += siDelta / 120;
                        }
                    }
                }
                delete[] lpb;
            }
            break;

        } // WM_INPUT

        default:
            return DefWindowProc( hWnd, uiMsg, wParam, lParam );
    }

    return 0;
}

DWORD WINAPI CheckWheel(LPVOID lpParam)
{
    HWND hWnd;
    WNDCLASS WndClass;

    memset( &WndClass, 0, sizeof( WndClass ) );
    WndClass.hInstance = GetModuleHandle( NULL );
    WndClass.lpszClassName = "MyRawInputClass";
    WndClass.lpfnWndProc = MyWindowProc;
    RegisterClass( &WndClass );

    hWnd = CreateWindow( WndClass.lpszClassName, NULL, 0, 0, 0, 0, 0,
                              HWND_MESSAGE, 0, WndClass.hInstance, 0 );

    RAWINPUTDEVICE RawInputDevice;
    RawInputDevice.usUsagePage = 0x01; // Generic Desktop Controls
    RawInputDevice.usUsage = 0x02; // Mouse
    RawInputDevice.dwFlags = RIDEV_INPUTSINK;
    RawInputDevice.hwndTarget = hWnd;
    BOOL bWin32Success = RegisterRawInputDevices( &RawInputDevice, 1,
                                                  static_cast<UINT>( sizeof( RAWINPUTHEADER ) ) );

    BOOL bRet;
    MSG msg;

    while( ( bRet = GetMessage( &msg, NULL, 0, 0 ) ) != 0 ) {
        if (bRet != -1) {
            DispatchMessage(&msg);
        }
    }

    // NO GUI, UNREACHABLE
    DestroyWindow( hWnd );
    UnregisterClass( WndClass.lpszClassName, WndClass.hInstance );

    return 0;
}

// Call this method first from the game
int DLL_EXPORT Start()
{
    if (CreateThread(NULL, 0, CheckWheel, NULL, 0, 0) == NULL) return -1;
    return 0;
}
// Call this to get the number of times the wheel scrolled in a certain direction
// + for UP
// - for DOWN
int DLL_EXPORT WheelDelta()
{
    int copyDelta = delta;
    delta = 0;
    return copyDelta;
}


Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

June 28, 2016, 02:32:12 am #1 Last Edit: June 28, 2016, 02:35:12 am by Blizzard
https://github.com/AprilAndFriends/april/blob/master/src/windowsystems/Win32/Win32_Window.cpp

Just find Win32_Window::childProcessCallback() and you'll see how we handle them in April. Just find the case for WM_MOUSEWHEEL.
Win32_Window::checkEvents() might also be useful to figure out how to localize it to one window.
You can prevent accumulation by handling focus-change and ignoring events from there, but usually inactive windows shouldn't receive these events in the first place.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

KK20

Quotebut usually inactive windows shouldn't receive these events in the first place.

That's because the code I used, from what I can understand, makes an invisible window in another thread. All the mouse wheel messages are being captured by this and modify a global int. I can access its value with a simple DLL call afterwards.
I have written code based off of F0's NoDeactivate DLL and can easily incorporate some of the ideas from it to make a simple boolean gate.

After looking at April, my question now would be how would you set the game window to have this callback to listen for WM_MOUSEWHEEL messages. All examples I have seen have just been creating a new WNDCLASS structure and setting lpfnWndProc to the callback function. What goes in the DLL and what can be done from Ruby?

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

I think that all you need is the HWND. But when I think about it, it's likely that RMXP's exe might be eating up the event before you can fetch it.
This post might help a bit, though: http://forum.chaos-project.com/index.php/topic,15396.msg194322.html#msg194322
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

KK20

June 28, 2016, 02:47:12 pm #4 Last Edit: June 28, 2016, 04:28:36 pm by KK20
I think I almost got it (disregard the fact I'm at work).

Pretty much copied the code you provided with that topic (forgot that existed) but used GetMsgProc instead. I'm receiving mouse wheel messages now, but it seems like it happens twice. I confirmed this with the message window I commented out. EDIT: Turned out it was wParam returning PM_REMOVE the first time and PM_NOREMOVE the second time. So just made it only process PM_REMOVE.

DLL:

#include "main.h"
//#include <stdio.h>

int delta = 0;
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (code >= 0)
    {
        MSG * msg = (MSG*)lParam;
        if (msg->message == WM_MOUSEWHEEL && wParam == PM_REMOVE)
        {
            short d = msg->wParam >> 16;
            delta += d / 120;
            //char buff[100];
            //sprintf(buff, "name is:%d",d);
            //MessageBoxA(0, buff, "HELLO THERE", 0);
        }
    }
    return CallNextHookEx(NULL, code, wParam, lParam);
}

int initialized = 0;
int DLL_EXPORT Initialize(HWND hwnd) // assuming you get HWND from RUBY using FindWindowA() with the above Ruby code (self.get_window)
{
    if (!initialized)
    {
        HINSTANCE hinstance = (HINSTANCE)GetWindowLongW(hwnd, GWL_HINSTANCE);
        DWORD threadId = GetCurrentThreadId();
        if (hinstance != 0 && threadId != 0 && SetWindowsHookExW(WH_GETMESSAGE, GetMsgProc, hinstance, threadId) != 0)
        {
            initialized = 1;
        }
    }
    return initialized;
}

int DLL_EXPORT WheelDelta()
{
    int copydelta = delta;
    delta = 0;
    return copydelta;
}



Ruby:

module MySpecialCode
 
  def self.get_window
    game_name = "\0" * 256
    read_ini = Win32API.new('kernel32', 'GetPrivateProfileStringA', ['p', 'p', 'p', 'p', 'l', 'p'], 'l')
    read_ini.call('Game', 'Title', '', game_name, 255, './Game.ini')
    game_name += "\0"
    find_window = Win32API.new('user32', 'FindWindowA', ['p', 'p'], 'L')
    return find_window.call('RGSS Player', game_name)
  end
 
  Delta = Win32API.new('GameCallback', 'WheelDelta', '', 'i')
 
  def self.mousewheel
    return Delta.call
  end
 
  window = self.get_window
  if window != 0
    Win32API.new('GameCallback', 'Initialize', 'l', 'v').call(window)
  end
 
 
end

module Input
  class << self
    alias get_mouse_wheel update
  end
 
  def self.update
    a = MySpecialCode.mousewheel
    p a if a != 0
    get_mouse_wheel
  end
end

So every time I move the wheel, I print a message box in Ruby. The first message will be +/- 2. Now if I keep the message box open but scroll the wheel, say, 3 times, and then close the message box, I will immediately get another message box printing 3 instead of 6.
EDIT: I guess the last thing I need to handle is if the game window is currently in focus, because mouse wheel messages are still processed when there's a message box active. Should be easy.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

Yeah, just check the focus-handling in Win32_Window in April. There are a few minor problems, but they are all solved properly in our implementation.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

KK20

Yep, I was able to get it working re-using some of the NoDeactivate code. Still playing around with it to see what I really need.

Definitely will post a script when I get the chance. Thanks for the feedback :D

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

KK20

So after all of that, I found out that F0's Rpg.NET already supported it xD Thanks to finding this topic:
http://www.pokecommunity.com/showthread.php?t=319562
Whatever, .NET-less is okay too.

Had Zexion test the DLL under XPA in his game. Frame rate dropped to ~20 for him, which is lulzy (how can his computer be that bad?).

So the plan is to make an extension off your Mouse Controller. While I was at it, I was trying to figure out how to not make the cursor disappear when the mouse is idle (curiousity really). The only almost-good-enough solution was to just call SetCursorPos at where the cursor currently is whenever GetCursorInfo's CURSORINFO#flags returned 0 (this is constantly checked in a separate thread). It mostly works but you can see the cursor blink. I could just constantly call SetCursorPos at its current location...but dunno the implications to that.

Just found it weird that I couldn't really find the reason for this behavior. It has nothing to do with ShowCursor (as repeated calls to it just incremented the display counter without showing the cursor from hiding) and I'm not sure if there's any messages or what have you triggering its disappearance.

Looking at your script's version notes, I'm not sure if you experienced and researched this yourself. I'm also not sure about the fullscreen issue you mention either.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

I think the automatic cursor hiding in RMXP is actually a Windows feature.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.