My “multiseat” (actually multiple X servers and one physical seat) configuration and zaphodkvm

I know I’m not the first to run with multiple X servers for a single user, but since these configurations are so rare I thought it would be good to put my details out there. I also wrote a program I call zaphodkvm to handle my input devices. My motivation for this project was to be able to use my second monitor to browse the web while using my primary monitor for fullscreen games that grab input devices, Dota 2 being my main concern at the moment (if you’d like to connect via Steam, hit me up for my Steam name via email). There is a program that does this for Windows called “Actual Multiple Monitors,” that is slightly more featureful, but it seems like getting something really similar to AMM on Linux would require a lot of window manager specific hacking. I could be wrong about that, but for now I went with a multiple X server approach rather than xrandr. That leads me to a discussion of:

Limitations

  • I can’t drag windows between the monitors, since they are separate X servers.
  • I may eventually add copy/paste sync but for now, the monitors have separate clipboards.
  • After I put my computer to sleep (ACPI S3, suspend-to-ram) and wake it up, the second X server dies along with any programs running on it. To restart the second monitor, if I want to use it, requires quitting the primary X server and reloading the module “nvidia”.
  • I use the package xbindkeys to handle keyboard shortcuts. For some reason, xbindkeys doesn’t seem to like zaphodkvm. xbindkeys will randomly start capturing my “up” key presses, making me restart xbindkeys to fix it. I might switch to Xfce keyboard shortcuts instead.
  • I use a simple mouse and keyboard. zaphodkvm won’t work for fancy mice and keyboards without a tiny bit of tweaking. If you need that let me know and it shouldn’t be hard to fix.

Hardware

I have an nVidia GTX 660 as my primary video card. I bought a second nVidia card, a GT 610, to run the second monitor. A separate graphics device is definitely required (no using multiple outputs on a single card). I initially tried using my onboard HD 4000 Intel graphics for the second monitor. Unfortunately, the kernel modesetting portions of the i915 driver did not interact well with nVidia’s binary driver. VTs 1 through 6 were messed up, and after a suspend/resume, the GTX 660 would go down, requiring a reboot. I read a thread somewhere saying that the kernel isn’t able to reset multiple cards at all after a resume, so I consider myself lucky that only the GT 610 is having suspend/resume issues.
My GTX 660
My GT 610: Zotac ZT-60607-10L (I needed a PCIe x1 card)

Xorg

lspci | grep VGA
01:00.0 VGA compatible controller: NVIDIA Corporation GK106 [GeForce GTX 660] (rev a1)
03:00.0 VGA compatible controller: NVIDIA Corporation GF119 [GeForce GT 610] (rev a1)

xorg.conf for the primary monitor:

Section "ServerLayout"
   Identifier     "Simple Layout"
   Screen      0  "Screen0" 0 0
   Option         "SingleCard" "true"
   InputDevice    "mouse0"     "CorePointer"
   InputDevice    "keyboard0"  "CoreKeyboard"
EndSection

#possibly only useful for archaic apps
Section "Files"
   FontPath        "/usr/share/fonts/misc/"
   FontPath        "/usr/share/fonts/Type1/"
   FontPath        "/usr/share/fonts/75dpi/"
   FontPath        "/usr/share/fonts/100dpi/"
   FontPath        "/usr/share/fonts/winfonts/"
   FontPath        "/usr/share/fonts/terminus/"
   FontPath        "/usr/share/fonts/ttf-bitstream-vera/"
   FontPath        "/usr/share/fonts/unifont/"
   FontPath        "/usr/share/fonts/freefont/"
EndSection

Section "ServerFlags"
   Option         "StandbyTime" "20"
   Option         "SuspendTime" "20"
   Option         "OffTime" "20"
   Option         "BlankTime" "0" # Blank the screen after 5 minutes (Fake)
   #For zaphodkvm
   Option         "AutoAddDevices" "false"
   Option         "AutoEnableDevices" "false"
   Option         "AllowMouseOpenFail" "on"
   Option         "AllowEmptyInput" "on"
EndSection

Section "InputClass"
   Identifier     "MyInputClass"
   Option         "Ignore"       "yes"
EndSection

Section "InputDevice"
   Identifier "keyboard0"
   Driver     "evdev"
   Option     "Device"     "/dev/input/by-id/zaphodkvm_kbd0"
   Option     "GrabDevice" "true"
EndSection

