I would like to use the SetMKeyState(...) and GetMKeyState(...) functions available in the Logitech LUA scripting api, to write some macros.
I am using Windows 10 and the latest version of Logitech GHUB (I can't use LGS for various reasons). On my keyboard, Logitech G815, it seems that any call to SetMKeyState(...) from a LUA macro does not do anything. While the GetMKeyState(...) seems to correctly return the current MKey state. I have read the docs related to these two functions in the "G-series Lua API V8.45" documentation and it looks like I am using them correctly.
For example, the following LUA script should switch the MKey state between M1/M2/M3 using the G1 key, but actually does not do anything (although the code is executed, as the debug log lines appear):
function OnEvent(event, arg)
if (event == "G_PRESSED" and arg == 1) then
currentState = GetMKeyState("kb")
OutputLogMessage("Current MKey state: %d\n", currentState)
newState = currentState + 1
if newState == 4 then
newState = 1
end
OutputLogMessage("Setting new MKey state: %d\n", newState )
SetMKeyState(newState , "kb");
end
end
Am I doing anything wrong here ?
If it is expected that the SetMKeyState(...) function is not supported on the Logitech G815, then which keyboard model would correctly support the SetMKeyState(...) function to change the MKey state ?
It's not actually an answer, just a suggestion, but IDK the way to paste the code in comments, so hope you will forgive me :)
There is a reference code in API doc:
-- Set the current M Key state to M1 when G1 is pressed
function OnEvent(event, arg)
if (event == "G_PRESSED" and arg == 1) then
SetMkeyState(1);
end
end
If I were you I'd get started from the check if it works or not as it is, without any change.
Then, step by step:
add debug messages;
recheck;
try to set it to 2 instead of 1;
try to set it to 3;
try to read and print current state, before and after the operation, considering this remark from API doc: "Calling GetMKeyState immediately afterwards, will likely return the previous state. Use the OnEvent handler to determine when the operation has completed."
Related
Due to an injury, I'm using a G13 to automate work tasks, one of which is entering the current date, in one of a few formats. Using the online compiler here: https://www.tutorialspoint.com/execute_lua_online.php, I was able to figure out how to get and format the date, but what I can't find is an output method that will send the string to Word, Chrome, Notepad, etc. - whatever the active window is. Outputting it to the log or LCD doesn't get it where I need it. Print doesn't output to the active window, and io.write gives the following error: [string "LuaVM"]:11: attempt to index global 'io' (a nil value). Os.date gave a similar error, that's why it's commented out.
My testbed code:
function OnEvent(event, arg)
OutputLogMessage("event = %s, arg = %s\n", event, arg)
local MKeyState = GetMKeyState("lhc")
--OutputLogMessage(MKeyState)
if (event == "G_PRESSED" and arg == 14 and MKeyState == 2) then
local date = GetDate("%m/%d/%y") --os.date("%m/%d/%y")
OutputLogMessage(date)
OutputLCDMessage(date)
io.write(date)
--print(os.date("%m/%d/%y"))
end
end
My testbed output:
event = PROFILE_DEACTIVATED, arg = 0
event = PROFILE_ACTIVATED, arg = 0
event = G_PRESSED, arg = 14
10/22/21[string "LuaVM"]:11: attempt to index global 'io' (a nil value)
event = G_RELEASED, arg = 14
Thanks in advance for any thoughts you might have.
As far as I can see, one has to use the function PressAndReleaseKey with each character of the string. It is further complicated by the circumstance that this function doesn't take the character itself as an argument, but rather a keyname (see the Table of scancodes and keynames in the API docs). For your date string, this could work (I haven't tested it):
for c in date:gmatch '.' do
if c == '/' then
c = "slash"
end
PressAndReleaseKey(c)
end
i recently picked up Lua while making a macro script for my LG mouse.
Unfortunately the Lua-API is really restricted with debug, io and file not working (source: http://www.softpanorama.org/Hardware/Peripherals/Keyboards/Logitech_G_keyboard_macros/lua_scripting.shtml#Limitations)
Here is also the official reference: https://douile.github.io/logitech-toggle-keys/APIDocs.pdf
The script is running a LGHUB macro while i want to attach mouse movement to that macro while it is running. There is no function for checking if a macro is currently running to just loop and stop the lua-side.
So I'm looking for a possibility in Lua to 'run a loop independent' which does not stop the execution of the rest of my script (to check if a status variable [isMacroRunning] has changed). I also want to break that macro if i hit another button while the macro is running.
BUT if i loop the mouse-movement, i cannot trigger another onEvent-function because the script-pointer(?) is still stuck in my loop. My current idea is to break that with a coroutine but iam not sure how to continue while no key-input is happening.
The script doesnt stop and wait if it encounters 'PlayMacro()', also this API-Function has no return value to just attach lua actions to the PlayMacro-function.
There would be also the possibility to transfer the LGHUB-macro (essentially its just pressButton A and leftclick for 40s then release the buttons) but that doesnt solve the problem (at least i cant find a solution within this) with the function being stuck in the loop and no way to break it.
For example, if MouseButton11 triggers a loop and i press MouseButton10, the $arg variable does not change to 10 but stays at 11 because the script-pointer(?) is still in the loop instead of triggering the onEvent function.
The script seems to get executed once at load (example on switching mouse-profiles) and then the onEvent-function listens. If i could somehow run a looping check on [isMacroRunning] while still be able to trigger the onEvent-function, i can make this work. But otherwise i know to little about Lua and its behavior.
Edit1: Added "essential script" which only describes the needed core-functions. This is not working due to a 2nd onEvent cannot be triggered until the 1st onEvent has finished. But the 1st onEvent is designed to break on a variable change. The variable change is triggered in the 2nd onEvent. Needed solution: some kind of workaround or use of other Lua functions to seperate the 1st onEvent execution from the 2nd onEvent.
Essential Script:
```
isMacroRunning = false
function wiggle()
PressKey("a")
while true do
if not isMacroRunning then break end
OutputLogMessage("wiggle\n")
MoveMouseRelative (5, 0)
Sleep(150)
MoveMouseRelative (-5, 0)
Sleep(150)
end
end
function OnEvent(event, arg)
if (event == "MOUSE_BUTTON_PRESSED" and arg == 11) then
isMacroRunning = true
wiggle()
end
if(event == "MOUSE_BUTTON_PRESSED" and arg == 10 and isMacroRunning) then
isMacroRunning = false
ReleaseKey("a")
end
end
´´´
Full Script:
local isMacroRunning = false
coco = coroutine.create(function()
while true do
if not isMacroRunning then break end
MoveMouseRelative (5, 0)
Sleep(150)
MoveMouseRelative (-5, 0)
Sleep(150)
coroutine.yield()
end
end)
function OnEvent(event, arg)
if (event == "MOUSE_BUTTON_PRESSED" and arg == 11) then
isMacroRunning = true
runMacro()
elseif(event == "MOUSE_BUTTON_PRESSED" and arg == 10 and isMacroRunning) then
isMacroRunning = false
runMacro()
end
end
function runMacro()
if isMacroRunning then
PlayMacro('farm')
coroutine.resume(coco)
OutputLogMessage("Start Macro\n")
else
AbortMacro()
OutputLogMessage("Aborted\n")
end
end
coroutine.resume(coco)
´´´
Step 1.
Make sure you're not using MB#4 ("backward") in the game.
If some action is assigned to MB#4 in the game, do the following:
choose keyboard button you don't currently use in the game (for example, F12)
goto GHUB(KEYS tab); bind F12 to your physical MB#4
goto game options; bind the action to F12 instead of MB#4
Now when you press physical MB#4, the game sees F12 and executes your old action.
Step 2.
Goto GHUB(SYSTEM tab); bind "Back" to MB#10
Step 3.
The script.
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 11 then
PressMouseButton(1) -- down LMB
PressKey("A") -- down "A"
repeat
MoveMouseRelative(5,0)
Sleep(150)
MoveMouseRelative(-5,0)
Sleep(150)
until IsMouseButtonPressed(4) -- btn#4 is bound to btn#10
ReleaseKey("A") -- up "A"
ReleaseMouseButton(1) -- up LMB
end
end
How does it work:
When you press MB#11, the loop starts.
When you later press MB#10, IsMouseButtonPressed() sees that button#4 is pressed, and the loop exits.
Disclaimer I dont know how to code at all and this is my first atempt at rewriting someone elses addon.
I need to check for the following before the addon loads at all.
frame:RegisterEvent("CLUB_STREAM_SUBSCRIBED");
The issue is, I dont know where or how to do that. I tried adding it on line 19 and then changing line 6 to if event == 'ADDON_LOADED' and arg1 == 'unitscan' and event == 'CLUB_STREAM_SUBSCRIBED' then
The entire code can be found at https://github.com/Damnedprinter/unitscan/blob/master/unitscan.lua
Bottom line up front:
You have, at a minimum, a mistake in Boolean logic where you say
event == 'this' and event == 'that'
but obviously something cannot be both at the same time. You can remedy this using two methods:
if ... elseif ... end (recommended for most situations)
event == 'this' or event == 'that' (not recommended)
Detailed Answer:
1. How the original addon works:
Your code on GitHub is comparable to the following:
local frame = CreateFrame("Frame");
frame:SetScript("OnEvent", function(__, event, arg1)
if (event == "ADDON_LOADED" and arg1 == "unitscan") then
doStuff();
end
end);
frame:RegisterEvent("ADDON_LOADED");
This creates a frame and tells it to respond whenever any addon finishes loading. It responds with an event handler that receives the type of the event (as event) and an argument (as arg1) which can mean different things for different kinds of events. In this example, arg1 will be the name of the addon that just finished loading (see ADDON_LOADED on Wowpedia).
The original author didn't want to do something when just any addon is loaded, so it checks to see that arg1 equals "unitscan" during the ADDON_LOADED event.
RegisterEvent is what causes the handler to be active. I can see in your source code on Git that there are in fact three different events which share the same handler... so that's why it always checks if event == 'NAME_OF_EVENT' to disambiguate which one is happening.
2. Making it work for you:
If you want the addon to also respond to CLUB_STREAM_SUBSCRIBED then I recommend using another elseif statement like in this example:
frame:SetScript("OnEvent", function(__, event, arg1, arg2)
if (event == "ADDON_LOADED" and arg1 == "unitscan") then
doStuff();
elseif (event == "CLUB_STREAM_SUBSCRIBED") then
doOtherStuff();
end
end);
Note I changed the handler to receive two arguments that I arbitrarily called arg1 and arg2. According to documentation on Wowpedia, these will be the club and stream IDs respectively. I don't know if you need these ID values for your purposes, but offer it just in case.
3. An alternative solution:
I don't recommend this solution for you! This is just an alternative.
If you want to execute the exact same code for both events, then you could simplify the above by using an or operator and some parenthesis to control the order of operations.
frame:SetScript("OnEvent", function(__, event, arg1, arg2)
if (
(event == "ADDON_LOADED" and arg1 == "unitscan")
or (event == "CLUB_STREAM_SUBSCRIBED")
) then
doStuff();
end
end);
4. Yet another solution, responding to the comment below rather than the original question
If the goal is to delay execution, then you might consider changing from ADDON_LOADED to another event such as PLAYER_ENTERING_WORLD. However, this also fires when you are entering an instance or travelling on the boat to another continent. As such, the sample code below deregisters the event to prevent this frame from acting on it more than once.
frame:SetScript("OnEvent", function(__, event, ...)
if (event == "PLAYER_ENTERING_WORLD") then
frame:UnregisterEvent("PLAYER_ENTERING_WORLD");
doStuff();
end
end);
frame:RegisterEvent("PLAYER_ENTERING_WORLD");
I have a G602 mouse and I want to use the DPI sensitive buttons (G10, G11) to control the M-Key state of my G910 keyboard. I'm trying to write a Lua script for it, but I'm having problems trying to set the M-Key state based off the API documentation sample:
if event == "MOUSE_BUTTON_PRESSED" and arg == 11 then
SetMkeyState(1,"kb")
end
I get the following error:
[string "LuaVM"]:20: attempt to call global 'SetMkeyState' (a nil value)
I even tried the exact sample from the API documentation and I get the same error:
-- Set the current M Key state to M1 when G1 is pressed
function OnEvent(event, arg)
if (event == "G_PRESSED" and arg == 1) then
SetMkeyState(1);
end
end
The command is case-sensitive and the sample in API Documentation has a typo. The letter K in SetMkeyState should be upper-case.
Using SetMKeyState works:
if event == "MOUSE_BUTTON_PRESSED" and arg == 11 then
SetMKeyState(1,"kb")
end
I am starting to learn how to use Lua scripting for different game profile with logitech software.
First I tried to use onevent (I know it isn't very advanced) and created this attack combo script
function OnEvent(event, arg)
if event == "MOUSE_BUTTON_PRESSED" and arg == 1 then --set flag for mb1
mb1_pressed = true
elseif event == "MOUSE_BUTTON_RELEASED" and arg == 1 then --set flag for mb1=false
mb1_pressed = false
end
end
if mb1_pressed then --using flags to determine whether to start attack or not
repeat
presskey("A")
Sleep(50)
releasekey("A")
Sleep(100)
--if MB1 is release, it will also break script. if i only tap mb1, this will only execute the first line of attack without the rest below
if not (**argument**, can be MB1/ismouse1) then break end
presskey("S")
Sleep(50)
releasekey("")
Sleep(120)
presskey("A")
Sleep(50)
releasekey("A")
Sleep(200)
if not (**argument**, can be MB1/ismouse1) then break end --if MB1 is release, it will also break script. this point will prevent script from looping from start if mb1 release
until not (**argument**, i use ismouse1) --end the loop of script
end
So I am trying to bind this to G6 button of my logiech mouse (using mouse_button_press == 6)
Setting a flag with MB6 works, but ending a loop/breaking a loop cannot be triggered by MB6
After some research on SDK/Lua forum of logitech support, it seems that there is a problem with my script
Flags cannot be used/detect as an argument while a script is performing a loop sequence
IsMouseButtonPressed (reads windows keypress) can be used in place or arguments
Windows only detects MB1-5, so binding to G6 is not possible (registers as 6th button)
I read that using couroutine.yield() or polling can be used for stopping repeat scripts in loop. But I cannot find a tutorial for beginners online.
Sorry for the noobish question!
I don't know anything about Logitech mice so I will try to explain things using a simplified, pure Lua example. Lets model the autoattack script as a loop that prints "A" and "B" alternatively. The "A" corresponds to the first part of your loop (press and release A) and the "B" represents the second part (press and release S and A).
function autoattack()
while true do
print("A")
print("B")
end
end
autoattack()
So far we are OK but the loop will obviously run forever and we need to add a way to stop it. I think what you are trying to do is something along the lines of:
local autoattacking = false
function autoattack()
autoattacking = true
while true do
print("A")
if not autoattacking then break end
print("B")
if not autoattacking then break end
end
end
function stop_autoattack()
autoattacking = false
end
autoattack()
stop_autoattack()
However, since autoattack is an infinite loop, stop_autoattack never runs and the autoattacking flag never gets updated. How can we fix this?
Polling
Instead of calling a function and setting a flag to stop the loop, what if we could call some code to see if the loop should be stopped or not?
function continue_autoattack()
print("continue autoattacking? y/n")
return (io.read("*l") == "y")
end
function autoattack()
while true do
print("A")
if not continue_autoattack() then break end
print("B")
if not continue_autoattack() then break end
end
end
autoattack()
In your mouse this would probably mean using some sort of isKeyPressed function, if its available in the API. Its also important to note that the autoattack loop is still an infinite loop - its just that we changed it so it is in control of its stopping condition.
Coroutines
If we want to keep the code to stop the loop outside the loop we will need a way to run the autoattack loop one step at a time. Here is an example:
local state = 1
function autoattack_step()
if state == 1 then
print("A")
state = 2
elseif state == 2
print("B")
state = 1
elseif state == 3
print("STOPPED")
--state remains as 3
else
error("bad state") -- defensive programming; I hate if/elseif without an else
end
end
function stop_autoattack()
state = 3
end
autoattack_step()
autoattack_step()
autoattack_step()
stop_autoattack()
autoattack_step()
Since we broke up the autoattack loop, we now have a chance to call stop_autoattack between calls to autoattack_step. To do this in your mouse script, I think stop_autoattack can go in "release button" handlers but I dont know where I would put the autoattack_step calls. Maybe the API includes something similar to setTimeout or setInterval in Javascript.
As for coroutines, where do they come in? Did you notice how we needed to do some substantial code refactoring to break the loop into single step chunks for autoattack_step? Coroutines are a Lua feature that lets you write code using loops while still being able to run them "one step at a time". When a coroutine reaches a coroutine.yield, it returns back to its caller. The thing is that when you call coroutine.resume again the coroutine will continue executing from where it stopped instead of going back to the start like a normal function would.
local autoattacking = true
autoattack = coroutine.create(function()
while true do
print("A")
coroutine.yield()
if not autoattacking then break end
print("B")
coroutine.yield()
if not autoattacking then break end
end
end)
function stop_autoattack()
autoattacking = false
end
coroutine.resume(autoattack)
coroutine.resume(autoattack)
coroutine.resume(autoattack)
stop_autoattack()
coroutine.resume(autoattack)
coroutine.resume(autoattack)
Very often, coroutines let you keep code more readable, without turning inside out with lots of explicit "state" variables. We still need to have some "higher up" code calling coroutine.resume though, just like we needed to have some higher level code calling autoattack_step.
Ok, so specific to Logitech's implementation of lua in the Logitech Gaming Software suite, you need to use polling.
Once you press a G-key (mouse, pad or keyboard) the OnEvent() function is called. Once inside on event no new OnEvent() events can be called until you exit, your process will become 'stuck' in any loop (as it can't exit the loop, it can't exit the OnEvent() call.
What you need is an interrupt to poll for.
There are three:- IsMouseButtonPressed( button), IsMKeyPressed( key ), IsModifierPressed( modifier ).
If you want your routiene to run while you hold the (any specified) mouse button, you can use IsMouseButtonPressed(n) thus:-
while IsMouseButtonPressed(n) do
doStuff()
end
If you wish to to say, have a toggle switch to start firing (ie: to auto-press a mouse button), then you have to use one of the other two available interrupts, ie:-
PressMouseButton(n);
while not IsModifierPressed("ctrl") do
doStuff()
end
Here your loop will run until you hold down the ctrl key. So not a puristic toggle switch (a G-key to turn on and ctrl to turn off), but passable I believe.
Note:- after further playing, er, testing, I have found IsMouseButtonPressed(n) is independent of PressMouseButton(n), rather this is read from the i/o device, so you can use IsMouseButtonPressed as the interrupt for auto-mouse pressing.
Using a G-Key to kick the action off and a mouse click to interrupt (end) the action , (or you could use both mouse and/or modifier).