【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化

一、一切的开始

  九个月的某一天,我女朋友在用Excel 核对财务报表的时候突然对我说:“用键盘快捷键切换工作表还是好麻烦,要是有什么更方便的操作方式就好了。”我答了一句:“这个简单啊,换个可编程鼠标就行了。”然后,我就成功地多了一个任务,帮她买个新鼠标。
  上面的故事告诉我们,有时候话是不可以乱讲的。现在想想,我可能被我女朋友套路了。不过想归想,鼠标还是必须要买的,剩下的问题就只是要买哪种品牌的哪款鼠标。起初我以为肯定需要选很久,然后发现在排除法之后,居然只剩下了罗技G304这唯一的选项。我女朋友对鼠标有两大硬性要求,一是要无线的,二是要好看的。首先是无线,她不接受锂电池充电式的,嫌麻烦;其次是好看,她不接受黑色的,不接受异形的,嫌丑。这些细则一套,就意味着无法选择功能键较多的游戏鼠标,按键不够也就意味着普通的鼠标宏无法满足大量的快捷键需求,也就意味着鼠标必须支持更进一步的自定义编程。纵观所有鼠标品牌,目前看来只有罗技的游戏鼠标支持Lua脚本语言编程,而罗技全系列游戏鼠标之中只有G304有白色这一配色(喷漆换壳等DIY操作不予考虑)。虽然我个人觉得带有横向滚轮的鼠标更加适合她的日常工作,但是它们的外观无一例外都被否了,想多花钱都不能的我也是非常无奈。

二、思路设计

1、查找资料

  网上关于罗技LUA编程的资料不算少,但是其中文字形式的资料极为有限,大部分教程是视频形式的。我个人在非必要的情况下懒得看视频材料,原因无他,就是觉得太墨迹效率低下。下面是我参考过的所有文档链接:

  • G系列Lua API参考文档V8.50
  • 罗技游戏软件LUA脚本编程从入门到放弃系列文章
  • Lua菜鸟教程

2、明确概念

  通读一遍API参考文档基本也就清楚了这个LUA编程所能完成的所有功能,其他的资料只是补充一些具体的语法规则和使用规范。到这里为止,我已经确定通过编程可以实现我想要的功能扩展。官方提供的API接口函数可以检测鼠标位置、鼠标指针状态、键盘锁定灯状态、键盘特殊修饰键状态,还可以控制鼠标移动、模拟鼠标按键和键盘按键。换言之,也就是可以实现几乎所有通过鼠标和键盘可以完成的操作。

3、确定功能扩展操作方案

  G304上面总共有六颗按键,且全部支持自定义编程,分别是主键、次键、中键、后退、前进、DPI循环。其中,主键和次键使用频率最高,不适合增加额外功能,所以实际可用的按键只有四颗。我的目标是将Excel中我女朋友常用的操纵快捷键以及常用函数全部囊括到鼠标按键上,保守估计需要十几个命令。
  非常自然地,我想到了日常使用中经常出现的操作,单击、双击、长按,通过这种形式,可以很轻松地将四个键扩展出十二种功能,搭配键盘上的Ctrl、Alt和Shift,更可以在这个数字上乘以七。想象很美好,而现实很残酷,在粗略设计了一张按键功能表之后我发现,这个方案有两个致命的缺陷:一是操作方式过多,导致学习成本过高,很难将每个键三种操作方式对应的功能记忆完全,以我女朋友的耐心看到那张功能表只会对我说一个字,“滚”;二是双击这个操作在脚本只能单线程运行(也就是一行代码运行完成后才会运行下一行代码)的情况下较难实现(并不是不能实现,只是麻烦),且鼠标上那四颗键双击的手感太为糟糕。
  第一个方案的破产让我考虑到了本次设计的一个隐形要求——简单,具体就是:按键操作方式要重新配置,能单击的绝对不长按,双击更是不应该纳入考虑范围;同种类型的命令尽可能集中在一颗按键上,同时尽可能让最容易按压的中键承担最多的命令。在这个方针指导之下我很快想到了第二套方案。首先,除了鼠标中键之外,其余三个键都只设定单击和长按两种触发方式。然后,对于中键则只保留单击这一操作,与其他键不同的是,鼠标指针在屏幕不同位置点击时会触发不同的功能。在这里我们不妨将屏幕以田字形划分为四个区域,在不同区域单击中键就会触发不同的快捷键,搭配上键盘上的修饰键,拓展出十几个功能绰绰有余。这一方案在设想出来后没多久就又被我否决了,理由很简单,本来使用编程鼠标的初衷就是希望在工作中能够尽可能少的移动鼠标指针,减少移动鼠标点击图形界面的麻烦。而点击屏幕不同区域来实现快捷键触发注定需要频繁移动鼠标指针在四个屏幕区域切换,鼠标移动幅度过大,与减负的初衷相违背。
  第二个方案虽然不实用,但是也提供了一个非常重要的思路,就是将按压鼠标按键与鼠标指针位置相结合。将这一思路拓展,我又得出了一个新的方案,也就是我目前采用的方案,姑且算是最终方案吧。该方案的具体内容为:将中键的功能拓展为五个,首先是单击中键,其次是按住中键向四个方向拖动。这个操作方式类似手机上的全面屏手势,从理论上来说甚至可以通过按住中键绘制不同的图形来实现不同的功能。当然,这样设计的代价有点大,编程难度大增不说,也没有任何实用性。

