<commit_before>/// @file SNIIS_Linux.cpp
/// Linux implementation of input

#include "SNIIS_Linux.h"
#include "SNIIS_Intern.h"

#if SNIIS_SYSTEM_LINUX
using namespace SNIIS;
#include "../Traumklassen/TraumBasis.h"

#include <cstring>
#include <sstream>
#include <fcntl.h>
#include <linux/input.h>

static bool IsBitSet( const uint8_t* bits, size_t i) { return (bits[i/8] & (1<<(i&7))) != 0; }

// --------------------------------------------------------------------------------------------------------------------
// Constructor
LinuxInput::LinuxInput( Window wnd)
{
  mWindow = wnd;
  mDisplay = nullptr;

  mDisplay = XOpenDisplay( nullptr);
  if( !mDisplay )
    throw std::runtime_error( "Failed to open XDisplay");

  int event = 0, error = 0;
  int major = 2, minor = 0;
  bool isAvailable = (XQueryExtension( mDisplay, "XInputExtension", &mXiOpcode, &event, &error) != False
               && XIQueryVersion( mDisplay, &major, &minor) != BadRequest);
  if( !isAvailable )
    throw std::runtime_error( "Failed to get XInputExtension");

  // Register for events
  XIEventMask evmask;
  uint8_t mask[] = { 0, 0, 0, 0 };
  XISetMask( mask, XI_HierarchyChanged);
  XISetMask( mask, XI_RawMotion);
  XISetMask( mask, XI_RawButtonPress);
  XISetMask( mask, XI_RawButtonRelease);
  XISetMask( mask, XI_RawKeyPress);
  XISetMask( mask, XI_RawKeyRelease);

  evmask.deviceid = XIAllDevices;
  evmask.mask_len = sizeof( mask);
  evmask.mask = mask;
  auto selwnd = DefaultRootWindow( mDisplay);
  if( XISelectEvents( mDisplay, selwnd, &evmask, 1) != 0 )
    throw std::runtime_error( "Failed to register for XInput2 events");

  int deviceCount = 0;
  XIDeviceInfo* devices = XIQueryDevice( mDisplay, XIAllDevices, &deviceCount);
  for( int i = 0; i < deviceCount; i++ )
  {
    const auto& dev = devices[i];
    static const char* sTypeName[6] = {
      "Invalid", "XIMasterPointer", "XIMasterKeyboard", "XISlavePointer", "XISlaveKeyboard", "XIFloatingSlave"
    };
    Traum::Konsole.Log( "Input device of type %d - \"%s\"", dev.use < 6 ? sTypeName[dev.use] : "Unknown", devices[i].name);

    /* We only look at "slave" devices. "Master" pointers are the logical
     * cursors, "slave" pointers are the hardware that back them.
     * "Floating slaves" are hardware that don't back a cursor.
     */
    if( dev.use != XISlavePointer && dev.use != XISlaveKeyboard && dev.use != XIFloatingSlave )
      continue;
    // Ignore some common pffft cases
    if( strstr(devices[i].name, "XTEST") != nullptr )
      continue;

    // Turns out the use field is unreliable. I got reports from keyboards being reported as mice because a pointer.
    // So I hereby declare the new method of distinguishing those by judging the axis/button ratio.
    size_t numButtons = 0, numAxes = 0;
    bool isAxisPresent[2] = { false, false };
    for( int a = 0; a < dev.num_classes; ++a )
    {
      auto cl = dev.classes[a];
      switch( cl->type )
      {
        case XIButtonClass:
        {
          auto bcl = reinterpret_cast<const XIButtonClassInfo*> (cl);
          numButtons += bcl->num_buttons;
          break;
        }
        case XIKeyClass:
        {
          // keys are most probably on a keyboard. I've never seen less than 248 num_keycodes, so this dominates the count
          auto kcl = reinterpret_cast<const XIKeyClassInfo*> (cl);
          numButtons += kcl->num_keycodes;
          break;
        }
        case XIValuatorClass:
        {
          // an axis
          auto vcl = reinterpret_cast<const XIValuatorClassInfo*> (cl);
          if( vcl->number < 2 )
            isAxisPresent[vcl->number] = true;
          numAxes++;
          break;
        }
        case XIScrollClass:
        {
          // probably a scroll wheel - might be on a mouse or a keyboard
          numAxes++;
          break;
        }
      }
    }

    // A mouse has at least two absolute axes, unfortunately we have no means to know if those are X and Y axes.
    // A mouse also has a few buttons, maybe a dozen at max. A keyboard, on the other hand, has at least several dozens
    // of buttons, but might also feature a few axes.
    if( isAxisPresent[0] && isAxisPresent[1] && numButtons < numAxes * 10 )
    {
      Traum::Konsole.Log( "%d axes, %d buttons - register this as mouse %d (id %d)", numAxes, numButtons, mNumMice, mDevices.size());
      try {
        auto m = new LinuxMouse( this, mDevices.size(), devices[i]);
        InputSystemHelper::AddDevice( m);
        mMiceById[devices[i].deviceid] = m;
      } catch( std::exception& e)
      {
        // TODO: invent logging
        Traum::Konsole.Log( "Exception: %s", e.what());
      }
    }
    else
    {
      Traum::Konsole.Log( "%d axes, %d buttons - register this as keyboard %d (id %d)", numAxes, numButtons, mNumKeyboards, mDevices.size());
      try {
        auto k = new LinuxKeyboard( this, mDevices.size(), devices[i]);
        InputSystemHelper::AddDevice( k);
        mKeyboardsById[devices[i].deviceid] = k;
      } catch( std::exception& e)
      {<commit_msg>- third try second guessing the type of Linux input devices - this time we generate both a mouse and a keyboard if the device exports those controls<commit_after>/// @file SNIIS_Linux.cpp
/// Linux implementation of input

#include "SNIIS_Linux.h"
#include "SNIIS_Intern.h"

#if SNIIS_SYSTEM_LINUX
using namespace SNIIS;
#include "../Traumklassen/TraumBasis.h"

#include <cstring>
#include <sstream>
#include <fcntl.h>
#include <linux/input.h>

static bool IsBitSet( const uint8_t* bits, size_t i) { return (bits[i/8] & (1<<(i&7))) != 0; }

// --------------------------------------------------------------------------------------------------------------------
// Constructor
LinuxInput::LinuxInput( Window wnd)
{
  mWindow = wnd;
  mDisplay = nullptr;

  mDisplay = XOpenDisplay( nullptr);
  if( !mDisplay )
    throw std::runtime_error( "Failed to open XDisplay");

  int event = 0, error = 0;
  int major = 2, minor = 0;
  bool isAvailable = (XQueryExtension( mDisplay, "XInputExtension", &mXiOpcode, &event, &error) != False
               && XIQueryVersion( mDisplay, &major, &minor) != BadRequest);
  if( !isAvailable )
    throw std::runtime_error( "Failed to get XInputExtension");

  // Register for events
  XIEventMask evmask;
  uint8_t mask[] = { 0, 0, 0, 0 };
  XISetMask( mask, XI_HierarchyChanged);
  XISetMask( mask, XI_RawMotion);
  XISetMask( mask, XI_RawButtonPress);
  XISetMask( mask, XI_RawButtonRelease);
  XISetMask( mask, XI_RawKeyPress);
  XISetMask( mask, XI_RawKeyRelease);

  evmask.deviceid = XIAllDevices;
  evmask.mask_len = sizeof( mask);
  evmask.mask = mask;
  auto selwnd = DefaultRootWindow( mDisplay);
  if( XISelectEvents( mDisplay, selwnd, &evmask, 1) != 0 )
    throw std::runtime_error( "Failed to register for XInput2 events");

  int deviceCount = 0;
  XIDeviceInfo* devices = XIQueryDevice( mDisplay, XIAllDevices, &deviceCount);
  for( int i = 0; i < deviceCount; i++ )
  {
    const auto& dev = devices[i];
    /// We only look at "slave" devices. "Master" pointers are the logical cursors, "slave" pointers are the hardware
    /// that back them. "Floating slaves" are hardware that don't back a cursor.
    if( dev.use != XISlavePointer && dev.use != XISlaveKeyboard && dev.use != XIFloatingSlave )
      continue;
    // Ignore some common pffft cases
    if( strstr(devices[i].name, "XTEST") != nullptr )
      continue;

    // Turns out the use field is unreliable. I got reports from keyboards being reported as mice because they back
    // a pointer. I also got reports from mice exposing "keys", and XInput always reports >248 keys if at least one key
    // is present. So I see no other options but to register some devices as a mouse AND a keyboard.
    size_t numButtons = 0, numAxes = 0, numKeys = 0;
    bool isAxisPresent[2] = { false, false };
    for( int a = 0; a < dev.num_classes; ++a )
    {
      auto cl = dev.classes[a];
      switch( cl->type )
      {
        case XIButtonClass:
        {
          auto bcl = reinterpret_cast<const XIButtonClassInfo*> (cl);
          numButtons += bcl->num_buttons;
          break;
        }
        case XIKeyClass:
        {
          // keys are most probably on a keyboard. I've never seen less than 248 num_keycodes; I wonder what made them do this.
          auto kcl = reinterpret_cast<const XIKeyClassInfo*> (cl);
          numKeys += kcl->num_keycodes;
          break;
        }
        case XIValuatorClass:
        {
          // an axis
          auto vcl = reinterpret_cast<const XIValuatorClassInfo*> (cl);
          if( vcl->number < 2 )
            isAxisPresent[vcl->number] = true;
          numAxes++;
          break;
        }
        case XIScrollClass:
        {
          // probably a scroll wheel - might be on a mouse or a keyboard
          numAxes++;
          break;
        }
      }
    }

    static const char* sTypeName[6] = {
      "Invalid", "XIMasterPointer", "XIMasterKeyboard", "XISlavePointer", "XISlaveKeyboard", "XIFloatingSlave"
    };
    Traum::Konsole.Log( "Input device of type %d - \"%s\" - %d axes, %d buttons, %d keys",
      dev.use < 6 ? sTypeName[dev.use] : "Unknown", devices[i].name, numAxes, numButtons, numKeys);

    // A mouse has at least two absolute axes, unfortunately we have no means to know if those are X and Y axes.
    // A mouse also has a few buttons, maybe a dozen at max.
    if( isAxisPresent[0] && isAxisPresent[1] )
    {
      Traum::Konsole.Log( "-> register this as mouse %d (id %d)", mNumMice, mDevices.size());
      try {
        auto m = new LinuxMouse( this, mDevices.size(), devices[i]);
        InputSystemHelper::AddDevice( m);
        mMiceById[devices[i].deviceid] = m;
      } catch( std::exception& e)
      {
        // TODO: invent logging
        Traum::Konsole.Log( "Exception: %s", e.what());
      }
    }

    // A keyboard on the other hand has keys, but might also feature a few axes. So register a device as both if necessary.
    if( numKeys > 0 )
    {
      Traum::Konsole.Log( "-> register this as keyboard %d (id %d)", mNumKeyboards, mDevices.size());
      try {
        auto k = new LinuxKeyboard( this, mDevices.size(), devices[i]);
        InputSystemHelper::AddDevice( k);
        mKeyboardsById[devices[i].deviceid] = k;
      } catch( std::exception& e)
      {
{'fraction_non_alphanumeric': 0.1400657767459857, 'fraction_numerical': 0.006674405107370864, 'mean_word_length': 2.527465029000341, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3461859', 'n_tokens_mistral': 3351, 'n_tokens_neox': 3255, 'n_words': 1032}