Reply
kgober
Posts: 5,866
Kudos: 797
Solutions: 483
Registered: ‎05-28-2009

Volume Up/Down in Lua

[ Edited ]

in a recent version of LGS, the ability to set repeat options on media keys was lost.  this was probably unintentional, because until it gets fixed it means that volume up and down need to be pressed repeatedly -- you can no longer simply press and hold.  it makes sense to disable repeat on keys like play/pause and stop, but the volume keys really need to be repeat-enabled.

 

so, until that gets fixed you can do it using Lua scripting instead.  auto-repeat of a held key in Lua requires the use of a technique called "polling".  there is another thread in this forum that explains more about polling, why you need it, and how it works, so I won't try to explain it again here.

 

here's the script (it is quite a bit larger than it actually needs to be because it includes a lot of 'framework' code that makes things easier to understand, albeit larger too):

-- all special functions must be defined BEFORE being assigned to a button

function fn_Rep(event, num, family, delay, ...)
	local t = "Rep" .. family .. num
	if event == "G_PRESSED" or event == "MOUSE_BUTTON_PRESSED" then
		if TaskRunning(t) then AbortTask(t) end
		RunTask(t, t_Rep, delay, ...)
	elseif event == "G_RELEASED" or event == "MOUSE_BUTTON_RELEASED" then
		StopTask(t)
	end
end

function t_Rep(delay, ...)
	local t1, t2 = 20, delay - 20
	if t2 < 0 then t1, t2 = delay / 2, delay - delay / 2 end
	local f = true
	while f do
		PressKey(...)
		f = TaskSleep(t1)
		ReleaseKey(...)
		f = f and TaskSleep(t2)
	end
	return -1
end


-- G13 Advanced Gameboard Bindings

G13 = {[0] = {}}

G13[1] = {	-- Mode M1
}

G13[2] = {	-- Mode M2
}

G13[3] = {	-- Mode M3
}

-- G510 Keyboard Bindings

G510 = {[0] = {}}

G510[1] = {	-- Mode M1
}

G510[2] = {	-- Mode M2
}

G510[3] = {	-- Mode M3
}

-- G602 Mouse Bindings

G602 = {[0] = {}}

G602[1] = {
	[1] = nil,		-- left button
	[2] = nil,		-- right button
	[3] = "mb2",		-- middle (wheel) button
	[4] = "mb4",		-- G4
	[5] = "mb5",		-- G5
	[6] = nil,		-- G6
	[7] = nil,		-- G7
	[8] = nil,		-- G8
	[9] = nil,		-- G9
	[10] = {fn_Rep, 50, 0x130},		-- G10
	[11] = {fn_Rep, 50, 0x12e},		-- G11
}

-- G930 Headset

G930 = {[0] = {}}

G930[1] = {
	[1] = nil,		-- G1
	[2] = nil,		-- G2
	[3] = nil,		-- G3
}

-- Choose which device(s) you are using.  The LGS Lua environment supports only one device for each category.

Map = {
	["lhc"] = G13,
	["kb"] = G510,
	["mouse"] = G602,
	[""] = G930,
}


-- everything below this line is "framework" code and shouldn't normally be changed

function OnEvent(event, arg, family)
	local st = StateTimer
	if event == "G_PRESSED" or event == "MOUSE_BUTTON_PRESSED" then
		local mode = GetMKeyState(family)
		local map = Map[family][mode] or {}
		local action = map[arg]
		local t = type(action)
		if t == "function" then
			action(event, arg, family)
		elseif t == "table" then
			local f = action[1]
			if type(f) == "function" then
				f(event, arg, family, unpack(action, 2))
			else
				PressKey(unpack(action))
			end
		elseif t == "string" or t == "number" then
			PressKey(action)
		end
		Map[family][0][arg] = action
	elseif event == "G_RELEASED" or event == "MOUSE_BUTTON_RELEASED" then
		local map = Map[family][0]
		local action = map[arg]
		local t = type(action)
		if t == "function" then
			action(event, arg, family)
		elseif t == "table" then
			local f = action[1]
			if type(f) == "function" then
				f(event, arg, family, unpack(action, 2))
			else
				ReleaseKey(unpack(action))
			end
		elseif t == "string" or t == "number" then
			ReleaseKey(action)
		end
		map[arg] = nil
	elseif event == "PROFILE_ACTIVATED" then
		EnablePrimaryMouseButtonEvents(true)
		InitPolling()
	elseif event == "PROFILE_DEACTIVATED" then
		ReleaseAllKeys()
	end
	DoTasks()
	Poll(event, arg, family, st)
end

POLL_FAMILY = "mouse"	-- current mice don't have M-states, so this is a good choice
POLL_INTERVAL = 10	-- delay (in milliseconds) before next loop, used to throttle polling rate
POLL_DEADTIME = 100	-- settling time (in milliseconds) during which old poll events are drained

function InitPolling()
	ActiveState = GetMKeyState_Hook(POLL_FAMILY)
	SetMKeyState_Hook(ActiveState, POLL_FAMILY)