4、确定快捷键方案

  操作方案确定之后就需要明确有哪些常用的快捷键及Excel函数,在询问与现场观察了我女朋友的Excel操作习惯后,我整理了如下的表格:

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第1张图片

  具体的按键功能分配如下:

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第2张图片

注:表中空白部分为其他拓展功能,在实际使用中需要借助其他编程语言或程序另行配置,在此并不列出。

三、程序设计

1、API参考文档中部分函数使用说明

  • OutputLogMessage:此函数是在代码调试过程中经常会用到的一个,输出消息文本到控制台窗口。
  • IsModifierPressed:这个函数只可以检测键盘上“Ctrl”“Alt”“Shift”这三种按键的按压状态,但是在实际使用中由于“Shift”键牵扯到输入法的中英文切换,在可能的情况下尽量减少以这个键作为功能触发的辅助键。最重要的一点,在按键被按下之后需要等待十毫秒左右,此函数才能正确的返回函数值,也就是说在调用此函数的代码前需要先调用Sleep函数。
  • IsMouseButtonPressed:此函数在你将鼠标实体按键在G-HUB驱动中更改功能后将无法直接使用,因为它检测的是鼠标默认按键状态下鼠标按键对应的那些功能有无触发,而不是检测鼠标实体按键有无按下。

注:其他函数注意事项参见上面链接中的参考文档。

2、G-HUB驱动设置

  在进行具体编程之前,我通过G-HUB驱动对鼠标的默认按键功能进行了修改,以方便后续的功能拓展。如下图所示,我将鼠标中键、前进、后退以及DPI键都设置为了右Ctrl,主要原因是为了方便检测鼠标按键的按压状态,同时屏蔽按键原本功能对期望命令的影响。这里需要注意的是,因为修改了按键功能,所以鼠标原本带有的中键按压功能以及DPI切换功能失去了,所以鼠标DPI之后只能在驱动中直接修改,而如果对中键按压功能有硬性需求的话,在此处也可以不做修改,只是后面的对应代码需要进行修改。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第3张图片

  本次设计的初衷是Excel专用功能拓展,所以必定是会丧失鼠标原本一部分功能的,毕竟有得有失。在实际使用中,由于在不运行G-HUB驱动的情况下,鼠标默认为其内置的板载内存配置,所以在不需要的时候直接退出驱动就能恢复为正常的鼠标。当然,在后面的具体编程中,我也留下了切换配置的功能逻辑,有需要也可以在大框架不变的前提下加入另一套配置。

