Universal Control Remapper (UCR) - v0.1.22 28th Oct 2018

Post gaming related scripts
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 10:40

@ Jamz - Not at the moment, no
The reason being, I cannot HIDE the original input.

I can do a similar thing for sensitivity amplifications, because I send additional output in addition to the input, but reversing the input is not something I cannot currently do.

HOWEVER. This is likely to change once Nefarius' HidGuardian is finished - this should allow us to hide the physical mouse from applications, so we can then send inverted virtual output without the original input cancelling it out.

I have no idea when Nefarius will release a working version tho :(

It also may be possible with SetWindowsHookEx Dll Calls (ie low-level mouse hooks)

@Tharg
I have extended the plugin to support up to 10 bands.
I will be adding it to the main UCR distro as-is, I think this is too useful to leave out, now that I have written it...
Tharg
Posts: 4
Joined: 05 Mar 2017, 08:42

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 11:10

Thanks again. With a bit of tweaking this is doing exactly what I wanted.

I realised after trying your new plugin that I'd (tried to) ask for slightly the wrong thing - actually the buttons don't need to be held, and the HUD element doesn't disappear on releasing the key. It goes when you press a the same key again, or open another HUD element, or press the back button. Poor comments in my AHK script led me astray :(. I was in fact doing this in a round about way by pressing a different button depending on the direction a band was entered. I didn't use the back button, but when I entered the middle band from the "b" band I'd press "b" again, or if from the "c" band I'd press "c" again.

At first I couldn't quite get it working with your plugin because if my initial misunderstandings, but once I set the middle band to send the "back" key it all works perfectly, so far as I can tell.

Do you have a donation link? I'd like to make a small donation if so.
Last edited by Tharg on 05 Mar 2017, 11:16, edited 1 time in total.
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 11:15

OK, I will look into whether I can add an option to only tap the button. Is this a show-stopper for you using the plugin at the moment?

I am currently redirecting donations to Nefarius, to help fund his ViGEm project: https://www.paypal.com/cgi-bin/webscr?c ... Y8RYKZZ28E
Pls mention me or UCR in the donation text, thankyou!
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 12:00

It was pretty easy to implement. New version here

I would delete the copy in your user folder and save this one to the core folder - it will be included in the next release.

I added a "Tap Mode" checkbox, with an Editbox to select how long to hold the button for (Defaults to 50ms).

The releasing of buttons is asynchronous, so if you enter a really high value, you could end up with more than one button pressed at once.
I did this, because many games do not recognize keys held for less than ~50ms, so if you moved the axis too quickly, it might not hit one of the buttons, and some use-cases may absolutely need the buttons for bands that you passed through to be pressed for at least 50ms.
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 12:21

Final update for today.
Renamed "Band" to "Range"
Fixed bug where there were gaps in the range unless you used floating point values (eg 0-9.9999, 10-19.999).
Now it rounds the input, so you can use integers for the ranges.

Improved behavior when toggling Tap Mode - it now presses/releases buttons as appropriate.

https://raw.githubusercontent.com/evilC ... uttons.ahk

Image
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 12:47

Jamz wrote:Hi, i'm hopeing UCR can solve my issue but can't seem to find a way, maybe I'm missing it?

I'm looking to invert the axis of a second mouse, is this possible?

My setup: A virtual table top/TV embedded into a table. So I want one mouse on one side of the table as normal, and a second mouse on the opposite side with it's x and y axis reversed (as they are viewing windows upside down). I really don't need a separate cursor, just the axis changed for a second mouse. (Both mice are logitech 5 button mice)
Hi, jamz.
I just made a script for multiple cursors, it is very simple to add inverted axis to that, as long as you don't move both mice simultaneously, the cursor acts pretty normal. This is is a modified version if you like to try. You invert the axis by setting a negative sensitivity. (Left edit is for x, right for y). You also need to edit the the confinment rectangle under the coordinates tab. More instructions: here.

Code: Select all

; TODO, 
;		remove debug hotkey - and ea() func
;		reconsider choise of default cursor when A_Cursor is Unknown.
;		Manage click up when more than mouse is moving and click is down.... 
class Cursor extends _UCR.Classes.Plugin
{
	type:="Cursor"
	description:="Mulitple cursor controller"
	
	static AllCursors:=[]											; Tracks all instances of this plugin
	static MouseIdCounter:=[]										; For tracking multiple active cursor from the same mouse id.
	static doNothingFunc:=ObjBindMethod(Cursor, "doNothing")		; For disabling mouse buttons.

	init()
	{
		this.LoadCursor()	; Loads cursor handles.
		
		; Gui 
		
		; Some control(s) are placed at rowX, colY, defined here:
		row0:=" y5 ",  		row0_:=" y2 "
		row1:=" y35 ",  	row1_:=" y32 "
		row2:=" y65 ",  	row2_:=" y62 "
		row3:=" y95 ",  	row3_:=" y92 "
		row4:=" y125 ", 	row4_:=" y123 "
		row5:=" y155 ", 	row5_:=" y153 "
		row6:=" y185 ", 	row6_:=" y183 "
		row7:=" y215 ", 	row7_:=" y213 "
		
		; Columns for gui placement
		col1:=" x25 ", col2:=" x145 ", col3:=" x260 ", col4:=" x375 ", col5:=" x490 "
		
		Gui, Add, Tab2, w650 h65, % "Main|Cycle|Coordinates|Help"
		Gui, Tab, 1
; Mouse selection
		Gui, Add, Button, % " w100 h25 hwndselectMouseButton", % "Select mouse"
		this.selectMouseButton:=selectMouseButton
		gFunc := this.SelectMouseInit.Bind(this)
		GuiControl, +g, % this.selectMouseButton, % gFunc
		this.AddControl("Edit", "MouseIdEdit", this.MouseIdSet.Bind(this),"x+10 yp+3 w75 center")
		this.AddControl("InputButton", "toggle", 0, this.toggle.Bind(this),"x+10 h25")
		
		; Toggle light
		this.AddControl("ButtonPreview", "bp1", 0,0, "x+10")
		; Sensitivity
		Gui,Add,Text,x+10 yp+3,Sensitivity:
		this.AddControl("Edit", "sensEditX", this.sensChanged.Bind(this,1),"x+10 yp-3 w35 center",1)
		this.AddControl("Edit", "sensEditY", this.sensChanged.Bind(this,2),"x+5 w35 center",1)
		; Hide/show option
		this.AddControl("Checkbox", "hideWhenInactiveCB", this.hideWhenInactiveCBChanged.Bind(this),"x+10 yp+3 checked", "Hide when inactive")
		Gui, Tab, 2
		Gui,Add,Text,,Cycle cursors:
		this.AddControl("InputButton", "cycleCursor", 0, this.cycleCursor.Bind(this),"x+10 h25")
		this.AddControl("Checkbox", "cycleCursorCB", 0,"x+10 yp+3 checked", "Cycle by id")
; Coordinates tab
		Gui, Tab, 3
		Gui,Add,Text, x+5,Confine to: (x1,y1)
		this.AddControl("Edit", "confineX1", this.confineEditChanged.Bind(this,1),"x+5 yp-3 w35 number center",0)
		this.AddControl("Edit", "confineY1", this.confineEditChanged.Bind(this,2),"x+5 w35 number center",0)
		Gui,Add,Text, x+5 yp+3,(x2,y2)
		this.AddControl("Edit", "confineX2", this.confineEditChanged.Bind(this,3),"x+5 yp-3 w35 number center",A_ScreenWidth)
		this.AddControl("Edit", "confineY2", this.confineEditChanged.Bind(this,4),"x+5 w35 number center",A_ScreenHeight)
		Gui,Add,Text, x+5 yp+3,Start at: (x,y)
		this.AddControl("Edit", "startX1", this.startEditChanged.Bind(this,1),"x+5 yp-3 w35 number center",A_ScreenWidth//2)
		this.AddControl("Edit", "startY1", this.startEditChanged.Bind(this,2),"x+5 w35 number center",A_ScreenHeight//2)
		
		Gui, Add, Button, % "x+5 hwndresetButton", % "Reset"
		this.resetButton:=resetButton
		gFunc := this.resetCoords.Bind(this)
		GuiControl, +g, % this.resetButton, % gFunc
		
; Help tab
		Gui, Tab, 4
		Gui, Font, s8 w400, Courier New
		Gui, add, Link, % row1 col1, % 		"Author: Helgef (2017-03-03). "
										.	"Instructions are available at: <a href=""https://autohotkey.com/boards/viewtopic.php?f=19&t=24538"">AutoHotkey.com forum.</a>`n"
		; Misc.
		this.createCursor()				; Make the cursor's gui.
		Cursor.AllCursors.Push(this)	; The plugin is self aware.
		
		this.hasBeenToggled:=0
		this.hideWhenInactive && !this.toggleState ? this.hideCursor() : this.showCursor()
		; DEBUG - REMOVE
	;	ff:=ObjBindMethod(Cursor,"ea")
	;	try
	;		Hotkey, Esc, % ff

	}

	; Clean up for UCR.
	onClose(){
		GuiControl, -g, % this.selectMouseButton
		GuiControl, -g, % this.resetButton
		for k, cur in Cursor.AllCursors																			; Find position in AllCursors list.
			if (cur=this)
				break
		Cursor.AllCursors.RemoveAt(k)																			; Remove from AllCursors list
		Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? --Cursor.MouseIdCounter[this.SelectedMouse] : ""		; Decerment mouse id counter
		if !Cursor.AllCursors.length()
			Cursor.md.Delete(),Cursor.md:="",Cursor.toggleHotkeys("Off")										; Remove the mouse delta if no more cursor plugins exists.
		this.destroyCursor() 																					; Destroy the gui cursor.
		base.onClose() ;? 
	}
	
	; Gui functions
	; Coords tab
	confineEditChanged(num,val){	; Confinement rectangle.
		if (num=1)
			this.minX:=val
		else if (num=2)
			this.minY:=val
		else if (num=3)
			this.maxX:=val
		else if (num=4)
			this.maxY:=val
		return
	}
	startEditChanged(num,val){	; start position
		if (num=1)
			this.oX:=val
		else if (num=2)
			this.oY:=val
		return
	}
	resetCoords(){ ; Reset coords button
		this.GuiControls.confineX1.Set(0)
		this.GuiControls.confineY1.Set(0)
		this.GuiControls.confineX2.Set(A_ScreenWidth)
		this.GuiControls.confineY2.Set(A_ScreenHeight)
		
		this.GuiControls.startX1.Set(A_ScreenWidth//2)
		this.GuiControls.startY1.Set(A_ScreenHeight//2)
	}
	
	sensChanged(num,val){
		if val is number
		{
			if (num=1)				
				this.sensX:=val			; Set sensitivity x-axis
			else if (num=2)
				this.sensY:=val			; Set sensitivity y-axis
		}
		return
	}
	
	hideWhenInactiveCBChanged(state){
		state && !this.toggleState ? this.hideCursor() : this.showCursor() 	; Show/hide cursor to match setting.
		return this.hideWhenInactive:=state									; Indicates wether cursor is hidden/shown when inactive
	}
	
	createCursor(){
		if this.hWin
			return ;?
		this.x:=this.oX
		this.y:=this.oY
		Gui, new, +hwndhWin -caption +toolwindow +alwaysontop +E0x20
		this.hWin:=hWin
		Gui, % hWin ": Margin",0,0 
		Gui, % hWin ": Color", abcdef
		Gui, % hWin ": Add", Picture, % "x0 y0 hwndhPic", % "hIcon:*" this.hIcons["Arrow"]
		this.hPic:=hPic
		this.setCursor()
		this.hs:=Cursor.GetCursorHotspot()
		GuiControl,, % this.hPic, % "hIcon:*" this.hIcons[this.IDC]	; Consider to remove this.
		;Gui, % hWin ": Show", % "Hide NA x" this.x " y" this.y 
		this.hidden:=1
		WinSet, TransColor, abcdef 255, % "ahk_id " hWin
		return
	}
	
	; Handle mouse selection
	SelectMouseInit(){
		; Starts the mouse selection procedure.
		; How to:
		;			- Press the button
		;			- Move the mouse within 5 seconds.
		;			- Done!
		p:=this.SelectedMouse														; Previous mouse
		selectMouseMD:= new Cursor.MouseDelta(ObjBindMethod(this,"SelectMouse"))
		selectMouseMD.Start()
		timeout:=A_TickCount														; For timeout
		while (this.SelectedMouse=p && A_TickCount-timeout<5000)					; 5 second timeout.
			Sleep,-1
		if (this.SelectedMouse!=p){
			Cursor.MouseIdCounter.HasKey(p) ? --Cursor.MouseIdCounter[p] : ""		; A new mouse chosen
			Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? ++Cursor.MouseIdCounter[this.SelectedMouse] : (Cursor.MouseIdCounter[this.SelectedMouse]:=1)
		}
		selectMouseMD.Delete()														; Delete the old mouse delta
		return
	}
	
	MouseIdSet(id){
		return this.SelectedMouse:=id
	}
	
	SelectMouse(MouseID,dx,dy) {
		; Callback function for temporary mouse delta instance.
		if (this.selectedMouse=MouseID)
			return
		this.GuiControls.MouseIdEdit.Set(MouseID)
		return
	}
	
	; destroy/hide/show/moveCursor() - these functions apply to the "gui cursor" not the real windows cursor.
	destroyCursor(){
		if !this.hWin
			return
		Gui, % this.hWin ": Destroy"
		return this.hWin:=""
	}
	
	hideCursor(){
		if !this.hWin
			return
		if this.hidden
			return
		Gui, % this.hWin ": Show", % "NA hide"
		this.hidden:=1
		return
	}
	
	showCursor(move:=0){
		; Consider toggle aot to avoid getting under other aot win.
		if !this.hWin
			return
		if (this.hidden && move)
			return this.showCursor()
		else if (!this.hidden && !move)
			return
		this.hidden:=0
		if (this.IDC!=this.pIDC)	; Only update if changed			
			GuiControl,, % this.hPic, % "hIcon:*" this.hIcons[this.IDC]
		this.pIDC:=this.IDC
		if !move
			Gui, % this.hWin ": Show", % "NA x" this.x-this.hs[1] " y" this.y-this.hs[2]			; Adjustment for hotspot.
		else
			WinMove, % "ahk_id " this.hWin,, this.x-this.hs[1], this.y-this.hs[2]
		return
	}
	
	moveCursor(){
		return this.showCursor(true)				
	}
	
	ea(){	; remove
		ExitApp
	}
	
	; For suppressing native mouse button functionality
	doNothing(){
		return
	}

	; Main togle
	toggle(state) {	
		if !state
			return
		if !this.hasBeenToggled {	; Add to MouseIdCounter on first time toggle on.
			Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? ++Cursor.MouseIdCounter[this.SelectedMouse] : (this.SelectedMouse ? (Cursor.MouseIdCounter[this.SelectedMouse]:=1) : "")
			this.hasBeenToggled:=true
		}
		this.toggleState:=!this.toggleState
		if (!Cursor.md && this.toggleState) {
			Cursor.md:=new Cursor.MouseDelta(ObjBindMethod(Cursor,"MoveCursors"))	; Create a new mousedelta for the Cursor class, if non exists.
		}
		Cursor.globalToggle(this.toggleState)
		if !this.toggleState {
			if (this.hideWhenInactive)	; Hide/show according to settings
				this.hideCursor()
			else
				this.showCursor()
			
		} else {
			this.showCursor()
		}
		this.GuiControls.bp1.SetState(this.toggleState)	; Green light when on (in UCR gui)
		return
	}
	
	globalToggle(state){
		; Sets the correct state of the MouseDelta: Cursor.md, depending on all toggleStates of all cursors in Cursor.AllCursors
		mdState:=Cursor.md.State
		if (state=mdState)							; State is matched, do nothing
			return
		else if (state && !mdState)	{				; State is on, and md is off, turn on md.
			Cursor.toggleHotkeys("On")
			Cursor.md.Start()
			return 
		}else if (!state && mdState) {				; State is off, but md is on, check if any other cursor is on, then do nothing, if no other is on, restore mouse functionality and turn off md.
			for k, cur in Cursor.AllCursors
				if cur.toggleState
					return
			Cursor.toggleHotkeys("Off")
			Cursor.md.Stop()
		}
		return
	}
	
	toggleHotkeys(state){
		; Normal mouse button functionality is suppressed while a cursor is active.
		hkfn:=Cursor.doNothingFunc
		Hotkey, *LButton,	% hkfn, % state
		Hotkey, *RButton, 	% hkfn, % state
		Hotkey, *MButton, 	% hkfn, % state
		Hotkey, *WheelUp, 	% hkfn, % state
		Hotkey, *WheelDown, % hkfn, % state
		Hotkey, *XButton1,	% hkfn, % state
		Hotkey, *XButton2,	% hkfn, % state
		if (state="On")					; Disables all mouse movement when "On"
			BlockInput, MouseMove
		else
			BlockInput, MouseMoveOff
		return
	}
	

	; Cycle cursors.
	cycleCursor(state){
		if !state								; Manage hotkey up event - do nothing.
			return
		if this.GuiControls.cycleCursorCB.Get() ; Only cycle same id
			this.cycleSame()
		else
			this.cycleAll()
	}
	cycleSame(){
		for k, cur in Cursor.AllCursors
			if (cur.selectedMouse!=this.selectedMouse) {	; Wrong id, continue
				continue
			} else if (cur.toggleState) {					; Turn off the first one that is on
				cur.toggle(1), oneOff:=1
			} else if (oneOff) {							; Now we can turn on the next
				cur.toggle(1), oneOn:=1
				break
			}
		if !oneOn											; No cursor was turned on, turn on the first with correct id.
			for k, cur in Cursor.AllCursors
				if (cur.selectedMouse=this.selectedMouse) {
					cur.toggle(1)
					break
				}
		return
	}
	cycleAll(){
		; Find the next cursor, turn it on
		for k, cur in Cursor.AllCursors
			if (cur.toggleState) {							; Turn off the first one that is on
				cur.toggle(1), oneOff:=1
			} else if (oneOff) {
				cur.toggle(1), oneOn:=1						; Turn on the next one
				break
			}
		if !oneOn
			Cursor.AllCursors[1].toggle(1)					; No cursor turned on, turn on the first one.
		return
	}
	
	MoveCursors(MouseID, dx, dy, usButtonFlags, usButtonData){
		; Move all cursors for the MouseId that generated the movement, dx,dy.
		; Confine to (x,y) ∈ [cur.MinW,cur.MaxW]x[cur.MinH,cur.MaxH]
		; Note: hide/show/move Cursor functions refer to the cursor GUI representation, not the actual (windows) cursor.
		Cursor.md.SetState(0)
		SetWinDelay,-1
		SetMouseDelay,-1
		CoordMode, Mouse, Screen
		idCtr:=Cursor.MouseIdCounter[MouseID]
		for k, cur in Cursor.AllCursors														; Consider all cursors. each "cur" is one instances of the plugin.
			if (cur.toggleState && cur.SelectedMouse=MouseID) {
				cur.x+=dx?dx*cur.sensX:0														; Update position
				cur.y+=dy?dy*cur.sensY:0
				cur.x:= cur.x>cur.MaxX?cur.MaxX:(cur.x<cur.MinX?cur.MinX:cur.x)				; Confine
				cur.y:= cur.y>cur.MaxY?cur.MaxY:(cur.y<cur.MinY?cur.MinY:cur.y)
			
				MouseMove, cur.x, cur.y, 0
				cs:=A_Cursor
				cur.hs:=Cursor.GetCursorHotspot(cs)											; Adjustment for gui, w.r.t cursor hotspot
				cur.setCursor(cs)
				--idCtr
				if !(idCtr) {
					cur.hideCursor()														; Hide the gui, the real mouse cursor is showing
				} else {
					cur.moveCursor()														; Move the gui, the real mouse is not here
				}
				if usButtonFlags
					Cursor.MouseButtons(usButtonFlags,usButtonData)							; Handle mouse buttons
			} else if (cur.toggleState && cur.hidden) {										; Show the gui, the real cursor has moved elsewhere.
				cur.showCursor()
			}
		Cursor.md.SetState(1)
		return
	}

	setCursor(CursorStyle:=""){
		; Decides which cursor image to show on the cursor gui.
		if !CursorStyle
			CursorStyle:=A_Cursor
		if (this.IDC=CursorStyle)
			return
		if this.hIcons.HasKey(CursorStyle)
			this.IDC:=CursorStyle
		else
			this.IDC:="Arrow"																; reconsider this choise.	This is when A_Cursor is "Unknown"											<--- NOTE
		return
	}
	MouseButtons(usButtonFlags,usButtonData){
		/*
		RAWMOUSE structure
		URL:https://msdn.microsoft.com/en-us/library/windows/desktop/ms645578(v=vs.85).aspx
		usButtonFlags:
			The transition state of the mouse buttons. This member can be one or more of the following values:
			RI_MOUSE_LEFT_BUTTON_DOWN:=0x0001
			RI_MOUSE_LEFT_BUTTON_UP:=0x0002
			RI_MOUSE_RIGHT_BUTTON_DOWN:=0x0004
			RI_MOUSE_RIGHT_BUTTON_UP:=0x0008
			RI_MOUSE_MIDDLE_BUTTON_DOWN:=0x0010
			RI_MOUSE_MIDDLE_BUTTON_UP:=0x0020
			RI_MOUSE_BUTTON_4_DOWN:=0x0040
			RI_MOUSE_BUTTON_4_UP:=0x0080
			RI_MOUSE_BUTTON_5_DOWN:=0x100
			RI_MOUSE_BUTTON_5_UP:=0x0200
			RI_MOUSE_WHEEL:=0x0400
		
		usButtonData:
			If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta.
		*/
		static ClickParams:=	{	 	 1:"Down", 			2:"Up", 		4:"Down Right", 8:"Up Right"
									,	16:"Down Middle",  32:"Up Middle", 64:"Down X1",  128:"Up X1"
									,  256:"Down X2",	  512:"Up X2",	 1024:"Wheel" }						; 1024:Wheel isn't used.
		static WheelDelta:=120
		(RI_MOUSE_WHEEL:= usButtonFlags & 1024) ? usButtonFlags^=1024 : ""
		while usButtonFlags {
			if ((paramNum:=2**(A_Index-1)) & usButtonFlags) {
				SendInput, % "{Click " . ClickParams[paramNum] . "}"
				usButtonFlags^=paramNum
			}
		}
		if RI_MOUSE_WHEEL																					; Mouse wheel
			Loop, % Round(abs(usButtonData)/WheelDelta)
				SendInput % "{Click " . (usButtonData>0 ? "WheelUp" : "WheelDown") . "}"
		return
	}
	
	LoadCursor(){
		; Url https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391%28v=vs.85%29.aspx
		static	cursorIds:={	IDC_ARROW		:32512
							,	IDC_IBEAM		:32513
							,	IDC_WAIT		:32514
							,	IDC_CROSS		:32515
							,	IDC_UPARROW		:32516
							,	IDC_SIZE		:32640
							,	IDC_ICON		:32641
							,	IDC_SIZENWSE	:32642
							,	IDC_SIZENESW	:32643
							,	IDC_SIZEWE		:32644
							,	IDC_SIZENS		:32645
							,	IDC_SIZEALL		:32646
							,	IDC_NO			:32648
							,	IDC_HAND		:32649
							,	IDC_APPSTARTING	:32650
							,	IDC_HELP		:32651 }
		this.hIcons:={}
		for IDC, id in cursorIds	
			this.hIcons[SubStr(IDC,5)]:=DllCall("LoadCursor", "Uint", 0, "Uint", id)
		return
	}
	
	GetCursorHotspot(CursorStyle:=""){
		; Url :
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648389%28v=vs.85%29.aspx 	(GetCursorInfo)
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648381(v=vs.85).aspx 		(Cursorinfo struct)
		;		struct cursorinfo {
		;		  DWORD   cbSize;
		;		  DWORD   flags;
		;		  HCURSOR hCursor;
		;		  POINT   ptScreenPos;
		;		}
		; Url:
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648070(v=vs.85).aspx		(GetIconInfo)
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648052(v=vs.85).aspx		(Iconinfo struct)
		;
		;		struct _ICONINFO {
		;		  BOOL    fIcon;			fIcon	 := NumGet(&PICONINFO, 0,"Int")
		;		  DWORD   xHotspot;
		;		  DWORD   yHotspot;
		;		  HBITMAP hbmMask;			hBMMask  := NumGet(&PICONINFO,12,"UPtr")
		;		  HBITMAP hbmColor;			BMColor  := NumGet(&PICONINFO,12+A_PtrSize,"UPtr")
		;		}
		;
		
		; Get Cursor handle
		static hotspots:={}
		if !CursorStyle
			CursorStyle:=A_Cursor
		if hotspots.Haskey(CursorStyle)
			return hotspots[CursorStyle]
		VarSetCapacity(pci, (cbSize:=16+A_PtrSize), 0)
		NumPut(cbSize,&pci,"Uint")
		DllCall("GetCursorInfo", "UPtr", &pci)
		hCursor := NumGet(pci, 8, "UPtr")
		
		; Get icon info - cursor hotspot
		VarSetCapacity(PICONINFO, 12+2*A_PtrSize, 0)
		DllCall("GetIconInfo", "Ptr", hCursor, "Ptr", &PICONINFO)
		xHotspot := NumGet(&PICONINFO, 4,"UInt")
		yHotspot := NumGet(&PICONINFO, 8,"UInt")
		hs:=[xHotspot?xHotspot:0, yHotspot?yHotspot:0]
		hotspots[CursorStyle]:=hs
		return hs
	}
	
	; Credits, Class MouseDelta: https://autohotkey.com/boards/viewtopic.php?f=19&t=10159
	; This version is slightly modified.
	Class MouseDelta {
		State := 0
		__New(callback){
			this.MouseMovedFn := this.MouseMoved.Bind(this)
			this.Callback := callback
		}

		Start(){
			static DevSize := 8 + A_PtrSize, RIDEV_INPUTSINK := 0x00000100
			; Register mouse for WM_INPUT messages.
			VarSetCapacity(RAWINPUTDEVICE, DevSize)
			NumPut(1, RAWINPUTDEVICE, 0, "UShort")
			NumPut(2, RAWINPUTDEVICE, 2, "UShort")
			NumPut(RIDEV_INPUTSINK, RAWINPUTDEVICE, 4, "Uint")
			; WM_INPUT needs a hwnd to route to, so get the hwnd of the AHK Gui.
			; It doesn't matter if the GUI is showing, it still exists
			Gui +hwndhwnd
			NumPut(hwnd, RAWINPUTDEVICE, 8, "Uint")
	 
			this.RAWINPUTDEVICE := RAWINPUTDEVICE
			DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
			OnMessage(0x00FF, this.MouseMovedFn)
			this.State := 1
			return this	; allow chaining
		}
		
		Stop(){
			static RIDEV_REMOVE := 0x00000001
			static DevSize := 8 + A_PtrSize
			OnMessage(0x00FF, this.MouseMovedFn, 0)
			RAWINPUTDEVICE := this.RAWINPUTDEVICE
			NumPut(RIDEV_REMOVE, RAWINPUTDEVICE, 4, "Uint")
			DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
			this.State := 0
			return this	; allow chaining
		}
		
		SetState(state){
			if (state && !this.State)
				this.Start()
			else if (!state && this.State)
				this.Stop()
			return this	; allow chaining
		}

		Delete(){
			this.Stop()
			this.MouseMovedFn := ""
		}
		
		; Called when the mouse moved.
		; Messages tend to contain small (+/- 1) movements, and happen frequently (~20ms)
		MouseMoved(wParam, lParam){
			Critical
			; RawInput statics
			static DeviceSize := 2 * A_PtrSize, iSize := 0, sz := 0, pcbSize:=8+2*A_PtrSize, offsets := {usButtonFlags:12+A_PtrSize*2, usButtonData:14+A_PtrSize*2, x: (20+A_PtrSize*2), y: (24+A_PtrSize*2)}, uRawInput

			; Get hDevice from RAWINPUTHEADER to identify which mouse this data came from
			VarSetCapacity(header, pcbSize, 0)
			If (!DllCall("GetRawInputData", "UPtr", lParam, "uint", 0x10000005, "UPtr", &header, "Uint*", pcbSize, "Uint", pcbSize) or ErrorLevel)
				Return 0
			
			if !(ThisMouse := NumGet(header, 8, "UPtr"))
				return

			; Find size of rawinput data - only needs to be run the first time.
			if (!iSize){
				r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", 0, "UInt*", iSize, "UInt", 8 + (A_PtrSize * 2))
				VarSetCapacity(uRawInput, iSize)
			}
			sz := iSize	; param gets overwritten with # of bytes output, so preserve iSize
			; Get RawInput data
			r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", &uRawInput, "UInt*", sz, "UInt", 8 + (A_PtrSize * 2))
	 
			; Ensure we always report a number for an axis. Needed? - No it is overwritten by the numget. 
			dx := NumGet(&uRawInput, offsets.x, "Int")
			dy := NumGet(&uRawInput, offsets.y, "Int")
			
			usButtonFlags:=NumGet(&uRawInput, offsets.usButtonFlags, "UShort")	; Transition state of the mouse buttons.
			usButtonData:=NumGet(&uRawInput, offsets.usButtonData, "Short")		; Contains mouse wheel delta, if usButtonFlags=RI_MOUSE_WHEEL=0x0400
			
			this.Callback.(ThisMouse, dx, dy, usButtonFlags, usButtonData)
			
		}
	}
		
}
@evilC. Cool stuff (the band plugin and c#). And, the mouse hook can't tell which mouse generated the movement, as far as I know.

Cheers!
Tharg
Posts: 4
Joined: 05 Mar 2017, 08:42

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 13:20

To be honest it seems to be working perfectly right now. I thought perhaps you were only sending single taps and I hadn't explained it properly. I guess the game is forgiving on that.
...
I just managed to "prove" that you are sending held keys though, but I can get around any problems it causes I think. I have the middle band bound to vjoy button 1, which is "back" in game. I also have a joystick button bound to the same vjoy button 1 too, for use as "back" in game menus. When I'm in the middle band this joystick button doesn't work (as vjoy button 1 is held by the band I guess). I also have a button on a physical gamepad as "back" (no vjoy), for some more complex menus, and this one also doesn't work when in the middle band (Elite offers 2 binds for each function).
...
So - I've edited your script, and added 2 new bands (only really needed one I guess) so the middle band is empty, but there's a 2 small bands either side of it bound to the back button (http://i.imgur.com/1f1VkgM.png). It all seems great, but even if I get some issues I'm pretty sure I'll be able to get around them by getting a little creative.

Please don't do any more work for me! It's working pretty perfectly now.

Donation made : http://i.imgur.com/evS6eqD.png

...

[Seen reply re "tap update"]

Man you make these and reply quicker than I test, tweak, and type a reply! ( I'm in VR so it's a bit laborious to get in and out of game etc. /humblebrag)

Tried the new script, set it up slightly differently, works even better. Thanks!

Just to explain a little better (perhaps) what I did in my original script : I stored the axis value from the last tick, and compared this to the current tick, to see if a band had been entered / left and from which direction. Actually it's probably better to explain it by saying that I monitored the borders between bands to see if they'd been crossed, and in which direction. This enabled me to press a different key depending on whether I was moving down or up the axis when I crossed the threshold. I think that this is something that this plugin can't do in quite the same way(?).

My 'config' in the script might make what I mean clearer :

Code: Select all

PsudoButtonAxisConfig[ "V" ][ "Pass" ][ "20" ] := { "Down": "b", "Up":"a" }
PsudoButtonAxisConfig[ "V" ][ "Pass" ][ "40" ] := { "Down": "a", "Up":"a" }
PsudoButtonAxisConfig[ "V" ][ "Pass" ][ "60" ] := { "Down": "d", "Up":"d" }
PsudoButtonAxisConfig[ "V" ][ "Pass" ][ "80" ] := { "Down": "d", "Up":"c" }
So when the value passes 20 going down, I send a "b", when it passes it going up, it sends an "a".

The only part I think the plugin can't do that this can is the middle 'band" I think. When I enter this from > 60 it taps "d" to close that HUD. When I enter it from < 40 it taps "a" to close that one. I don't think the plugin can do this in the same way. But I can mimic this in game by using "back" instead of the same key again to close the hud elements when I return the axis to the middle.

....

[Seen later reply with next update]

Aaand you've done more work and replied again.

That's it from me, everything is perfect, thanks again!
Jamz
Posts: 5
Joined: 05 Mar 2017, 09:08

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 14:11

Helgef wrote:
Jamz wrote:Hi, i'm hopeing UCR can solve my issue but can't seem to find a way, maybe I'm missing it?

I'm looking to invert the axis of a second mouse, is this possible?

My setup: A virtual table top/TV embedded into a table. So I want one mouse on one side of the table as normal, and a second mouse on the opposite side with it's x and y axis reversed (as they are viewing windows upside down). I really don't need a separate cursor, just the axis changed for a second mouse. (Both mice are logitech 5 button mice)
Hi, jamz.
I just made a script for multiple cursors, it is very simple to add inverted axis to that, as long as you don't move both mice simultaneously, the cursor acts pretty normal. This is is a modified version if you like to try. You invert the axis by setting a negative sensitivity. (Left edit is for x, right for y). You also need to edit the the confinment rectangle under the coordinates tab. More instructions: here.

Code: Select all

; TODO, 
;		remove debug hotkey - and ea() func
;		reconsider choise of default cursor when A_Cursor is Unknown.
;		Manage click up when more than mouse is moving and click is down.... 
class Cursor extends _UCR.Classes.Plugin
{
	type:="Cursor"
	description:="Mulitple cursor controller"
	
	static AllCursors:=[]											; Tracks all instances of this plugin
	static MouseIdCounter:=[]										; For tracking multiple active cursor from the same mouse id.
	static doNothingFunc:=ObjBindMethod(Cursor, "doNothing")		; For disabling mouse buttons.

	init()
	{
		this.LoadCursor()	; Loads cursor handles.
		
		; Gui 
		
		; Some control(s) are placed at rowX, colY, defined here:
		row0:=" y5 ",  		row0_:=" y2 "
		row1:=" y35 ",  	row1_:=" y32 "
		row2:=" y65 ",  	row2_:=" y62 "
		row3:=" y95 ",  	row3_:=" y92 "
		row4:=" y125 ", 	row4_:=" y123 "
		row5:=" y155 ", 	row5_:=" y153 "
		row6:=" y185 ", 	row6_:=" y183 "
		row7:=" y215 ", 	row7_:=" y213 "
		
		; Columns for gui placement
		col1:=" x25 ", col2:=" x145 ", col3:=" x260 ", col4:=" x375 ", col5:=" x490 "
		
		Gui, Add, Tab2, w650 h65, % "Main|Cycle|Coordinates|Help"
		Gui, Tab, 1
; Mouse selection
		Gui, Add, Button, % " w100 h25 hwndselectMouseButton", % "Select mouse"
		this.selectMouseButton:=selectMouseButton
		gFunc := this.SelectMouseInit.Bind(this)
		GuiControl, +g, % this.selectMouseButton, % gFunc
		this.AddControl("Edit", "MouseIdEdit", this.MouseIdSet.Bind(this),"x+10 yp+3 w75 center")
		this.AddControl("InputButton", "toggle", 0, this.toggle.Bind(this),"x+10 h25")
		
		; Toggle light
		this.AddControl("ButtonPreview", "bp1", 0,0, "x+10")
		; Sensitivity
		Gui,Add,Text,x+10 yp+3,Sensitivity:
		this.AddControl("Edit", "sensEditX", this.sensChanged.Bind(this,1),"x+10 yp-3 w35 center",1)
		this.AddControl("Edit", "sensEditY", this.sensChanged.Bind(this,2),"x+5 w35 center",1)
		; Hide/show option
		this.AddControl("Checkbox", "hideWhenInactiveCB", this.hideWhenInactiveCBChanged.Bind(this),"x+10 yp+3 checked", "Hide when inactive")
		Gui, Tab, 2
		Gui,Add,Text,,Cycle cursors:
		this.AddControl("InputButton", "cycleCursor", 0, this.cycleCursor.Bind(this),"x+10 h25")
		this.AddControl("Checkbox", "cycleCursorCB", 0,"x+10 yp+3 checked", "Cycle by id")
; Coordinates tab
		Gui, Tab, 3
		Gui,Add,Text, x+5,Confine to: (x1,y1)
		this.AddControl("Edit", "confineX1", this.confineEditChanged.Bind(this,1),"x+5 yp-3 w35 number center",0)
		this.AddControl("Edit", "confineY1", this.confineEditChanged.Bind(this,2),"x+5 w35 number center",0)
		Gui,Add,Text, x+5 yp+3,(x2,y2)
		this.AddControl("Edit", "confineX2", this.confineEditChanged.Bind(this,3),"x+5 yp-3 w35 number center",A_ScreenWidth)
		this.AddControl("Edit", "confineY2", this.confineEditChanged.Bind(this,4),"x+5 w35 number center",A_ScreenHeight)
		Gui,Add,Text, x+5 yp+3,Start at: (x,y)
		this.AddControl("Edit", "startX1", this.startEditChanged.Bind(this,1),"x+5 yp-3 w35 number center",A_ScreenWidth//2)
		this.AddControl("Edit", "startY1", this.startEditChanged.Bind(this,2),"x+5 w35 number center",A_ScreenHeight//2)
		
		Gui, Add, Button, % "x+5 hwndresetButton", % "Reset"
		this.resetButton:=resetButton
		gFunc := this.resetCoords.Bind(this)
		GuiControl, +g, % this.resetButton, % gFunc
		
; Help tab
		Gui, Tab, 4
		Gui, Font, s8 w400, Courier New
		Gui, add, Link, % row1 col1, % 		"Author: Helgef (2017-03-03). "
										.	"Instructions are available at: <a href=""https://autohotkey.com/boards/viewtopic.php?f=19&t=24538"">AutoHotkey.com forum.</a>`n"
		; Misc.
		this.createCursor()				; Make the cursor's gui.
		Cursor.AllCursors.Push(this)	; The plugin is self aware.
		
		this.hasBeenToggled:=0
		this.hideWhenInactive && !this.toggleState ? this.hideCursor() : this.showCursor()
		; DEBUG - REMOVE
	;	ff:=ObjBindMethod(Cursor,"ea")
	;	try
	;		Hotkey, Esc, % ff

	}

	; Clean up for UCR.
	onClose(){
		GuiControl, -g, % this.selectMouseButton
		GuiControl, -g, % this.resetButton
		for k, cur in Cursor.AllCursors																			; Find position in AllCursors list.
			if (cur=this)
				break
		Cursor.AllCursors.RemoveAt(k)																			; Remove from AllCursors list
		Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? --Cursor.MouseIdCounter[this.SelectedMouse] : ""		; Decerment mouse id counter
		if !Cursor.AllCursors.length()
			Cursor.md.Delete(),Cursor.md:="",Cursor.toggleHotkeys("Off")										; Remove the mouse delta if no more cursor plugins exists.
		this.destroyCursor() 																					; Destroy the gui cursor.
		base.onClose() ;? 
	}
	
	; Gui functions
	; Coords tab
	confineEditChanged(num,val){	; Confinement rectangle.
		if (num=1)
			this.minX:=val
		else if (num=2)
			this.minY:=val
		else if (num=3)
			this.maxX:=val
		else if (num=4)
			this.maxY:=val
		return
	}
	startEditChanged(num,val){	; start position
		if (num=1)
			this.oX:=val
		else if (num=2)
			this.oY:=val
		return
	}
	resetCoords(){ ; Reset coords button
		this.GuiControls.confineX1.Set(0)
		this.GuiControls.confineY1.Set(0)
		this.GuiControls.confineX2.Set(A_ScreenWidth)
		this.GuiControls.confineY2.Set(A_ScreenHeight)
		
		this.GuiControls.startX1.Set(A_ScreenWidth//2)
		this.GuiControls.startY1.Set(A_ScreenHeight//2)
	}
	
	sensChanged(num,val){
		if val is number
		{
			if (num=1)				
				this.sensX:=val			; Set sensitivity x-axis
			else if (num=2)
				this.sensY:=val			; Set sensitivity y-axis
		}
		return
	}
	
	hideWhenInactiveCBChanged(state){
		state && !this.toggleState ? this.hideCursor() : this.showCursor() 	; Show/hide cursor to match setting.
		return this.hideWhenInactive:=state									; Indicates wether cursor is hidden/shown when inactive
	}
	
	createCursor(){
		if this.hWin
			return ;?
		this.x:=this.oX
		this.y:=this.oY
		Gui, new, +hwndhWin -caption +toolwindow +alwaysontop +E0x20
		this.hWin:=hWin
		Gui, % hWin ": Margin",0,0 
		Gui, % hWin ": Color", abcdef
		Gui, % hWin ": Add", Picture, % "x0 y0 hwndhPic", % "hIcon:*" this.hIcons["Arrow"]
		this.hPic:=hPic
		this.setCursor()
		this.hs:=Cursor.GetCursorHotspot()
		GuiControl,, % this.hPic, % "hIcon:*" this.hIcons[this.IDC]	; Consider to remove this.
		;Gui, % hWin ": Show", % "Hide NA x" this.x " y" this.y 
		this.hidden:=1
		WinSet, TransColor, abcdef 255, % "ahk_id " hWin
		return
	}
	
	; Handle mouse selection
	SelectMouseInit(){
		; Starts the mouse selection procedure.
		; How to:
		;			- Press the button
		;			- Move the mouse within 5 seconds.
		;			- Done!
		p:=this.SelectedMouse														; Previous mouse
		selectMouseMD:= new Cursor.MouseDelta(ObjBindMethod(this,"SelectMouse"))
		selectMouseMD.Start()
		timeout:=A_TickCount														; For timeout
		while (this.SelectedMouse=p && A_TickCount-timeout<5000)					; 5 second timeout.
			Sleep,-1
		if (this.SelectedMouse!=p){
			Cursor.MouseIdCounter.HasKey(p) ? --Cursor.MouseIdCounter[p] : ""		; A new mouse chosen
			Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? ++Cursor.MouseIdCounter[this.SelectedMouse] : (Cursor.MouseIdCounter[this.SelectedMouse]:=1)
		}
		selectMouseMD.Delete()														; Delete the old mouse delta
		return
	}
	
	MouseIdSet(id){
		return this.SelectedMouse:=id
	}
	
	SelectMouse(MouseID,dx,dy) {
		; Callback function for temporary mouse delta instance.
		if (this.selectedMouse=MouseID)
			return
		this.GuiControls.MouseIdEdit.Set(MouseID)
		return
	}
	
	; destroy/hide/show/moveCursor() - these functions apply to the "gui cursor" not the real windows cursor.
	destroyCursor(){
		if !this.hWin
			return
		Gui, % this.hWin ": Destroy"
		return this.hWin:=""
	}
	
	hideCursor(){
		if !this.hWin
			return
		if this.hidden
			return
		Gui, % this.hWin ": Show", % "NA hide"
		this.hidden:=1
		return
	}
	
	showCursor(move:=0){
		; Consider toggle aot to avoid getting under other aot win.
		if !this.hWin
			return
		if (this.hidden && move)
			return this.showCursor()
		else if (!this.hidden && !move)
			return
		this.hidden:=0
		if (this.IDC!=this.pIDC)	; Only update if changed			
			GuiControl,, % this.hPic, % "hIcon:*" this.hIcons[this.IDC]
		this.pIDC:=this.IDC
		if !move
			Gui, % this.hWin ": Show", % "NA x" this.x-this.hs[1] " y" this.y-this.hs[2]			; Adjustment for hotspot.
		else
			WinMove, % "ahk_id " this.hWin,, this.x-this.hs[1], this.y-this.hs[2]
		return
	}
	
	moveCursor(){
		return this.showCursor(true)				
	}
	
	ea(){	; remove
		ExitApp
	}
	
	; For suppressing native mouse button functionality
	doNothing(){
		return
	}

	; Main togle
	toggle(state) {	
		if !state
			return
		if !this.hasBeenToggled {	; Add to MouseIdCounter on first time toggle on.
			Cursor.MouseIdCounter.HasKey(this.SelectedMouse) ? ++Cursor.MouseIdCounter[this.SelectedMouse] : (this.SelectedMouse ? (Cursor.MouseIdCounter[this.SelectedMouse]:=1) : "")
			this.hasBeenToggled:=true
		}
		this.toggleState:=!this.toggleState
		if (!Cursor.md && this.toggleState) {
			Cursor.md:=new Cursor.MouseDelta(ObjBindMethod(Cursor,"MoveCursors"))	; Create a new mousedelta for the Cursor class, if non exists.
		}
		Cursor.globalToggle(this.toggleState)
		if !this.toggleState {
			if (this.hideWhenInactive)	; Hide/show according to settings
				this.hideCursor()
			else
				this.showCursor()
			
		} else {
			this.showCursor()
		}
		this.GuiControls.bp1.SetState(this.toggleState)	; Green light when on (in UCR gui)
		return
	}
	
	globalToggle(state){
		; Sets the correct state of the MouseDelta: Cursor.md, depending on all toggleStates of all cursors in Cursor.AllCursors
		mdState:=Cursor.md.State
		if (state=mdState)							; State is matched, do nothing
			return
		else if (state && !mdState)	{				; State is on, and md is off, turn on md.
			Cursor.toggleHotkeys("On")
			Cursor.md.Start()
			return 
		}else if (!state && mdState) {				; State is off, but md is on, check if any other cursor is on, then do nothing, if no other is on, restore mouse functionality and turn off md.
			for k, cur in Cursor.AllCursors
				if cur.toggleState
					return
			Cursor.toggleHotkeys("Off")
			Cursor.md.Stop()
		}
		return
	}
	
	toggleHotkeys(state){
		; Normal mouse button functionality is suppressed while a cursor is active.
		hkfn:=Cursor.doNothingFunc
		Hotkey, *LButton,	% hkfn, % state
		Hotkey, *RButton, 	% hkfn, % state
		Hotkey, *MButton, 	% hkfn, % state
		Hotkey, *WheelUp, 	% hkfn, % state
		Hotkey, *WheelDown, % hkfn, % state
		Hotkey, *XButton1,	% hkfn, % state
		Hotkey, *XButton2,	% hkfn, % state
		if (state="On")					; Disables all mouse movement when "On"
			BlockInput, MouseMove
		else
			BlockInput, MouseMoveOff
		return
	}
	

	; Cycle cursors.
	cycleCursor(state){
		if !state								; Manage hotkey up event - do nothing.
			return
		if this.GuiControls.cycleCursorCB.Get() ; Only cycle same id
			this.cycleSame()
		else
			this.cycleAll()
	}
	cycleSame(){
		for k, cur in Cursor.AllCursors
			if (cur.selectedMouse!=this.selectedMouse) {	; Wrong id, continue
				continue
			} else if (cur.toggleState) {					; Turn off the first one that is on
				cur.toggle(1), oneOff:=1
			} else if (oneOff) {							; Now we can turn on the next
				cur.toggle(1), oneOn:=1
				break
			}
		if !oneOn											; No cursor was turned on, turn on the first with correct id.
			for k, cur in Cursor.AllCursors
				if (cur.selectedMouse=this.selectedMouse) {
					cur.toggle(1)
					break
				}
		return
	}
	cycleAll(){
		; Find the next cursor, turn it on
		for k, cur in Cursor.AllCursors
			if (cur.toggleState) {							; Turn off the first one that is on
				cur.toggle(1), oneOff:=1
			} else if (oneOff) {
				cur.toggle(1), oneOn:=1						; Turn on the next one
				break
			}
		if !oneOn
			Cursor.AllCursors[1].toggle(1)					; No cursor turned on, turn on the first one.
		return
	}
	
	MoveCursors(MouseID, dx, dy, usButtonFlags, usButtonData){
		; Move all cursors for the MouseId that generated the movement, dx,dy.
		; Confine to (x,y) ∈ [cur.MinW,cur.MaxW]x[cur.MinH,cur.MaxH]
		; Note: hide/show/move Cursor functions refer to the cursor GUI representation, not the actual (windows) cursor.
		Cursor.md.SetState(0)
		SetWinDelay,-1
		SetMouseDelay,-1
		CoordMode, Mouse, Screen
		idCtr:=Cursor.MouseIdCounter[MouseID]
		for k, cur in Cursor.AllCursors														; Consider all cursors. each "cur" is one instances of the plugin.
			if (cur.toggleState && cur.SelectedMouse=MouseID) {
				cur.x+=dx?dx*cur.sensX:0														; Update position
				cur.y+=dy?dy*cur.sensY:0
				cur.x:= cur.x>cur.MaxX?cur.MaxX:(cur.x<cur.MinX?cur.MinX:cur.x)				; Confine
				cur.y:= cur.y>cur.MaxY?cur.MaxY:(cur.y<cur.MinY?cur.MinY:cur.y)
			
				MouseMove, cur.x, cur.y, 0
				cs:=A_Cursor
				cur.hs:=Cursor.GetCursorHotspot(cs)											; Adjustment for gui, w.r.t cursor hotspot
				cur.setCursor(cs)
				--idCtr
				if !(idCtr) {
					cur.hideCursor()														; Hide the gui, the real mouse cursor is showing
				} else {
					cur.moveCursor()														; Move the gui, the real mouse is not here
				}
				if usButtonFlags
					Cursor.MouseButtons(usButtonFlags,usButtonData)							; Handle mouse buttons
			} else if (cur.toggleState && cur.hidden) {										; Show the gui, the real cursor has moved elsewhere.
				cur.showCursor()
			}
		Cursor.md.SetState(1)
		return
	}

	setCursor(CursorStyle:=""){
		; Decides which cursor image to show on the cursor gui.
		if !CursorStyle
			CursorStyle:=A_Cursor
		if (this.IDC=CursorStyle)
			return
		if this.hIcons.HasKey(CursorStyle)
			this.IDC:=CursorStyle
		else
			this.IDC:="Arrow"																; reconsider this choise.	This is when A_Cursor is "Unknown"											<--- NOTE
		return
	}
	MouseButtons(usButtonFlags,usButtonData){
		/*
		RAWMOUSE structure
		URL:https://msdn.microsoft.com/en-us/library/windows/desktop/ms645578(v=vs.85).aspx
		usButtonFlags:
			The transition state of the mouse buttons. This member can be one or more of the following values:
			RI_MOUSE_LEFT_BUTTON_DOWN:=0x0001
			RI_MOUSE_LEFT_BUTTON_UP:=0x0002
			RI_MOUSE_RIGHT_BUTTON_DOWN:=0x0004
			RI_MOUSE_RIGHT_BUTTON_UP:=0x0008
			RI_MOUSE_MIDDLE_BUTTON_DOWN:=0x0010
			RI_MOUSE_MIDDLE_BUTTON_UP:=0x0020
			RI_MOUSE_BUTTON_4_DOWN:=0x0040
			RI_MOUSE_BUTTON_4_UP:=0x0080
			RI_MOUSE_BUTTON_5_DOWN:=0x100
			RI_MOUSE_BUTTON_5_UP:=0x0200
			RI_MOUSE_WHEEL:=0x0400
		
		usButtonData:
			If usButtonFlags is RI_MOUSE_WHEEL, this member is a signed value that specifies the wheel delta.
		*/
		static ClickParams:=	{	 	 1:"Down", 			2:"Up", 		4:"Down Right", 8:"Up Right"
									,	16:"Down Middle",  32:"Up Middle", 64:"Down X1",  128:"Up X1"
									,  256:"Down X2",	  512:"Up X2",	 1024:"Wheel" }						; 1024:Wheel isn't used.
		static WheelDelta:=120
		(RI_MOUSE_WHEEL:= usButtonFlags & 1024) ? usButtonFlags^=1024 : ""
		while usButtonFlags {
			if ((paramNum:=2**(A_Index-1)) & usButtonFlags) {
				SendInput, % "{Click " . ClickParams[paramNum] . "}"
				usButtonFlags^=paramNum
			}
		}
		if RI_MOUSE_WHEEL																					; Mouse wheel
			Loop, % Round(abs(usButtonData)/WheelDelta)
				SendInput % "{Click " . (usButtonData>0 ? "WheelUp" : "WheelDown") . "}"
		return
	}
	
	LoadCursor(){
		; Url https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391%28v=vs.85%29.aspx
		static	cursorIds:={	IDC_ARROW		:32512
							,	IDC_IBEAM		:32513
							,	IDC_WAIT		:32514
							,	IDC_CROSS		:32515
							,	IDC_UPARROW		:32516
							,	IDC_SIZE		:32640
							,	IDC_ICON		:32641
							,	IDC_SIZENWSE	:32642
							,	IDC_SIZENESW	:32643
							,	IDC_SIZEWE		:32644
							,	IDC_SIZENS		:32645
							,	IDC_SIZEALL		:32646
							,	IDC_NO			:32648
							,	IDC_HAND		:32649
							,	IDC_APPSTARTING	:32650
							,	IDC_HELP		:32651 }
		this.hIcons:={}
		for IDC, id in cursorIds	
			this.hIcons[SubStr(IDC,5)]:=DllCall("LoadCursor", "Uint", 0, "Uint", id)
		return
	}
	
	GetCursorHotspot(CursorStyle:=""){
		; Url :
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648389%28v=vs.85%29.aspx 	(GetCursorInfo)
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648381(v=vs.85).aspx 		(Cursorinfo struct)
		;		struct cursorinfo {
		;		  DWORD   cbSize;
		;		  DWORD   flags;
		;		  HCURSOR hCursor;
		;		  POINT   ptScreenPos;
		;		}
		; Url:
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648070(v=vs.85).aspx		(GetIconInfo)
		;		- https://msdn.microsoft.com/en-us/library/windows/desktop/ms648052(v=vs.85).aspx		(Iconinfo struct)
		;
		;		struct _ICONINFO {
		;		  BOOL    fIcon;			fIcon	 := NumGet(&PICONINFO, 0,"Int")
		;		  DWORD   xHotspot;
		;		  DWORD   yHotspot;
		;		  HBITMAP hbmMask;			hBMMask  := NumGet(&PICONINFO,12,"UPtr")
		;		  HBITMAP hbmColor;			BMColor  := NumGet(&PICONINFO,12+A_PtrSize,"UPtr")
		;		}
		;
		
		; Get Cursor handle
		static hotspots:={}
		if !CursorStyle
			CursorStyle:=A_Cursor
		if hotspots.Haskey(CursorStyle)
			return hotspots[CursorStyle]
		VarSetCapacity(pci, (cbSize:=16+A_PtrSize), 0)
		NumPut(cbSize,&pci,"Uint")
		DllCall("GetCursorInfo", "UPtr", &pci)
		hCursor := NumGet(pci, 8, "UPtr")
		
		; Get icon info - cursor hotspot
		VarSetCapacity(PICONINFO, 12+2*A_PtrSize, 0)
		DllCall("GetIconInfo", "Ptr", hCursor, "Ptr", &PICONINFO)
		xHotspot := NumGet(&PICONINFO, 4,"UInt")
		yHotspot := NumGet(&PICONINFO, 8,"UInt")
		hs:=[xHotspot?xHotspot:0, yHotspot?yHotspot:0]
		hotspots[CursorStyle]:=hs
		return hs
	}
	
	; Credits, Class MouseDelta: https://autohotkey.com/boards/viewtopic.php?f=19&t=10159
	; This version is slightly modified.
	Class MouseDelta {
		State := 0
		__New(callback){
			this.MouseMovedFn := this.MouseMoved.Bind(this)
			this.Callback := callback
		}

		Start(){
			static DevSize := 8 + A_PtrSize, RIDEV_INPUTSINK := 0x00000100
			; Register mouse for WM_INPUT messages.
			VarSetCapacity(RAWINPUTDEVICE, DevSize)
			NumPut(1, RAWINPUTDEVICE, 0, "UShort")
			NumPut(2, RAWINPUTDEVICE, 2, "UShort")
			NumPut(RIDEV_INPUTSINK, RAWINPUTDEVICE, 4, "Uint")
			; WM_INPUT needs a hwnd to route to, so get the hwnd of the AHK Gui.
			; It doesn't matter if the GUI is showing, it still exists
			Gui +hwndhwnd
			NumPut(hwnd, RAWINPUTDEVICE, 8, "Uint")
	 
			this.RAWINPUTDEVICE := RAWINPUTDEVICE
			DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
			OnMessage(0x00FF, this.MouseMovedFn)
			this.State := 1
			return this	; allow chaining
		}
		
		Stop(){
			static RIDEV_REMOVE := 0x00000001
			static DevSize := 8 + A_PtrSize
			OnMessage(0x00FF, this.MouseMovedFn, 0)
			RAWINPUTDEVICE := this.RAWINPUTDEVICE
			NumPut(RIDEV_REMOVE, RAWINPUTDEVICE, 4, "Uint")
			DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
			this.State := 0
			return this	; allow chaining
		}
		
		SetState(state){
			if (state && !this.State)
				this.Start()
			else if (!state && this.State)
				this.Stop()
			return this	; allow chaining
		}

		Delete(){
			this.Stop()
			this.MouseMovedFn := ""
		}
		
		; Called when the mouse moved.
		; Messages tend to contain small (+/- 1) movements, and happen frequently (~20ms)
		MouseMoved(wParam, lParam){
			Critical
			; RawInput statics
			static DeviceSize := 2 * A_PtrSize, iSize := 0, sz := 0, pcbSize:=8+2*A_PtrSize, offsets := {usButtonFlags:12+A_PtrSize*2, usButtonData:14+A_PtrSize*2, x: (20+A_PtrSize*2), y: (24+A_PtrSize*2)}, uRawInput

			; Get hDevice from RAWINPUTHEADER to identify which mouse this data came from
			VarSetCapacity(header, pcbSize, 0)
			If (!DllCall("GetRawInputData", "UPtr", lParam, "uint", 0x10000005, "UPtr", &header, "Uint*", pcbSize, "Uint", pcbSize) or ErrorLevel)
				Return 0
			
			if !(ThisMouse := NumGet(header, 8, "UPtr"))
				return

			; Find size of rawinput data - only needs to be run the first time.
			if (!iSize){
				r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", 0, "UInt*", iSize, "UInt", 8 + (A_PtrSize * 2))
				VarSetCapacity(uRawInput, iSize)
			}
			sz := iSize	; param gets overwritten with # of bytes output, so preserve iSize
			; Get RawInput data
			r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", &uRawInput, "UInt*", sz, "UInt", 8 + (A_PtrSize * 2))
	 
			; Ensure we always report a number for an axis. Needed? - No it is overwritten by the numget. 
			dx := NumGet(&uRawInput, offsets.x, "Int")
			dy := NumGet(&uRawInput, offsets.y, "Int")
			
			usButtonFlags:=NumGet(&uRawInput, offsets.usButtonFlags, "UShort")	; Transition state of the mouse buttons.
			usButtonData:=NumGet(&uRawInput, offsets.usButtonData, "Short")		; Contains mouse wheel delta, if usButtonFlags=RI_MOUSE_WHEEL=0x0400
			
			this.Callback.(ThisMouse, dx, dy, usButtonFlags, usButtonData)
			
		}
	}
		
}
@evilC. Cool stuff (the band plugin and c#). And, the mouse hook can't tell which mouse generated the movement, as far as I know.

Cheers!
Thanks! I was able to easily change it to invert the axis in the MoveCursors function.

Is there a way so it doesn't require a bind key to toggle the mice? Right now, it seems I have to bind a key that turns this control on/off?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

05 Mar 2017, 14:29

Jamz wrote: Thanks! I was able to easily change it to invert the axis in the MoveCursors function.
Ok, good, but you shouln't have to change the code for that, there is a sensitivity setting, set the first edit to -1 to invert the x-axis, similarily, the second edit is for the y-axis. But if it works for you :thumbup:
Jamz wrote:Is there a way so it doesn't require a bind key to toggle the mice? Right now, it seems I have to bind a key that turns this control on/off?
That is how it is now, yes. You should make two instances of the plugin, one for each mouse, and only set up one to be inverted. Toggle both on, and go about your business as usual. It seems like the mouse ids changes on reboot, so one has to set it up every time one reboots anyways.

I think it will be very easy to write a script dedicated for this purpose only, using MouseDelta, as I mentioned to gwarble in the other thread, I saw you asked there too (EitherMouse).

Cheers.
Jamz
Posts: 5
Joined: 05 Mar 2017, 09:08

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

06 Mar 2017, 13:13

Helgef wrote:
Jamz wrote: Thanks! I was able to easily change it to invert the axis in the MoveCursors function.
Ok, good, but you shouln't have to change the code for that, there is a sensitivity setting, set the first edit to -1 to invert the x-axis, similarily, the second edit is for the y-axis. But if it works for you :thumbup:
Jamz wrote:Is there a way so it doesn't require a bind key to toggle the mice? Right now, it seems I have to bind a key that turns this control on/off?
That is how it is now, yes. You should make two instances of the plugin, one for each mouse, and only set up one to be inverted. Toggle both on, and go about your business as usual. It seems like the mouse ids changes on reboot, so one has to set it up every time one reboots anyways.

I think it will be very easy to write a script dedicated for this purpose only, using MouseDelta, as I mentioned to gwarble in the other thread, I saw you asked there too (EitherMouse).

Cheers.
Ahh! I didn't get that the first time lol, that would have saved time using the sensitivity setting...

And ya, I was looking at EitherMouse as well. I also looked at creating a simple script myself and got it to work fine for all mice, but I was getting lost in the code on how to detect the mouse (I was fine with hardcoding it). The eithermouse code was pretty hard to follow but I may take another look at your code, it was easier to follow.

I did run into an issue using it last night as well, when using it with MapTool (a java program), the cursor didn't change to match java's cursor change (so cursors overlapped). That and 2 second mouse seem to give a lot of false double clicks when using it. I'm fine with a single cursor as well so may still need a simpler custom solution.

But until then, UCR "works" for now!
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

06 Mar 2017, 13:20

I'm not surprised by the issues you describe.
I have some ideas for this I want to try. Maybe later tonight, if time be.
Jamz
Posts: 5
Joined: 05 Mar 2017, 09:08

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

06 Mar 2017, 15:36

OK, so now I see what's going on. Being new to AHK, hard to tell what is custom libs and native. :)

So, I basically took the Sniper example from the MouseDelta example (thanks!) and just changed the scale to negative and added if (MouseID == SecondMouseID) and bam!

And FYI: the double-clicking issue is on my end. Apparently my mouse is dying and behaving badly resulting in double left clicks...
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

06 Mar 2017, 15:52

Hmm, strange.
I would have suggested the sniper script tweak but I did not think it would work. Because it does not hide the original input, I would have thought that just inverting the input would result in the real and fake movement cancelling each other out. I suppose you could invert and double, but I thought that would be jittery and odd.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Universal Control Remapper (UCR) - v0.1.11 19th Feb 2017

06 Mar 2017, 16:26

Pretty much what I had in mind, but I would hide the input using BlockInput. I guess it is good enough without the BlockInput, but, there is some jitter on one of mice. I guess it depends some on the mouse's sensitivity/acceleration/dpi settings.
Jamz, if you try to confine the mouse to only one axis, you might notice the jitter. Eg, set

Code: Select all

if (MouseID==SomeId){
	x *= -1
Anyways, I glad you solved your problem :thumbup:

Cheers!
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.12 7th Mar 2017

07 Mar 2017, 15:36

New version released

Code: Select all

0.1.12 - 7th Mar 2017
+ Added AxisRangeToButtons plugin
  This allows you to map an axis to up to 10 buttons
  Each button has a "Low" and "High" value. If the axis is in this range...
  Then the button is pressed. Else it is released.
  If you need more buttons, more can be added by editing the source code.
+ You can now duplicate profiles using the "Copy" button in the toolbox.
  Code contributed by Snoothy - thankyou!
+ You can now rename plugins to the same name but with different case
+ IOControl menus are now initialized when first opened.
  This will mainly benefit users with large numbers of profiles.
  UCR should load quicker, and you should not get a "Too many menus" error.
  Code contributed by Snoothy - thankyou!
+ MaxThreads setting cranked up to 255
  This seems to alleviate issues with fast, simultaneous input.
+ The InputDelta now self-updates Seen Mice
User avatar
raron
Posts: 37
Joined: 11 Aug 2014, 00:50

Re: Universal Control Remapper (UCR) - v0.1.12 7th Mar 2017

08 Mar 2017, 00:46

Just a quick heads-up that the axis previews in "Remapper (Axis Merger)" plugin don't show, and layout is a bit messed up (UCR v0.1.12).

From looking in Control Panel - Game Controllers while testing, it seems I can't use the same axis input for different axis's outputs anymore? I have a setup with three pedals (G27), where I merged the Accelerator and Clutch pedal to one vJoy axis, and "reused" the Accelerator merged with the Brake pedal to another vJoy axis. Works great in UCR 0.0.15. (vJoy 2.1.6.20 and also 2.1.8.33 judging from the preview in UCR).

Also, I'm unsure if "Uninstall SCVPBus.." works. I haven't tried to use the vxbox yet. But when I tried to uninstall it (after installing ofc), there is no change in the IOClasses menu (install SCVPBus is still grayed out, I can run uninstall SCVPBus again though..).


Great work btw, it's an awesome utility you are making!
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.12 7th Mar 2017

08 Mar 2017, 05:38

Thanks for the bug reports raron, will look into them.

Any idea when the axis re-use broke? 0.0.15 is a fair way back, did it work on any 0.1.x versions?

All previous versions are available here, so if you can be bothered, working out which version it broke in may well help me work out what is wrong.
User avatar
raron
Posts: 37
Joined: 11 Aug 2014, 00:50

Re: Universal Control Remapper (UCR) - v0.1.12 7th Mar 2017

08 Mar 2017, 10:23

I hadn't updated in a while, so I didn't know when re-use broke. I just randomly decided to try to update it now :)

But I briefly tested UCR Alpha 0.0.16 through Alpha 0.1.12 just now. I used the latest vJoy I installed (vJoy 2.1.8.33), and I basically only tested the "Remapper (Axis Merger)" plugin with my G27 pedals. When preview didn't work I used Game Controller properties in Control Panel. Also, I only started the "UCR.exe" from its folder when testing. Using Windows 7 Ultimate, 64-bit.

In short, the latest that works (with axis re-use) is UCR Alpha 0.0.17.
  • Alpha 0.0.16: Works. Nice controller selection, enumumerates vJoys.
  • Alpha 0.0.17: Works. Also nice controller input selection. Output axis selection menu don't enumerate nr. of vJoys.
  • Alpha 0.0.18: I can't select vJoy as output, instead it says "Titan axis" when trying to. So couldn't really test it.
  • Alpha 0.1.0 to 0.1.3: I get 5-6 errors (invalid GUI name, other stuff), but selecting continue anyway starts UCR. G27 (stick #4 atm) axis #4 was unselectable. Looses input axis invert setting.
  • Alpha 0.1.4 to 0.1.8: Same as above (not sure about invert setting), but G27 axis #4 was selectable again.
  • Alpha 0.1.9 - 0.1.10: Started without errors, otherwise same as 0.1.8
  • Alpha 0.1.11: As above, except axis preview don't show
  • Alpha 0.1.12: As above, except added plugin names are missing (they are numbered though, unless renamed).
As a sidenote, I'd like to say I like the input axis selection menu in both Alpha 0.0.16 and 0.0.17 (Showing controller names). And it seems Alpha 0.0.16 is the last one that enumerates how many vJoys there are (no biggie though).
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Universal Control Remapper (UCR) - v0.1.12 7th Mar 2017

08 Mar 2017, 10:57

Awesome, thanks for taking the time to do that.

0.0.x -> 0.1.x was quite a major rewrite in terms of the code that handles this feature, so it is understandable that it broke then.
I will try and work out what has changed and fix it.

I need to do some work in this area anyway - it has never been possible, for example, to have the same keyboard key bound to multiple inputs, but it is technically feasible.
So I should do some work to harmonize the feature set across keyboard and joystick, and this would fall under that.

Regarding showing available vJoy sticks, yeah, that also is something I need to revisit. I had not planned on doing this yet though, because I am in the process of implementing a replacement for AHK's joystick support (Which will bring us full 8 axis, 128 button support) so I will quite possibly have to re-write the code that supports this feature in order to implement that.

RE: Controller names - I had to remove the old implementation because it was rendering UCR unusable for some people - the new joystick detection engine can get names, so once I implement that, this feature will return.
raron wrote:Also, I'm unsure if "Uninstall SCVPBus.." works. I haven't tried to use the vxbox yet. But when I tried to uninstall it (after installing ofc), there is no change in the IOClasses menu (install SCVPBus is still grayed out, I can run uninstall SCVPBus again though..).
If you do "Show vJoy Log", it will show some logging which may help determine if it thinks SCPVBus is installed.
To verify true state, you can also open an admin command prompt to the Resources folder and run the install / uninstall bat files

Return to “Gaming Scripts (v1)”

Who is online

Users browsing this forum: No registered users and 49 guests