end

function Poll(event, arg, family, st)
	if st == nil and StateTimer ~= nil then return end
	local t = GetRunningTime()
	if family == POLL_FAMILY then
		if event == "M_PRESSED" and arg ~= ActiveState then
			if StateTimer ~= nil and t >= StateTimer then StateTimer = nil end
			if StateTimer == nil then ActiveState = arg end
			StateTimer = t + POLL_DEADTIME
		elseif event == "M_RELEASED" and arg == ActiveState then
			Sleep(POLL_INTERVAL)
			SetMKeyState_Hook(ActiveState, POLL_FAMILY)
		end
	end
end

GetMKeyState_Hook = GetMKeyState
GetMKeyState = function(family)
	family = family or "kb"
	if family == POLL_FAMILY then
		return ActiveState
	else
		return GetMKeyState_Hook(family)
	end
end

SetMKeyState_Hook = SetMKeyState
SetMKeyState = function(mkey, family)
	family = family or "kb"
	if family == POLL_FAMILY then
		if mkey == ActiveState then return end
		ActiveState = mkey
		StateTimer = GetRunningTime() + POLL_DEADTIME
	end
	return SetMKeyState_Hook(mkey, family)
end


-- Task Management functions

TaskList = {}

function DoTasks()
	local t = GetRunningTime()
	for key, task in pairs(TaskList) do
		if t >= task.time then
			local s, d = coroutine.resume(task.task, task.run)
			if (not s) or ((d or -1) < 0) then
				TaskList[key] = nil
			else
				task.time = task.time + d
			end
		end
	end
end

function RunTask(key, func, ...)
	AbortTask(key)
	local task = {}
	task.time = GetRunningTime()
	task.task = coroutine.create(func)
	task.run = true
	local s, d = coroutine.resume(task.task, ...)
	if (s) and ((d or -1) >= 0) then
		task.time = task.time + d
		TaskList[key] = task
	end
end

function StopTask(key)
	local task = TaskList[key]
	if task ~= nil then task.run = false end
end

function AbortTask(key)
	local task = TaskList[key]
	if task == nil then return end
	while true do
		local s, d = coroutine.resume(task.task, false)
		if (not s) or ((d or -1) < 0) then
			TaskList[key] = nil
			return
		end
	end
end

function TaskRunning(key)
	local task = TaskList[key]
	if task == nil then return false end
	return task.run
end

function TaskSleep(delay)
	return coroutine.yield(delay)
end


-- PressKey / ReleaseKey enhancements
-- if PressKey is called n times for a key, don't release until ReleaseKey has been called n times
-- allow arguments to be tables
-- release keys in reverse order of how they were pressed
-- allow "_", "^" and "@" prefixes for key-specific Shift, Ctrl and Alt modifiers
-- allow "mb1" through "mb5" for mouse buttons

MODIFIER_PREDELAY = 20	-- 20ms delay to allow Shift, Ctrl or Alt to register
MODIFIER_POSTDELAY = 10	-- 10ms delay before releasing Shift, Ctrl or Alt

KeyCounts = {}

PressKey_Hook = PressKey
PressKey = function(...)
	local n = select("#", ...)
	for i = 1, n do
		local arg = select(i, ...)
		if type(arg) == "table" then
			PressKey(unpack(arg))
		elseif arg ~= nil then
			local fShift, fCtrl, fAlt
			local c = string.sub(arg, 1, 1)
			while c == "_" or c == "^" or c == "@" do
				if c == "_" then
					fShift = true
				elseif c == "^" then
					fCtrl = true
				else
					fAlt = true
				end
				arg = string.sub(arg, 2)
				c = string.sub(arg, 1, 1)
			end
			c = KeyCounts[arg] or 0
			c = c + 1
			if c == 1 then
				if fShift then PressKey_Hook("lshift") end
				if fCtrl then PressKey_Hook("lctrl") end
				if fAlt then PressKey_Hook("lalt") end
				if fShift or fCtrl or fAlt then Sleep(MODIFIER_PREDELAY) end
				if string.sub(arg, 1, 2) == "mb" then
					PressMouseButton(string.sub(arg, 3))
				else
					PressKey_Hook(arg)
				end
				if fShift or fCtrl or fAlt then Sleep(MODIFIER_POSTDELAY) end
				if fAlt then ReleaseKey_Hook("lalt") end
				if fCtrl then ReleaseKey_Hook("lctrl") end
				if fShift then ReleaseKey_Hook("lshift") end
			end
			KeyCounts[arg] = c
		end
	end
end

ReleaseKey_Hook = ReleaseKey
ReleaseKey = function(...)
	local n = select("#", ...)
	for i = n, 1, -1 do
		local arg = select(i, ...)
		if type(arg) == "table" then
			ReleaseKey(unpack(arg))
		elseif arg ~= nil then
			local c = string.sub(arg, 1, 1)
			while c == "_" or c == "^" or c == "@" do
				arg = string.sub(arg, 2)
				c = string.sub(arg, 1, 1)
			end
			c = KeyCounts[arg] or 1
			c = c - 1
			if c == 0 then
				c = nil
				if string.sub(arg, 1, 2) == "mb" then
					ReleaseMouseButton(string.sub(arg, 3))
				else
					ReleaseKey_Hook(arg)
				end
			end
			KeyCounts[arg] = c
		end
	end