3、具体代码解析


  第一行定义一个变量,用于防止快速连续按键导致的相互干扰,当按下一个鼠标按键后,会把这个变量设为true,直到该按键对应的功能完成后再把该变量设为false。第四行定义了一个表,用来储存不同鼠标键位对应的编号,方便后面的调用,从左到右依次为主键、次键、中键、后退键、前进键、DPI键。


  第七行定义了事件触发函数,也可以理解为本次编程的主函数。第八行定义了六个局部变量,依次代表鼠标初始位置x轴坐标、初始位置y轴坐标、结束位置x轴坐标、结束位置y轴坐标、按键按下时间、按键松开时间。第十一行为第一重判断逻辑,当鼠标按键被按下,bEnabled变量为false,并且Scrolllock灯为熄灭状态时触发。第十三行获取按键按下瞬间的时刻,此数值为此脚本运行的时长。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第4张图片

  第十七行为第二重判断逻辑,当按下的按键为鼠标中键时触发。第十九行获取按键按下瞬间鼠标的位置坐标。第二十行的while死循环会在右Ctrl松开时通过break命令跳出循环,并记录下此时的时刻,因为我们此前将中键设为了右Ctrl,所以实际上记录的就是松开中键的时刻。


  第二十九行为第三重判断逻辑,当按键按压时间小于两秒时触发。第三十一行记录下鼠标松开时的位置坐标。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第5张图片

  第三十二行为第四重判断逻辑,通过计算两次坐标x、y的差值来判断鼠标移动的方向,此处为当鼠标向上运动,且运动幅度超过10个坐标单位时触发。第三十四行为第五重判断逻辑,当鼠标松开时如果Ctrl和Alt同时处于按压状态时触发,下一行“--”处为可配置的功能命令。第三十七行为单按压Ctrl时触发,第四十行为单按压Alt时触发,第四十三行为无修饰键配合时触发。需要注意,此处几个触发条件的判断顺序不能随意更改,部分条件含有包含关系。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第6张图片

  第四十六行为第四重判断逻辑,当鼠标向下运动,且运动幅度超过10个坐标单位时触发。后面同上。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第7张图片

  鼠标向左运动时触发,其他同上。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第8张图片

  鼠标向右运动时触发,其他同上。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第9张图片

  第八十八行为鼠标不移动时触发,其他同上。第一百零二行的else对应上面第三重判断逻辑,当鼠标按压时间超过两秒时触发,触发后会更改Scrolllock灯的亮灭状态,以此来进行配置切换。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第10张图片

  第一百零七行为第二重判断逻辑,当按下的按键为鼠标DPI键时触发。第一百零九行的while死循环会在两种情况下跳出,一是当按键按压大于半秒时,二是在半秒内松开按键时。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第11张图片

  此段代码的功能是当按键按压超过半秒时,先按下Alt键保持按压状态,然后通过while循环检测鼠标按键的按压状态,每半秒按一次tab键,直到鼠标按键松开时释放Alt键并按一下回车;当按键按压小于半秒时,也就是单击鼠标DPI键时,按下tab+Ctrl。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第12张图片

  此段代码判断逻辑与之前类似,仅仅是判断一下鼠标前进键是单击还是长按并分别做出相应的快捷键操作。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第13张图片

  此段代码判断逻辑与之前类似,仅仅是判断一下鼠标后退键是单击还是长按并分别做出相应的快捷键操作。第一百八十二行将bEnabled变量重新设为false,让鼠标可以响应别的按键命令。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第14张图片

  此段代码为第二套按键配置的预留框架,第一百八十五行的elseif对应第十一行的第一重判断逻辑,与其唯一的不同条件为Scrolllock灯的亮灭状态。同时还与第一百零二行处的代码相照应,长按鼠标中键两秒后触发切换配置命令。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第15张图片

  此段代码是之前代码中曾多次调用的一个自写函数,函数的逻辑很简单,此处不多赘述,主要讲一下这个函数的输入和实现的功能。函数的两个输入都是列表,前一个为必须输入,后一个为可选输入。主要功能为先按下pressH列表中的按键,然后依次按下并松开pressR列表中的按键,在pressR中按键全部输入后松开pressH中的按键。同时,pressR列表中可以直接输入单个英文字母的大写形式,对于大写字母函数会根据大写锁定灯的状态切换为大写输入状态后按下对应的英文按键。

