mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-03-18 05:41:55 +01:00
UsbSubSys rework; HIDScript TriggerAction
This commit is contained in:
parent
a99836ac08
commit
60717ac478
Binary file not shown.
@ -1 +0,0 @@
|
||||
Binary blobs for modified kernel modules
|
15
dist/HIDScripts/test1.js
vendored
15
dist/HIDScripts/test1.js
vendored
@ -1,8 +1,7 @@
|
||||
//Endless looping script moving mouse, used to test interrupts and timeouts
|
||||
while(true) {
|
||||
moveStepped(200,0);
|
||||
moveStepped(0,-200);
|
||||
moveStepped(-200,0);
|
||||
moveStepped(0,200);
|
||||
delay(1000);
|
||||
}
|
||||
waitLED(ANY);
|
||||
|
||||
moveStepped(200,0);
|
||||
moveStepped(0,-200);
|
||||
moveStepped(-200,0);
|
||||
moveStepped(0,200);
|
||||
|
||||
|
@ -32,6 +32,13 @@ const (
|
||||
cSTORE_PREFIX_TRIGGER_ACTION_SET = "tas_"
|
||||
)
|
||||
|
||||
|
||||
func NewRpcServerService(root *Service) *server {
|
||||
return &server{
|
||||
rootSvc:root,
|
||||
}
|
||||
}
|
||||
|
||||
type server struct {
|
||||
rootSvc *Service
|
||||
|
||||
@ -116,12 +123,6 @@ func (s *server) DeployTriggerActionSetRemove(ctx context.Context, removeTas *pb
|
||||
|
||||
|
||||
|
||||
func NewRpcServerService(root *Service) *server {
|
||||
return &server{
|
||||
rootSvc:root,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Start() error {
|
||||
return nil
|
||||
}
|
||||
@ -181,8 +182,6 @@ func (s *server) ListenWiFiStateChanges(ctx context.Context, empty *pb.Empty) (w
|
||||
}
|
||||
|
||||
func (s *server) GetDeployedEthernetInterfaceSettings(ctx context.Context, req *pb.StringMessage) (resp *pb.EthernetInterfaceSettings, err error) {
|
||||
|
||||
|
||||
if mi,err := s.rootSvc.SubSysNetwork.GetManagedInterface(req.Msg); err == nil {
|
||||
return mi.GetState().CurrentSettings, nil
|
||||
} else {
|
||||
@ -283,7 +282,7 @@ func (s *server) FSCreateTempDirOrFile(ctx context.Context, req *pb.TempDirOrFil
|
||||
}
|
||||
|
||||
func (s *server) HIDGetRunningJobState(ctx context.Context, req *pb.HIDScriptJob) (res *pb.HIDRunningJobStateResult, err error) {
|
||||
targetJob,err := s.rootSvc.SubSysUSB.HidCtl.GetBackgroundJobByID(int(req.Id))
|
||||
targetJob,err := s.rootSvc.SubSysUSB.HidScriptGetBackgroundJobByID(int(req.Id))
|
||||
if err != nil { return nil, err }
|
||||
|
||||
vmID,_ := targetJob.GetVMId() // ignore error, as VM ID would be -1 in error case
|
||||
@ -301,11 +300,7 @@ func (s *server) HIDGetRunningJobState(ctx context.Context, req *pb.HIDScriptJob
|
||||
}
|
||||
|
||||
func (s *server) HIDGetRunningScriptJobs(ctx context.Context, rEmpty *pb.Empty) (jobs *pb.HIDScriptJobList, err error) {
|
||||
if !s.rootSvc.SubSysUSB.Usable { return nil, ErrUsbNotUsable }
|
||||
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return nil, rpcErrNoHid}
|
||||
|
||||
retJobs,err := s.rootSvc.SubSysUSB.HidCtl.GetAllBackgroundJobs()
|
||||
retJobs,err := s.rootSvc.SubSysUSB.HidScriptGetAllRunningBackgroundJobs()
|
||||
if err != nil { return nil, err }
|
||||
jobs = &pb.HIDScriptJobList{}
|
||||
for _, aJob := range retJobs {
|
||||
@ -315,91 +310,75 @@ func (s *server) HIDGetRunningScriptJobs(ctx context.Context, rEmpty *pb.Empty)
|
||||
}
|
||||
|
||||
func (s *server) HIDCancelAllScriptJobs(ctx context.Context, rEmpty *pb.Empty) (empty *pb.Empty, err error) {
|
||||
empty = &pb.Empty{}
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return empty, rpcErrNoHid}
|
||||
|
||||
// Try to find script
|
||||
s.rootSvc.SubSysUSB.HidCtl.CancelAllBackgroundJobs()
|
||||
err = s.rootSvc.SubSysUSB.HidScriptCancelAllRunningBackgroundJobs()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *server) HIDCancelScriptJob(ctx context.Context, sJob *pb.HIDScriptJob) (empty *pb.Empty, err error) {
|
||||
empty = &pb.Empty{}
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return empty, rpcErrNoHid}
|
||||
|
||||
// Try to find script
|
||||
job,err := s.rootSvc.SubSysUSB.HidCtl.GetBackgroundJobByID(int(sJob.Id))
|
||||
if err != nil { return empty, err }
|
||||
job,err := s.rootSvc.SubSysUSB.HidScriptGetBackgroundJobByID(int(sJob.Id))
|
||||
if err != nil { return nil, err }
|
||||
|
||||
job.Cancel()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) HIDRunScript(ctx context.Context, scriptReq *pb.HIDScriptRequest) (scriptRes *pb.HIDScriptResult, err error) {
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return nil, rpcErrNoHid}
|
||||
err = s.rootSvc.SubSysUSB.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
|
||||
|
||||
if scriptFile, err := ioutil.ReadFile(scriptReq.ScriptPath); err != nil {
|
||||
scriptFile, err := ioutil.ReadFile(scriptReq.ScriptPath)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Couldn't load HIDScript '%s': %v\n", scriptReq.ScriptPath, err))
|
||||
} else {
|
||||
//jobCtx := context.Background()
|
||||
jobCtx := ctx //we want to interrupt the script if the gRPC client cancels
|
||||
// ToDo: we don't retrieve the cancelFunc which should be called to free resources. Solution: use withCancel context and call cancel by go routine on timeout
|
||||
if scriptReq.TimeoutSeconds > 0 { jobCtx,_ = context.WithTimeout(jobCtx, time.Second * time.Duration(scriptReq.TimeoutSeconds))}
|
||||
}
|
||||
|
||||
// ToDo: we don't retrieve the cancelFunc which should be called to free resources. Solution: use withCancel context and call cancel by go routine on timeout
|
||||
if scriptReq.TimeoutSeconds > 0 { ctx,_ = context.WithTimeout(ctx, time.Second * time.Duration(scriptReq.TimeoutSeconds))}
|
||||
|
||||
scriptVal,err := s.rootSvc.SubSysUSB.HidCtl.RunScript(jobCtx, string(scriptFile))
|
||||
if err != nil { return nil,err }
|
||||
val,_ := scriptVal.Export() //Convert to Go representation, error is always nil
|
||||
jsonVal,err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Script seems to have succeeded but result couldn't be converted to JSON: %v\n", err))
|
||||
}
|
||||
val,err := s.rootSvc.SubSysUSB.HidScriptRun(ctx, string(scriptFile))
|
||||
if err != nil { return nil,err }
|
||||
|
||||
if jsonVal,err := json.Marshal(val); err == nil {
|
||||
scriptRes = &pb.HIDScriptResult{
|
||||
IsFinished: true,
|
||||
Job: &pb.HIDScriptJob{Id:0},
|
||||
ResultJson: string(jsonVal),
|
||||
}
|
||||
return scriptRes,nil
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("Script seems to have succeeded but result couldn't be converted to JSON: %v\n", err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *server) HIDRunScriptJob(ctx context.Context, scriptReq *pb.HIDScriptRequest) (rJob *pb.HIDScriptJob, err error) {
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return nil, rpcErrNoHid}
|
||||
err = s.rootSvc.SubSysUSB.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
if scriptFile, err := ioutil.ReadFile(scriptReq.ScriptPath); err != nil {
|
||||
scriptFile, err := ioutil.ReadFile(scriptReq.ScriptPath)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Couldn't load HIDScript '%s': %v\n", scriptReq.ScriptPath, err))
|
||||
} else {
|
||||
//Note: Don't use the gRPC context, it would cancel after this call and thus interrupt the job immediately
|
||||
jobCtx := context.Background()
|
||||
// ToDo: we don't retrieve the cancelFunc which should be called to free resources. Solution: use withCancel context and call cancel by go routine on timeout
|
||||
if scriptReq.TimeoutSeconds > 0 { jobCtx,_ = context.WithTimeout(jobCtx, time.Second * time.Duration(scriptReq.TimeoutSeconds))}
|
||||
job,err := s.rootSvc.SubSysUSB.HidCtl.StartScriptAsBackgroundJob(jobCtx, string(scriptFile))
|
||||
if err != nil { return nil,err }
|
||||
|
||||
rJob = &pb.HIDScriptJob{
|
||||
Id: uint32(job.Id),
|
||||
}
|
||||
return rJob,nil
|
||||
}
|
||||
return
|
||||
|
||||
//Note: Don't use the gRPC context, it would cancel after this call and thus interrupt the job immediately
|
||||
jobCtx := context.Background()
|
||||
// ToDo: we don't retrieve the cancelFunc which should be called to free resources. Solution: use withCancel context and call cancel by go routine on timeout
|
||||
if scriptReq.TimeoutSeconds > 0 { jobCtx,_ = context.WithTimeout(jobCtx, time.Second * time.Duration(scriptReq.TimeoutSeconds))}
|
||||
job,err := s.rootSvc.SubSysUSB.HidScriptStartBackground(jobCtx, string(scriptFile))
|
||||
if err != nil { return nil,err }
|
||||
|
||||
rJob = &pb.HIDScriptJob{
|
||||
Id: uint32(job.Id),
|
||||
}
|
||||
return rJob,nil
|
||||
}
|
||||
|
||||
func (s *server) HIDGetScriptJobResult(ctx context.Context, sJob *pb.HIDScriptJob) (scriptRes *pb.HIDScriptResult, err error) {
|
||||
if s.rootSvc.SubSysUSB.HidCtl == nil { return nil, rpcErrNoHid}
|
||||
|
||||
// Try to find script
|
||||
job,err := s.rootSvc.SubSysUSB.HidCtl.GetBackgroundJobByID(int(sJob.Id))
|
||||
if err != nil { return scriptRes, err }
|
||||
job,err := s.rootSvc.SubSysUSB.HidScriptGetBackgroundJobByID(int(sJob.Id))
|
||||
if err != nil { return nil, err }
|
||||
|
||||
|
||||
//ToDo: check impact/behavior, because ctx is provided by gRPC server
|
||||
scriptVal,err := s.rootSvc.SubSysUSB.HidCtl.WaitBackgroundJobResult(ctx, job)
|
||||
val,err := s.rootSvc.SubSysUSB.HidScriptWaitBackgroundJobResult(ctx, job)
|
||||
if err != nil { return nil,err }
|
||||
val,_ := scriptVal.Export() //Convert to Go representation, error is always nil
|
||||
jsonVal,err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Script seems to have succeeded but result couldn't be converted to JSON: %v\n", err))
|
||||
@ -410,7 +389,6 @@ func (s *server) HIDGetScriptJobResult(ctx context.Context, sJob *pb.HIDScriptJo
|
||||
ResultJson: string(jsonVal),
|
||||
}
|
||||
return scriptRes,nil
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,12 +3,14 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mame82/P4wnP1_go/common"
|
||||
"github.com/mame82/P4wnP1_go/common_web"
|
||||
pb "github.com/mame82/P4wnP1_go/proto"
|
||||
"github.com/mame82/P4wnP1_go/service/util"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -352,7 +354,65 @@ func (tam *TriggerActionManager) executeActionStartHidScript(evt *pb.Event, ta *
|
||||
|
||||
fmt.Printf("Trigger '%s' fired -> executing action '%s' ('%s')\n", triggerName, actionName, action.ScriptName)
|
||||
|
||||
// ToDo: Implement
|
||||
scriptPath := PATH_HID_SCRIPTS + "/" + action.ScriptName
|
||||
preScript := fmt.Sprintf("TRIGGER='%s';\n", triggerName)
|
||||
|
||||
switch tt {
|
||||
case triggerTypeGpioIn:
|
||||
gpioPin := ta.Trigger.(*pb.TriggerAction_GpioIn).GpioIn.GpioNum
|
||||
gpioPinName := pb.GPIONum_name[int32(gpioPin)]
|
||||
preScript += fmt.Sprintf("GPIO_PIN=%s;\n", gpioPinName)
|
||||
case triggerTypeGroupReceiveSequence:
|
||||
groupName := ta.Trigger.(*pb.TriggerAction_GroupReceiveSequence).GroupReceiveSequence.GroupName
|
||||
values := ta.Trigger.(*pb.TriggerAction_GroupReceiveSequence).GroupReceiveSequence.Values
|
||||
// create bash array of values
|
||||
jsArray := "["
|
||||
for idx,v := range values {
|
||||
if idx >= len(values) - 1 {
|
||||
jsArray += fmt.Sprintf("%d", v)
|
||||
} else {
|
||||
jsArray += fmt.Sprintf("%d, ", v)
|
||||
}
|
||||
}
|
||||
jsArray += "]"
|
||||
preScript += fmt.Sprintf("GROUP='%s';\n", groupName)
|
||||
preScript += fmt.Sprintf("var VALUES=%s;\n", jsArray)
|
||||
case triggerTypeGroupReceive:
|
||||
groupName := ta.Trigger.(*pb.TriggerAction_GroupReceive).GroupReceive.GroupName
|
||||
value := ta.Trigger.(*pb.TriggerAction_GroupReceive).GroupReceive.Value
|
||||
preScript += fmt.Sprintf("GROUP='%s';\n", groupName)
|
||||
preScript += fmt.Sprintf("VALUE=%d;\n", value)
|
||||
case triggerTypeDhcpLeaseGranted:
|
||||
iface := evt.Values[1].GetTstring()
|
||||
mac := evt.Values[2].GetTstring()
|
||||
ip := evt.Values[3].GetTstring()
|
||||
preScript += fmt.Sprintf("DHCP_LEASE_IFACE=%s;\n", iface)
|
||||
preScript += fmt.Sprintf("DHCP_LEASE_MAC=%s;\n", mac)
|
||||
preScript += fmt.Sprintf("DHCP_LEASE_IP=%s;\n", ip)
|
||||
case triggerTypeSshLogin:
|
||||
loginUser := evt.Values[1].GetTstring()
|
||||
preScript += fmt.Sprintf("SSH_LOGIN_USER=%s;\n", loginUser)
|
||||
|
||||
}
|
||||
|
||||
err := tam.rootSvc.SubSysUSB.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
scriptFile, err := ioutil.ReadFile(scriptPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't load HIDScript '%s': %v\n", scriptPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
newScriptFile := preScript + string(scriptFile)
|
||||
|
||||
_,err = tam.rootSvc.SubSysUSB.HidScriptStartBackground(context.Background(), newScriptFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't start HIDScript as background job'%s': %v\n", action.ScriptName, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (tam *TriggerActionManager) executeActionDeploySettingsTemplate(evt *pb.Event, ta *pb.TriggerAction, tt triggerType, at actionType, action *pb.ActionDeploySettingsTemplate) {
|
||||
@ -395,7 +455,7 @@ func (tam *TriggerActionManager) executeActionBashScript(evt *pb.Event, ta *pb.T
|
||||
value := ta.Trigger.(*pb.TriggerAction_GroupReceive).GroupReceive.Value
|
||||
env = append(env,
|
||||
fmt.Sprintf("GROUP='%s'", groupName),
|
||||
fmt.Sprintf("VALUE=%s", value),
|
||||
fmt.Sprintf("VALUE=%d", value),
|
||||
)
|
||||
case triggerTypeDhcpLeaseGranted:
|
||||
iface := evt.Values[1].GetTstring()
|
||||
|
@ -8,13 +8,13 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
pb "github.com/mame82/P4wnP1_go/proto"
|
||||
"time"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/mame82/P4wnP1_go/hid"
|
||||
pb "github.com/mame82/P4wnP1_go/proto"
|
||||
"net"
|
||||
"regexp"
|
||||
"github.com/mame82/P4wnP1_go/hid"
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -76,6 +76,7 @@ const (
|
||||
|
||||
var (
|
||||
ErrUsbNotUsable = errors.New("USB subsystem not available")
|
||||
ErrHidNotUsable = errors.New("HIDScript not available (mouse and keyboard disabled)")
|
||||
rp_usbHidDevName = regexp.MustCompile("(?m)DEVNAME=(.*)\n")
|
||||
)
|
||||
|
||||
@ -91,7 +92,7 @@ type UsbGadgetManager struct {
|
||||
|
||||
State *UsbManagerState
|
||||
// ToDo: variable, indicating if HIDScript is usable
|
||||
HidCtl *hid.HIDController // Points to an HID controller instance only if keyboard and/or mouse are enabled, nil otherwise
|
||||
hidCtl *hid.HIDController // Points to an HID controller instance only if keyboard and/or mouse are enabled, nil otherwise
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HandleEvent(event hid.Event) {
|
||||
@ -99,6 +100,65 @@ func (gm *UsbGadgetManager) HandleEvent(event hid.Event) {
|
||||
gm.RootSvc.SubSysEvent.Emit(ConstructEventHID(event))
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptUsable() error {
|
||||
if gm.hidCtl == nil {
|
||||
return ErrHidNotUsable
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptRun(ctx context.Context, scriptContent string) (result interface{}, err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
scriptVal,err := gm.hidCtl.RunScript(ctx, scriptContent)
|
||||
if err != nil { return nil, err}
|
||||
|
||||
return scriptVal.Export()
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptStartBackground(ctx context.Context, scriptContent string) (job *hid.AsyncOttoJob, err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
return gm.hidCtl.StartScriptAsBackgroundJob(ctx, scriptContent)
|
||||
}
|
||||
|
||||
//WaitBackgroundJobResult(ctx context.Context, job *AsyncOttoJob) (val otto.Value, err error) {
|
||||
func (gm *UsbGadgetManager) HidScriptWaitBackgroundJobResult(ctx context.Context, job *hid.AsyncOttoJob) (result interface{}, err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
scriptVal,err := gm.hidCtl.WaitBackgroundJobResult(ctx, job)
|
||||
if err != nil { return nil, err}
|
||||
|
||||
return scriptVal.Export()
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptGetBackgroundJobByID(id int) (job *hid.AsyncOttoJob, err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
return gm.hidCtl.GetBackgroundJobByID(id)
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptGetAllRunningBackgroundJobs() (jobs []*hid.AsyncOttoJob, err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
return gm.hidCtl.GetAllBackgroundJobs()
|
||||
}
|
||||
|
||||
func (gm *UsbGadgetManager) HidScriptCancelAllRunningBackgroundJobs() (err error) {
|
||||
err = gm.HidScriptUsable()
|
||||
if err != nil { return }
|
||||
|
||||
gm.hidCtl.CancelAllBackgroundJobs()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
func NewUSBGadgetManager(rooSvc *Service) (newUGM *UsbGadgetManager, err error) {
|
||||
newUGM = &UsbGadgetManager{
|
||||
RootSvc: rooSvc,
|
||||
@ -674,16 +734,16 @@ func (gm *UsbGadgetManager) DeployGadgetSettings(settings *pb.GadgetSettings) er
|
||||
devPathMouse := gm.State.DevicePath[USB_FUNCTION_HID_MOUSE_name]
|
||||
|
||||
var errH error
|
||||
gm.HidCtl, errH = hid.NewHIDController(context.Background(), devPathKeyboard, PATH_KEYBOARD_LANGUAGE_MAPS, devPathMouse)
|
||||
gm.HidCtl.SetEventHandler(gm)
|
||||
gm.hidCtl, errH = hid.NewHIDController(context.Background(), devPathKeyboard, PATH_KEYBOARD_LANGUAGE_MAPS, devPathMouse)
|
||||
gm.hidCtl.SetEventHandler(gm)
|
||||
if errH != nil {
|
||||
log.Printf("ERROR: Couldn't bring up an instance of HIDController for keyboard: '%s', mouse: '%s' and mapping path '%s'\nReason: %v\n", devPathKeyboard, devPathMouse, PATH_KEYBOARD_LANGUAGE_MAPS, errH)
|
||||
} else {
|
||||
log.Printf("HIDController for keyboard: '%s', mouse: '%s' and mapping path '%s' initialized\n", devPathKeyboard, devPathMouse, PATH_KEYBOARD_LANGUAGE_MAPS)
|
||||
}
|
||||
} else {
|
||||
if gm.HidCtl != nil { gm.HidCtl.Abort() }
|
||||
gm.HidCtl = nil
|
||||
if gm.hidCtl != nil { gm.hidCtl.Abort() }
|
||||
gm.hidCtl = nil
|
||||
log.Printf("HIDController for keyboard / mouse disabled\n")
|
||||
}
|
||||
}
|
||||
@ -765,8 +825,8 @@ func (gm *UsbGadgetManager) DestroyAllGadgets() error {
|
||||
}
|
||||
}
|
||||
|
||||
if gm.HidCtl != nil { gm.HidCtl.Abort() }
|
||||
gm.HidCtl = nil
|
||||
if gm.hidCtl != nil { gm.hidCtl.Abort() }
|
||||
gm.hidCtl = nil
|
||||
log.Printf("HIDController for keyboard / mouse disabled\n")
|
||||
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user