Input Remapper as a universal input device configuration software

April 11, 2022 - Reading time: 9 minutes

Not many manufacturers provide good software for their devices on Linux. Usually tools can be provided to assign different functions or keys to the mouse or keyboard. On Linux, their are great projects like libratbag and its frontend PIPER, but my hardware wasn't properly supported last time I tested. This is where a tool like Input Remapper shines.

I previously was using a combination of tools (see the Logitech G400s hack page) but it wasn't as good as I needed in some cases. Input Remapper will now replace evrouter and imwheel.

So like before I will use the new g400s_hack tool to select my mouse driver mode (so I can use and remap some special buttons which would be locked otherwise) and resolution, and the input-remapper-control tool to assign profiles to my devices (keyboard, mouse, game controller, whatever input device).

Like in the Logitech G400s hack page linked above, we'll need to compile the tool and add the executable to the system and add our user permission to allow using this tool with sudo without password (not mandatory but I prefer like that to execute sudo commands in scripts).

Here's the latest update of the code I found:

//
// Change DPI and driver mode on Logitech "G400s Gaming Mouse"
// By Matti 'ccr' Hamalainen <ccr@tnsp.org> https://tnsp.org/~ccr/files/g400s_hack.c
//
// Based on https://bitbucket.org/extaliones/g400_hack/raw/b1a0f430dcb1c10991294447bf4f74a6acfff748/g400_hack.c
// by Przemek Aksamit <extaliones dot gmail.com>
//
// Compilation: gcc -o g400s_hack g400s_hack.c $(pkg-config libusb-1.0 --cflags --libs)
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>

#define G400_VENDOR_ID  0x046d
#define G400_PRODUCT_ID 0xc24c

void errmsg(const char *str, int ret)
{
  printf("%s [%d: %s / %s]\n",
    str, ret,
    libusb_error_name(ret),
    libusb_strerror(ret));
}

int main(int argc, char *argv[])
{
  libusb_context *usb_ctx = NULL;
  libusb_device_handle *usb_handle = NULL;
  int usb_detached = 0;
  int usb_claimed  = 0;
  int ret, dpi_idx, dmode = -1;
  char *arg;

  if (argc < 3)
  {
    printf(
      "Usage: %s <dpi> <mode>\n"
      "<dpi> is DPI sensitivity:\n"
      "   400|800|1800|3600\n"
      "\n"
      "<mode> is 'driver mode' value:\n"
      "   0 / off = 'driverless', special button events NOT sent to host\n"
      "   1 / on  = driver mode, special button events ARE sent to host\n"
      "\n", argv[0]);
    return 1;
  }

  // 0 => 400dpi, 1 => 800dpi, 2 => 1800dpi, 3 => 3600dpi
  arg = argv[1];
  switch (arg[0])
  {
    case '4': dpi_idx = 0; break;
    case '8': dpi_idx = 1; break;
    case '1': dpi_idx = 2; break;
    case '3': dpi_idx = 3; break;
    default:
      printf("Invalid DPI value '%s'.\n", arg);
      goto out;
  }

  arg = argv[2];
  if (strncasecmp(arg, "on", 2) == 0 ||
      strncasecmp(arg, "en", 2) == 0)
    dmode = 1;
  else
  if (strncasecmp(arg, "of", 2) == 0 ||
      strncasecmp(arg, "di", 2) == 0)
    dmode = 0;
  else
    dmode = atoi(arg);

  if (dmode != 0 && dmode != 1)
  {
    printf("Invalid driver mode value '%s', must be 0/off/disable OR 1/on/enable.\n",
      arg);
    goto out;
  }

  // Initialize libusb and find device
  if ((ret = libusb_init(&usb_ctx)) != 0)
  {
    errmsg("LibUSB initialization failed!", ret);
    goto out;
  }

  usb_handle = libusb_open_device_with_vid_pid(usb_ctx, G400_VENDOR_ID, G400_PRODUCT_ID);
  if (usb_handle == NULL)
  {
    printf("Logitech G400s not found! (Do you have permissions to the usb devices? Try with sudo?)\n");
    goto out;
  }

  // Check if there is a driver attached
  if (libusb_kernel_driver_active(usb_handle, 1) == 1)
  {
    // Attempt to detach it temporarily
    if ((ret = libusb_detach_kernel_driver(usb_handle, 1)) != 0)
    {
      errmsg("Can't detach kernel driver.", ret);
      goto out;
    }
    usb_detached = 1;
  }

  // Claim the interface for our purposes
  if ((ret = libusb_claim_interface(usb_handle, 1)) != 0)
  {
    errmsg("Failed to claim interface.", ret);
    goto out;
  }
  usb_claimed = 1;

  // Send the data
  printf("Setting DPI index to %d, driver mode is %s.\n",
    dpi_idx, dmode ? "ENABLED" : "DISABLED");

  if ((ret = libusb_control_transfer(usb_handle,
    0x40, 2, 0x008e,
    (0x03 + dpi_idx) |     // The two lowest bits seem to be needed, results in error otherwise
    (dmode ? 0x80 : 0x00), // The 8th bit seems to control the driver mode
    0, 0, 1000)) != 0)
  {
    errmsg("Error writing to USB device!", ret);
    goto out;
  }

  printf("Successfully finished.\n");

  // Shut down
out:
  if (usb_handle != NULL)
  {
    if (usb_claimed)
      libusb_release_interface(usb_handle, 1);

    if (usb_detached)
      libusb_attach_kernel_driver(usb_handle, 1);

    libusb_close(usb_handle);
  }

  if (usb_ctx != NULL)
    libusb_exit(usb_ctx);

  return 0;
}