【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化_第16张图片

  最后一段代码是为了解决编程中碰到的一个小问题而最后加上的。问题是这样的,在例如第三十九行这种通过鼠标和键盘修饰键组合触发的指令时,由于Ctrl、Alt等键处于按压状态,会与需要输入的Excel函数名中的字母组成类似复制粘贴的快捷命令,导致无法正确输入函数名称。为了解决这个问题,我在pressKeyboard函数触发之前加入了一个判断逻辑,只有当所有的修饰键松开时才会执行pressKeyboard函数。

4、代码原件下载地址

链接:https://pan.baidu.com/s/10n27EdORiGxQScY6yneDdg
提取码:wlsf

注:文章末尾附录中也有完整代码。

四、结束语

  本次Excel专用鼠标功能拓展的难点主要在于前期的操作方案设计与快捷键方案配置,后期的代码编写并没有什么难度,只要理清判断逻辑,建好框架,一层一层往里面写就行了。整个项目的耗时为五个晚上,前三个晚上都是在做方案设计,后两个晚上就完成了代码的编写。解决问题最关键的是思路的开拓,所以本文的主要目的并不是编程教学,而是给日后的我以及你们分享一下此时的我解决这个问题的思路历程。

五、附件

bEnabled = false


msBt = {L = 1, R = 2, M = 3, B = 4, F = 5, D = 6}


function OnEvent(event, arg, family)
    local x1, y1, x2, y2, t1, t2


    if(event == "MOUSE_BUTTON_PRESSED" and bEnabled == false and IsKeyLockOn("scrolllock") == false)
    then
        t1 = GetRunningTime()
        bEnabled = true


        if arg == msBt.M
        then
            x1, y1 = GetMousePosition()
            while(true)
            do
                Sleep(20)
                if(not IsModifierPressed("rctrl"))
                then
                    t2 = GetRunningTime()
                    break
                end
            end
            if((t2 - t1) < 2000)
            then
                x2, y2 = GetMousePosition()
                if(math.abs(x1 - x2) < math.abs(y1 - y2) and y1 > y2 and math.abs(y1 - y2) >= 10)
                then
                    if(IsModifierPressed("ctrl") and IsModifierPressed("alt"))
                    then
                        --
                    elseif IsModifierPressed("ctrl")
                    then
                        noModifierPressKey({"equal", "s", "u", "m", "enter"})
                    elseif IsModifierPressed("alt")
                    then
                        --
                    else
                        pressKeyboard({"c"}, {"lctrl"})
                    end
                elseif(math.abs(x1 - x2) < math.abs(y1 - y2) and y1 < y2 and math.abs(y1 - y2) >= 10)
                then
                    if(IsModifierPressed("ctrl") and IsModifierPressed("alt"))
                    then
                        --
                    elseif IsModifierPressed("ctrl")
                    then
                        noModifierPressKey({"equal", "s", "u", "m", "p", "r", "o", "d", "u", "c", "t", "enter"})
                    elseif IsModifierPressed("alt")
                    then
                        --
                    else
                        pressKeyboard({"v"}, {"lctrl"})
                    end
                elseif(math.abs(x1 - x2) > math.abs(y1 - y2) and x1 > x2 and math.abs(x1 - x2) >= 10)
                then
                    if(IsModifierPressed("ctrl") and IsModifierPressed("alt"))
                    then
                        --
                    elseif IsModifierPressed("ctrl")
                    then
                        noModifierPressKey({"equal", "r", "o","u", "n", "d", "enter"})
                    elseif IsModifierPressed("alt")
                    then
                        --
                    else
                        pressKeyboard({"z"}, {"lctrl"})
                    end
                elseif(math.abs(x1 - x2) > math.abs(y1 - y2) and x1 < x2 and math.abs(x1 - x2) >= 10)
                then
                    if(IsModifierPressed("ctrl") and IsModifierPressed("alt"))
                    then
                        --
                    elseif IsModifierPressed("ctrl")
                    then
                        noModifierPressKey({"equal", "c", "o", "u","n", "t", "i", "f", "enter"})
                    elseif IsModifierPressed("alt")
                    then
                        --
                    else
                        pressKeyboard({"f"}, {"lctrl"})
                    end
                else
                    if(IsModifierPressed("ctrl") and IsModifierPressed("alt"))
                    then
                        --
                    elseif IsModifierPressed("ctrl")
                    then
                        noModifierPressKey({"equal", "v", "l", "o", "o", "k", "u", "p", "enter"})
                    elseif IsModifierPressed("alt")
                    then
                        noModifierPressKey({"lalt", "d", "p"})
                    else
                        pressKeyboard({"a"}, {"lctrl"})
                    end
                end
            else
                PressAndReleaseKey("scrolllock")
            end


        elseif arg == msBt.D
        then
            while(true)
            do
                Sleep(20)
                t2 = GetRunningTime()
                if((t2 - t1) < 500)
                then
                    if(not IsModifierPressed("rctrl"))
                    then
                        break
                    end 
                else
                    break
                end
            end
            if ((t2 - t1) >= 500)
            then
                PressKey("lalt")
                while(true)
                do
                    if(IsModifierPressed("rctrl"))
                    then
                        PressAndReleaseKey("tab")
                    else
                        ReleaseKey("lalt")
                        PressAndReleaseKey("enter")
                        break
                    end
                    Sleep(500)
                end
            else
                pressKeyboard({"tab"}, {"lctrl"})
            end


        elseif arg == msBt.F
        then
            while(true)
            do
                Sleep(20)
                if(not IsModifierPressed("rctrl"))
                then
                    t2 = GetRunningTime()
                    break
                end
            end
            if ((t2 - t1) < 300)
            then
                pressKeyboard({"pageup"}, {"lctrl"})
            else
                pressKeyboard({"home"}, {"lctrl"})
            end


        elseif arg == msBt.B
        then
            while(true)
            do
                Sleep(20)
                if(not IsModifierPressed("rctrl"))
                then
                    t2 = GetRunningTime()
                    break
                end
            end
            if ((t2 - t1) < 300)
            then
                pressKeyboard({"pagedown"}, {"lctrl"})
            else
                pressKeyboard({"end"}, {"lctrl"})
            end
        end


        bEnabled = false


    elseif(event == "MOUSE_BUTTON_PRESSED" and bEnabled == false and IsKeyLockOn("scrolllock") == true)
    then
        t1 = GetRunningTime()
        bEnabled = true
        if arg == msBt.M
        then
            --
        elseif arg ==msBt.F
        then
            --
        elseif arg ==msBt.B
        then
            --
        elseif arg ==msBt.D
        then
            --
        end
        bEnabled = false
    end
end


function pressKeyboard(pressR, pressH)
    local i
    local element
    if(pressH)
    then
        for i, element in ipairs(pressH)
        do
            PressKey(element)
        end
    end
    if(IsKeyLockOn("capslock"))
    then
        PressAndReleaseKey("capslock")
    end
    for i, element in ipairs(pressR)
    do
        if(string.len(element) == 1 and string.lower(element) ~= element)
        then
            Sleep(5)
            PressAndReleaseKey("capslock")
            PressAndReleaseKey(string.lower(element))
            PressAndReleaseKey("capslock")
        else
            Sleep(5)
            PressAndReleaseKey(element)
        end
    end
    if(pressH)
    then
        for i, element in ipairs(pressH)
        do
            ReleaseKey(element)
        end
    end
end


function noModifierPressKey(key)
    while(true)
    do
        if ((not IsModifierPressed("ctrl")) and (not IsModifierPressed("alt")))
        then
            Sleep(5)
            pressKeyboard(key)
            break
        end
    end
end

你可能感兴趣的:(【鼠标】【罗技】【Lua】G304鼠标按键功能拓展——Excel特化)