...

Source file src/edge-infra.dev/pkg/sds/lib/xorg/xinput/xinput.go

Documentation: edge-infra.dev/pkg/sds/lib/xorg/xinput

     1  package xinput
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"regexp"
     7  	"slices"
     8  	"strings"
     9  
    10  	"edge-infra.dev/pkg/sds/lib/xorg"
    11  )
    12  
    13  var (
    14  	xinputPath = "/usr/bin/xinput"
    15  
    16  	list     = "list"
    17  	nameOnly = "--name-only"
    18  	idOnly   = "--id-only"
    19  
    20  	absoluteModePattern = ".*Mode: absolute.*"
    21  )
    22  
    23  type InputDeviceInfo struct {
    24  	ID       xorg.InputDeviceID
    25  	Name     string
    26  	Absolute bool
    27  }
    28  
    29  type InputDeviceInfos []InputDeviceInfo
    30  
    31  func (inputDeviceInfos InputDeviceInfos) AbsoluteDevices() []InputDeviceInfo {
    32  	return slices.DeleteFunc(inputDeviceInfos, func(inputDeviceInfo InputDeviceInfo) bool {
    33  		return !inputDeviceInfo.Absolute
    34  	})
    35  }
    36  
    37  func (inputDeviceInfos InputDeviceInfos) RelativeDevices() []InputDeviceInfo {
    38  	return slices.DeleteFunc(inputDeviceInfos, func(inputDeviceInfo InputDeviceInfo) bool {
    39  		return inputDeviceInfo.Absolute
    40  	})
    41  }
    42  
    43  type Xinput interface {
    44  	GetInputDeviceInfos() (InputDeviceInfos, error)
    45  }
    46  
    47  type xinput struct{}
    48  
    49  func NewXinput() Xinput {
    50  	return &xinput{}
    51  }
    52  
    53  func (*xinput) GetInputDeviceInfos() (InputDeviceInfos, error) {
    54  	ids, names, err := listInputDevices()
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	inputDevices := []InputDeviceInfo{}
    60  	for idx, id := range ids {
    61  		absolute, err := isAbsoluteDevice(id)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		inputDevices = append(inputDevices, InputDeviceInfo{
    66  			ID:       id,
    67  			Name:     names[idx],
    68  			Absolute: absolute,
    69  		})
    70  	}
    71  
    72  	return inputDevices, nil
    73  }
    74  
    75  func listInputDevices() (ids []xorg.InputDeviceID, names []string, err error) {
    76  	ids, err = listInputDeviceIDs()
    77  	if err != nil {
    78  		return nil, nil, err
    79  	}
    80  
    81  	names, err = listInputDeviceNames()
    82  	if err != nil {
    83  		return nil, nil, err
    84  	}
    85  
    86  	if len(ids) != len(names) {
    87  		return nil, nil, fmt.Errorf(
    88  			"input device IDs and names do not match in length (%d and %d)",
    89  			len(ids), len(names),
    90  		)
    91  	}
    92  
    93  	return ids, names, err
    94  }
    95  
    96  func listInputDeviceIDs() ([]xorg.InputDeviceID, error) {
    97  	idsCmd := exec.Command(xinputPath, list, idOnly)
    98  	output, err := idsCmd.CombinedOutput()
    99  	if err != nil {
   100  		return nil, fmt.Errorf("failed to list input device ids: %s: %w", output, err)
   101  	}
   102  
   103  	ids := []xorg.InputDeviceID{}
   104  	for _, id := range entriesFromOutput(output) {
   105  		ids = append(ids, xorg.InputDeviceID(id))
   106  	}
   107  
   108  	return ids, nil
   109  }
   110  
   111  func listInputDeviceNames() ([]string, error) {
   112  	namesCmd := exec.Command(xinputPath, list, nameOnly)
   113  	output, err := namesCmd.CombinedOutput()
   114  	if err != nil {
   115  		return nil, fmt.Errorf("failed to list input device names: %s: %w", output, err)
   116  	}
   117  	return entriesFromOutput(output), nil
   118  }
   119  
   120  // Split output into entries and remove empty lines
   121  func entriesFromOutput(output []byte) []string {
   122  	lines := strings.Split(string(output), "\n")
   123  	entries := []string{}
   124  	for _, line := range lines {
   125  		if line != "" {
   126  			entries = append(entries, line)
   127  		}
   128  	}
   129  	return entries
   130  }
   131  
   132  func isAbsoluteDevice(id xorg.InputDeviceID) (bool, error) {
   133  	cmd := exec.Command(xinputPath, list, id.String()) //#nosec G204
   134  	output, err := cmd.CombinedOutput()
   135  	if err != nil {
   136  		return false, fmt.Errorf("failed to get capabilities for device %s: %s: %w", id, output, err)
   137  	}
   138  
   139  	re, err := regexp.Compile(absoluteModePattern)
   140  	if err != nil {
   141  		return false, err
   142  	}
   143  
   144  	lines := strings.Split(string(output), "\n")
   145  	for _, line := range lines {
   146  		if match := re.MatchString(line); match {
   147  			return true, nil
   148  		}
   149  	}
   150  
   151  	return false, nil
   152  }
   153  

View as plain text