end

function ReleaseAllKeys()
	for arg, _ in pairs(KeyCounts) do
		KeyCounts[arg] = nil
		if string.sub(arg, 1, 2) == "mb" then
			ReleaseMouseButton(string.sub(arg, 3))
		else
			ReleaseKey_Hook(arg)
		end
	end
end

 

in this example I have volume up/down assigned to the G10/G11 buttons on my G602 mouse.  pressing these buttons invokes the "fn_Rep" function which starts or stops a repeating task depending on whether the button was pressed or released.

 

the repeating task is performed by making the "t_Rep" function into a coroutine which receives a repeat delay (50 is 50ms, which gives a repeat rate of 20 per second) and a key (or keys) to be repeated.  polling is used to continuously pause and resume the "t_Rep" coroutine (the coroutine gets paused whenever it calls TaskSleep(), and polling is used to resume the coroutine when the sleep amount has passed).

 

EDIT (2012-07-12): fixed a small bug in PressKey and ReleaseKey that could cause "mb1" through "mb5" mouse button presses to prevent "1" through "5" key presses from working.

 

-ken

________________________________
I do not work for Logitech. I'm just a user.
Logi Nu
Fady
Posts: 4
Registered: ‎06-27-2014

Re: Volume Up/Down in Lua

[ Edited ]

Wow, I need this workaround :smileyhappy:

 

For G400s I changed following and it works!

 

-- G400s Mouse Bindings

G400s = {[0] = {}}

G400s[1] = {
	[1] = nil,		-- left button
	[2] = nil,		-- right button
	[3] = "mb2",		-- middle (wheel) button
	[4] = "mb4",		-- G4
	[5] = "mb5",		-- G5
	[6] = nil,		-- G6
	[7] =  {fn_Rep, 50, 0x130},		-- G7
	[8] = {fn_Rep, 50, 0x12e},		-- G8
	[9] = nil,		-- G9
	[10] = nil,		-- G10
	[11] = nil,		-- G11
}

-- Choose which device(s) you are using. 

Map = {
	["mouse"] = G400s,
}

 

 

 Btw, what does mb2, mb4, mb5 (line 60- 62) ?

 

	[3] = "mb2",		-- middle (wheel) button
	[4] = "mb4",		-- G4
	[5] = "mb5",		-- G5

 

 

 

 

 

Btw, my original post about this volume up/down usability issue: http://forums.logitech.com/t5/G-series-Gaming-Mice/Logitech-gaming-software-volume-up-down-usability...

kgober
Posts: 5,866
Kudos: 797
Solutions: 483
Registered: ‎05-28-2009

Re: Volume Up/Down in Lua

"mb2", "mb4" and "mb5" set the associated button to work as middle (wheel) button, back button and forward button respectively.  this allows you to have all your buttons unassigned on the LGS button assigments screen and do everything from within the Lua script.

 

if you want to assign the wheel button and the back and forward buttons on the LGS button assignments screen, then set them to 'nil' in the script.  otherwise you'll end up getting both the button assignments action as well as the Lua action, which is usually not what you wanted.

 

-ken

________________________________
I do not work for Logitech. I'm just a user.
Logi Nu
Jeffen
Posts: 4
Registered: ‎11-02-2014

Re: Volume Up/Down in Lua

im not a scripter so idk what i have to do here

 

i want g8 for volume up and g9 for volume down on a g602

 

how do i make that work? :s

kgober
Posts: 5,866
Kudos: 797
Solutions: 483
Registered: ‎05-28-2009

Re: Volume Up/Down in Lua

the sample in the first post is set up for buttons 10 and 11.  just change the 10 and 11 to 8 and 9.

 

to access the script editor, right click on the profile icon while in the button assignments screen, then choose Scripting from the menu.

 

-ken

________________________________
I do not work for Logitech. I'm just a user.
Logi Nu
Jeffen
Posts: 4
Registered: ‎11-02-2014

Re: Volume Up/Down in Lua

sweet that worked, only 1 little problem

 

when clicking the button it immediately repeats so that it acts as if the button has been pressed several times, is there a way to add a delay so that it only starts repeating after letse say 500ms?

kgober
Posts: 5,866
Kudos: 797
Solutions: 483
Registered: ‎05-28-2009

Re: Volume Up/Down in Lua

an initial delay would involve more coding which would take more time than I have right this second, but in the meantime you can slow down the repeat rate, right now it's 50 so changing it to 100 or 150 will make it repeat slower.  remember to change it for both buttons so that it goes up and down at the same speed.

 

-ken

________________________________
I do not work for Logitech. I'm just a user.