Input Remapper is available in the AUR so the installation process is easy with an AUR helper like Pamac. After it is installed, simply enable its service, and then the GUI or command line tool can be used to manage and enable profiles. As for the g400s_hack tool, I gave my user permission to allow using sudo without password for the input-remapper-control tool in my sudoers file.

pamac build input-remapper-git
systemctl enable --now input-remapper

I created a few mouse profiles for now, but may also create some for the keyboard or game controller if I need at some point too.

To manage easily the mouse configuration I want on the fly, I made a script (that will use both the g400s_hack and input-remapper-control tools) and execute script commands from my .desktop file taskbar menu method.

Here is the ~/Scripts/g400s_hack/G400s_Profile.sh script:

#!/usr/bin/bash
# Usage: G400s_Profile.sh [preset] [dpi]
# - [preset]: Default/Games/SoT (the ones created in Input Remapper GUI)
# - [dpi]: 400/800/1800/3600
sudo /usr/bin/input-remapper-control --command stop --device "Logitech G400s Optical Gaming Mouse"
sudo /usr/bin/input-remapper-control --command start --device "Logitech G400s Optical Gaming Mouse" --preset "$1"
sudo /usr/bin/g400s_hack $2 1

Here is the ~/.local/share/applications/mymenu.desktop file:

[Desktop Entry]
Name=My Menu
Comment=Application for managing My Menu
Exec=konsole --noclose -e ~/Scripts/menu.sh
Icon=face-glasses
Terminal=false
Type=Application
Actions=UPDATE;G400S;G400GAMES;G400SOT;_SEPARATOR_;POWERSAVE;SCHEDUTIL;PERFORMANCE;_SEPARATOR_;SSHGAME;SSHVPS;_SEPARATOR_;HTOP;TEAMVIEWER;_SEPARATOR_;

[Desktop Action UPDATE]
Name=System Update
Icon=poedit-update
Exec=konsole --noclose -e ~/Scripts/menu.sh -u

[Desktop Action G400S]
Name=G400S Reset
Icon=input-mouse-symbolic
Exec=bash -c '~/Scripts/g400s_hack/G400s_Profile.sh Default 1800'

[Desktop Action G400GAMES]
Name=G400S Games
Icon=input-mouse-symbolic
Exec=bash -c '~/Scripts/g400s_hack/G400s_Profile.sh Games 1800'

[Desktop Action G400SOT]
Name=G400S SoT
Icon=input-mouse-symbolic
Exec=bash -c '~/Scripts/g400s_hack/G400s_Profile.sh SoT 1800'

[Desktop Action SSHGAME]
Name=SSH GAME
Icon=utilities-terminal-symbolic
Exec=konsole --noclose -e ssh root@123.123.123.123

[Desktop Action SSHVPS]
Name=SSH VPS
Icon=utilities-terminal-symbolic
Exec=konsole --noclose -e ssh ogp_agent@123.123.123.123

[Desktop Action TEAMVIEWER]
Name=Teamviewer
Icon=teamviewer-indicator
Exec=konsole -e systemctl start teamviewerd.service && teamviewer && systemctl stop teamviewerd.service;

[Desktop Action POWERSAVE]
Name=Power Save
Icon=cpu
Exec=sudo cpupower frequency-set -g powersave

[Desktop Action SCHEDUTIL]
Name=Schedutil
Icon=cpu
Exec=sudo cpupower frequency-set -g schedutil

[Desktop Action PERFORMANCE]
Name=Performance
Icon=cpu
Exec=sudo cpupower frequency-set -g performance

[Desktop Action HTOP]
Name=htop
Icon=htop
Exec=konsole -e htop

With that in place, I can quickly change mouse profiles and resolution by right clicking My Menu icon in the taskbar and selecting the desired profile, so it will execute a bash command, for example for the G400S SoT menu item, it will apply the SoT mouse profile and set the resolution to 1800 dpi.