Section "InputDevice"
   Identifier "mouse0"
   Driver     "evdev"
   Option     "Device"     "/dev/input/by-id/zaphodkvm_mouse0"
   Option     "GrabDevice" "true"
EndSection

Section "Monitor"
   Identifier     "Monitor0"
   Option         "DPMS"
EndSection

Section "Device"
   Identifier     "Videocard0"
   Driver         "nvidia"
   Option         "NoLogo" "true"
   Option         "RandRRotation"
   Option         "ModeValidation" "NoHorizSyncCheck,NoVertRefreshCheck,NoVesaModes"
   Option         "AddARGBGLXVisuals" "True"
   Option         "ProbeAllGpus" "false"
   BusId          "PCI:1:0:0"
EndSection

Section "Screen"
   Identifier     "Screen0"
   Device         "Videocard0"
   Monitor        "Monitor0"
   DefaultDepth    24
EndSection

xorg.conf for the second monitor (I called this file nvidia610.conf):

Section "ServerLayout"
   Identifier     "Simple Layout"
   Screen         0  "Screen0" 0 0
   Option         "SingleCard" "true"
   InputDevice    "mouse0"     "CorePointer"
   InputDevice    "keyboard0"  "CoreKeyboard"
EndSection

#possibly only useful for archaic apps
Section "Files"
   FontPath        "/usr/share/fonts/misc/"
   FontPath        "/usr/share/fonts/Type1/"
   FontPath        "/usr/share/fonts/75dpi/"
   FontPath        "/usr/share/fonts/100dpi/"
   FontPath        "/usr/share/fonts/winfonts/"
   FontPath        "/usr/share/fonts/terminus/"
   FontPath        "/usr/share/fonts/ttf-bitstream-vera/"
   FontPath        "/usr/share/fonts/unifont/"
   FontPath        "/usr/share/fonts/freefont/"
EndSection

Section "ServerFlags"
   Option         "StandbyTime" "40"
   Option         "SuspendTime" "40"
   Option         "OffTime" "40"
   Option         "BlankTime" "0" # Blank the screen after 5 minutes (Fake)
   Option         "AutoAddDevices" "false"
   Option         "AutoEnableDevices" "false"
   Option         "AllowMouseOpenFail" "on"
   Option         "AllowEmptyInput" "on"
   #this should do the exact same thing as -novtswitch , but it's here just to make sure
   Option         "DontVTSwitch" "true"
EndSection

Section "InputClass"
   Identifier     "MyInputClass"
   Option         "Ignore"       "yes"
EndSection

Section "InputDevice"
   Identifier "keyboard0"
   Driver     "evdev"
   Option     "Device"    "/dev/input/by-id/zaphodkvm_kbd1"
   Option     "GrabDevice" "true"
EndSection

Section "InputDevice"
   Identifier "mouse0"
   Driver     "evdev"
   Option     "Device"    "/dev/input/by-id/zaphodkvm_mouse1"
   Option     "GrabDevice" "true"
EndSection

Section "Monitor"
   Identifier     "Monitor0"
   Option         "DPMS"
EndSection

Section "Device"
   Identifier     "Videocard0"
   Driver         "nvidia"
   Option         "NoLogo" "true"
   Option         "RandRRotation"
   Option         "ModeValidation" "NoHorizSyncCheck,NoVertRefreshCheck,NoVesaModes"
   Option         "AddARGBGLXVisuals" "True"
   Option         "ProbeAllGpus" "false"
   BusId          "PCI:3:0:0"
EndSection

Section "Screen"
   Identifier     "Screen0"
   Device         "Videocard0"
   Monitor        "Monitor0"
   DefaultDepth    24
EndSection

To start the primary monitor:

startx -- :0 -dpi 96

To start the second monitor:

startx -- :1 -dpi 96 vt8 -nolisten tcp -novtswitch -sharevts -config nvidia610.conf

zaphodkvm

In order to use a single keyboard and mouse to control both X servers, I needed to switch between them somehow. Somebody had done that using synergy; I tried the synergy approach and it didn’t work with Dota 2, so I had to write zaphodkvm. The synergy setup involved running both synergyc and synergys on the same computer and using non-hostname aliases for the screens. It would also probably be possible to use a physical KVM for this, but my experiences with USB KVMs have been less than optimal.

zaphodkvm-0.1.tgz

zaphodkvm - software KVM switch for multiple X servers
Copyright (C) 2014 by geekamole, released under the GNU GPLv3 or later (see COPYING)

libsuinput used and distributed under terms of the GNU GPLv3 or later, and obtained from

http://tjjr.fi/sw/libsuinput/

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses/

----------------------------------------------------------------------------------------

To install: simply

make

then copy the binary "zaphodkvm" to somewhere in your path. It should be run as root. I run it
early in the boot process which seems to resolve some race conditions with X and the VTs.
It runs in the foreground but it should be put in the background and disowned so that it runs
as a daemon while the system is running.

Since this program uses the uinput kernel module, be sure to enable it as built-in or module.
This program also uses the system command "fgconsole," which on Gentoo is in the sys-apps/kbd
package.

zaphodkvm implements a software-based KVM switch (really just the KM part) for a multiseat-X
Linux configuration. With only one user controlling all the physical input devices, it might
be more appropriately called a single-seat-multiple-X-server configuration.

zaphodkvm listens to all connected USB mice and keyboards, and supports hotplugging them via
libudev. It generates four virtual input devices, two "mice" and two "keyboards," and
redirects all input events from the real hardware to one or the other of the virtual
mice/keyboard sets. Double-tapping scroll lock switches the active virtual mice/keyboard set.
If your mice and keyboards aren't USB devices, you will need to change the udev filters in
zaphodkvm.cpp. The filters are there to keep the uinput devices from being detected as
physical input devices.

When uinput devices are created, they show up as a node with the form /dev/input/eventX.
Unlike a physical HID device, udev does not automatically create a persistent symlink in
/dev/input/by-id/. I had to add some rules to my /etc/udev/rules.d/00-custom-persistent.rules
file:

ATTRS{name}=="zaphodkvm_kbd0", DEVPATH=="*event*", SYMLINK+="input/by-id/zaphodkvm_kbd0"
ATTRS{name}=="zaphodkvm_kbd1", DEVPATH=="*event*", SYMLINK+="input/by-id/zaphodkvm_kbd1"
ATTRS{name}=="zaphodkvm_mouse0", DEVPATH=="*event*", SYMLINK+="input/by-id/zaphodkvm_mouse0"
ATTRS{name}=="zaphodkvm_mouse1", DEVPATH=="*event*", SYMLINK+="input/by-id/zaphodkvm_mouse1"

Then either reboot or do a "udevadm control --reload-rules" to activate the new rules. Once
the persistent symlinks are present, configure X servers to use the symlinks as their only
input devices.

I use simple, standard mice with the normal left/right buttons and clickwheel. I also use
standard keyboards. Fancy mice or keyboards might require you to add additional calls to
suinput_enable_event() in zaphodkvm.cpp, to turn on the additional buttons. I also discard
all the raw scancodes from the physical /dev/input/eventX devices, but those might be
useful to your application.

Since the virtual terminals 1-6 tend to latch onto the uinput device nodes when they are
created, I initially had issues with duplicate keystrokes when the X servers weren't running
or when I switched VTs with Ctrl-Alt-FX. Now, zaphodkvm checks to see if VT7 is the active
virtual terminal, and only passes on input events when it is. The X server on VT8 uses
-sharevts and DontVTSwitch so it doesn't affect fgconsole/VT detection.

I set this up for a two-X-server system, but it could be easily extended to an arbitrary
number of X servers.

So far it’s been pretty nice to adjust my system volume, browse the web, and do email while listening to and watching Dota 2 party chat. It’s also nice to have the Dota 2 wiki open while playing unfamiliar heroes. Finally–yes, this stuff really works here despite it being posted on April 1 :)

Posted in hacks | 2 Comments

rt2800usb fix for Ralink/MediaTek 3070 [Gentoo / Linux]

I just received a $4.39 USB WiFi adapter from a Hong Kong eBay seller (auction titled “Mini 150M USB WiFi Wireless N LAN Network Adapter 802.11n/g/b”) that has a Ralink 3070 chipset. I tried the rt2800usb driver in gentoo-sources-3.7.9 and got “Invalid RF chipset 0×3070 detected” in dmesg when I plugged the adapter in. The 2012-11-29 drivers from the MediaTek website compiled and loaded OK but they wouldn’t work with wpa_supplicant, and they spammed dmesg. But, luckily I was able to get rt2800usb working by making a few changes to the kernel sources. I’m not getting stellar transfer rates (max ~20 Mbps even though wavemon claims 150 Mbps), but it’s fine for my uses.

lsusb shows this for the adapter:

148f:3070 Ralink Technology, Corp. RT2870/RT3070 Wireless Adapter

I basically just made the driver treat the 0×3070 RF module as a 0×5370:

--- /usr/src/linux/drivers/net/wireless/rt2x00/rt2800.h
+++ rt2800.h
@@ -68,6 +68,7 @@
 #define RF3320            0x000b
 #define RF3322            0x000c
 #define RF3053            0x000d
+#define RF3070            0x3070
 #define RF3290            0x3290
 #define RF5360            0x5360
 #define RF5370            0x5370
--- /usr/src/linux/drivers/net/wireless/rt2x00/rt2800lib.c
+++ rt2800lib.c
@@ -2221,6 +2221,7 @@
      break;
   case RF5360:
   case RF5370:
+   case RF3070:
   case RF5372:
   case RF5390:
   case RF5392:
@@ -2234,6 +2235,7 @@
       rt2x00_rf(rt2x00dev, RF3322) ||
       rt2x00_rf(rt2x00dev, RF5360) ||
       rt2x00_rf(rt2x00dev, RF5370) ||
+      rt2x00_rf(rt2x00dev, RF3070) ||
       rt2x00_rf(rt2x00dev, RF5372) ||
       rt2x00_rf(rt2x00dev, RF5390) ||
       rt2x00_rf(rt2x00dev, RF5392)) {
@@ -2746,6 +2748,7 @@
   case RF3290:
   case RF5360:
   case RF5370:
+   case RF3070:
   case RF5372:
   case RF5390:
   case RF5392:
@@ -4775,6 +4778,7 @@
   case RF3322:
   case RF5360:
   case RF5370:
+   case RF3070:
   case RF5372:
   case RF5390:
   case RF5392:
@@ -5094,6 +5098,7 @@
         rt2x00_rf(rt2x00dev, RF3322) ||
         rt2x00_rf(rt2x00dev, RF5360) ||
         rt2x00_rf(rt2x00dev, RF5370) ||
+        rt2x00_rf(rt2x00dev, RF3070) ||
         rt2x00_rf(rt2x00dev, RF5372) ||
         rt2x00_rf(rt2x00dev, RF5390) ||
         rt2x00_rf(rt2x00dev, RF5392)) {
@@ -5182,6 +5187,7 @@
   case RF3290:
   case RF5360:
   case RF5370:
+  case RF3070:
   case RF5372:
   case RF5390:
   case RF5392:

I also had to download firmware for the dongle:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
cd linux-firmware
cp -a rt*.bin /lib/firmware

EDIT 9/11/2013:
I’m not actually using the dongle anymore with Linux; even though this fix allows it to work in some cases, it will still intermittently drop out or refuse to associate with my AP. Since the patch has worked for so many people, I might just have a flaky unit. In any case, I’m going to swap out my mini-PCIe card instead.

Posted in hacks | 18 Comments

Generate an extremely long brown/pink/white noise MP3 in Linux

Using https://bitbucket.org/rsvp/gists/src/fc42e2068cf0e2109bf58c94f59bfb1976405755/noise.sh?at=default, simplified. Added fade-in and fade-out. Compile sox and lame with sndfile support to get the w64 format.

#!/bin/bash

noise='brown'
# ^your choice: 'white', 'pink', 'brown', 'tpdf'
# where tpdf stands for Triangular Probability Density Function (cf. dither).
# N.B. - white and pink noise have higher frequencies than Brown.

#does not determine the size of the output when "fade" is used, but must be at least
#as long as the fade cutoff.
len='10000:00'

progress='--show-progress'

# FYI Channels: 2 @ 32-bit, Samplerate: 48000Hz.

#w64 format eliminates the 2GB sox barrier with conventional wav files
mkfifo out.w64
rm -fr noise.mp3

#Makes for a ~7hr, 1GB MP3 with these lame settings (320 kbps CBR)
real_len='414:00'
#This volume was selected to just barely avoid clipping (view the MP3 in Audacity)
volume='-0.4dB'

sox -c 2 --null out.w64 synth $len ${noise}noise vol $volume fade t 3 $real_len &

lame --preset insane out.w64 out.mp3

#for some reason, lame puts a little loud blip at the beginning of the file,
#at position 0.05s or so. So remove it here
ffmpeg -ss 0.1 -i out.mp3 -acodec copy noise.mp3

rm out.w64
rm out.mp3
Posted in hacks | Leave a comment