Created
November 1, 2021 11:57
-
-
Save Egor-Skriptunoff/765036ce4a3dc23d94c53267e682e94e to your computer and use it in GitHub Desktop.
Scroll for Logitech Trackman Marble trackball
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------- | |
-- Scroll for Logitech Trackman Marble | |
-------------------------------------- | |
-- This script is based on the project "MarbleScroll for Logitech Trackman Marble" by Primož. | |
-- https://www.fewprojects.com/marblescroll-for-logitech-trackman-marble/ | |
-- The original code was rewritten to LuaJIT FFI and slightly modified by Egor Skriptunoff. | |
-- Logitech Trackman Marble trackball has only 4 buttons: LMB, RMB, Back, Forward. | |
-- It lacks scroll wheel and middle button. | |
-- This script adds the following possibilities: | |
-- | |
-- buttons combination function | |
-- ------------------- ------------- | |
-- Back alone = Back | |
-- Forward alone = Forward | |
-- Back + Forward = Middle Mouse Button | |
-- Back + Marble turn = Scroll (horizontal, vertical, diagonal) | |
-- Forward + Marble turn = Fast Scroll (speed x10) | |
-- | |
-- How to run the script: | |
-- wluajit.exe marble_scroll.lua | |
-- | |
-- The script runs in the background, it creates neither console nor GUI window. | |
-- | |
-- How to install: | |
-- 1) If you use SetPoint, you need to set "Generic Button" as a task for the buttons 3 and 4: | |
-- https://www.fewprojects.com/files/marblescroll/setpoint.jpg | |
-- 2) Get windowless LuaJIT: | |
-- either download binary files (wluajit.exe + lua51.dll) from this repo: | |
-- https://github.com/Egor-Skriptunoff/LGS_extension | |
-- or build LuaJIT from sources using instructions at: | |
-- https://gist.github.com/Egor-Skriptunoff/22bf55c1abe44d7825605e132e48c084 | |
-- 3) Place the 3 files | |
-- * wluajit.exe | |
-- * lua51.dll | |
-- * marble_scroll.lua | |
-- in any folder on your hard drive and create a shortcut for | |
-- wluajit.exe marble_scroll.lua | |
-- 4) Put the shortcut in Startup folder for auto start at reboot. | |
---------------------------- SETTINGS ----------------------------- | |
local sens_x = 20 -- mouse move required for one horizontal scroll | |
local sens_y = 30 -- mouse move required for one vertical scroll | |
local fast_scroll_factor = 10 | |
------------------------------------------------------------------- | |
-- Script version: November 1st 2021 | |
local ffi = require'ffi' | |
local C, AND, HEX, tonumber = ffi.C, bit.band, bit.tohex, tonumber | |
ffi.cdef[[ | |
// common WinAPI definitions | |
typedef int BOOL; | |
typedef uint32_t DWORD; | |
typedef intptr_t LRESULT; | |
typedef intptr_t LPARAM; | |
typedef uintptr_t WPARAM; | |
typedef intptr_t HANDLE; | |
typedef HANDLE HINSTANCE; | |
typedef HANDLE HWND; | |
typedef HANDLE HHOOK; | |
// Low Level Mouse Hook | |
typedef struct { | |
int x; | |
int y; | |
int16_t reserved; | |
int16_t mouseData_high; | |
DWORD flags; | |
DWORD time; | |
uintptr_t dwExtraInfo; | |
} MSLLHOOKSTRUCT; | |
typedef LRESULT (__stdcall * MSHOOKPROC)( | |
int nCode, | |
WPARAM wParam, | |
MSLLHOOKSTRUCT * lParam | |
); | |
// Hook Functions | |
HHOOK SetWindowsHookExA( | |
int idHook, | |
MSHOOKPROC lpfn, | |
HINSTANCE hmod, | |
DWORD dwThreadId | |
); | |
BOOL UnhookWindowsHookEx(HHOOK hhk); | |
LRESULT CallNextHookEx( | |
HHOOK hhk, | |
int nCode, | |
WPARAM wParam, | |
void * lParam | |
); | |
// Misc Functions | |
void mouse_event( | |
DWORD dwFlags, | |
int dx, | |
int dy, | |
DWORD dwData, | |
uintptr_t dwExtraInfo | |
); | |
typedef struct { int x, y; } POINT; | |
HWND WindowFromPoint(POINT Point); | |
HWND GetForegroundWindow(); | |
BOOL SetForegroundWindow(HWND hWnd); | |
HWND GetAncestor(HWND hwnd, DWORD gaFlags); | |
BOOL GetMessageA( | |
void * lpMsg, | |
HWND hWnd, | |
DWORD wMsgFilterMin, | |
DWORD wMsgFilterMax | |
); | |
]] | |
local POINT = ffi.typeof"POINT" | |
local back_is_down, forward_is_down, pure_x_btn, middle_is_down, last_x, last_y, dx, dy | |
local function LowLevelMouseProc(nCode, wParam, lParam) | |
if nCode == 0 then -- HC_ACTION | |
local msg_id = tonumber(wParam) | |
if msg_id == 0x020B then -- WM_XBUTTONDOWN | |
if not (back_is_down or forward_is_down) then | |
pure_x_btn = true | |
last_x, last_y, dx, dy = lParam.x, lParam.y, 0, 0 | |
-- for scrolling in window under mouse pointer | |
local pt = POINT(last_x, last_y) | |
local focus_window = C.WindowFromPoint(pt) | |
local foreground_window = C.GetForegroundWindow() | |
-- only focus if window is not already focused | |
if C.GetAncestor(focus_window, 3) ~= C.GetAncestor(foreground_window, 3) then -- GA_ROOTOWNER | |
C.SetForegroundWindow(focus_window) | |
end | |
end | |
local x_btn_mask = lParam.mouseData_high | |
back_is_down = back_is_down or AND(x_btn_mask, 1) ~= 0 | |
forward_is_down = forward_is_down or AND(x_btn_mask, 2) ~= 0 | |
if back_is_down and forward_is_down then | |
C.mouse_event(0x0020, lParam.x, lParam.y, 0, 0) -- MOUSEEVENTF_MIDDLEDOWN | |
middle_is_down = true | |
pure_x_btn = false | |
end | |
return 1 | |
elseif msg_id == 0x020C then -- WM_XBUTTONUP | |
if middle_is_down then | |
C.mouse_event(0x0040, lParam.x, lParam.y, 0, 0) -- MOUSEEVENTF_MIDDLEUP | |
middle_is_down = false | |
end | |
local x_btn_mask = lParam.mouseData_high | |
back_is_down = back_is_down and AND(x_btn_mask, 1) == 0 | |
forward_is_down = forward_is_down and AND(x_btn_mask, 2) == 0 | |
if back_is_down or forward_is_down or not pure_x_btn then | |
return 1 | |
end | |
elseif msg_id == 0x0200 and (back_is_down or forward_is_down) then -- WM_MOUSEMOVE | |
local speed_factor = forward_is_down and fast_scroll_factor or 1 | |
dx = dx + (lParam.x - last_x) * speed_factor | |
dy = dy + (lParam.y - last_y) * speed_factor | |
last_x, last_y = lParam.x, lParam.y | |
repeat | |
local wheel_dist | |
if dx >= sens_x then | |
dx = dx - sens_x | |
wheel_dist = 120 | |
elseif dx <= -sens_x then | |
dx = dx + sens_x | |
wheel_dist = -120 | |
end | |
if wheel_dist then | |
C.mouse_event(0x1000, 0, 0, wheel_dist, 0) -- MOUSEEVENTF_HWHEEL | |
pure_x_btn = false | |
wheel_dist = false | |
end | |
if dy >= sens_y then | |
dy = dy - sens_y | |
wheel_dist = -120 | |
elseif dy <= -sens_y then | |
dy = dy + sens_y | |
wheel_dist = 120 | |
end | |
if wheel_dist then | |
C.mouse_event(0x0800, 0, 0, wheel_dist, 0) -- MOUSEEVENTF_WHEEL | |
pure_x_btn = false | |
end | |
until wheel_dist == nil | |
end | |
end | |
return C.CallNextHookEx(0, nCode, wParam, lParam) | |
end | |
local ms_hook = C.SetWindowsHookExA(14, LowLevelMouseProc, 0, 0) -- WH_MOUSE_LL | |
if ms_hook ~= 0 then | |
local msg = ffi.new("intptr_t[?]", 8) -- this memory block must be large enough to hold the MSG struct | |
repeat | |
local res = C.GetMessageA(msg, 0, 0, 0) | |
until res == 0 or res == -1 | |
C.UnhookWindowsHookEx(ms_hook) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment