Fix: Implement custom procfs watcher for connect / disconnect events (#7)
* Fix: Implement custom procfs watcher for connect / disconnect events * fix encoding on build files * Update travis CI * Use last supported framework on CI Co-authored-by: Christopher Lees <leezer3@gmail.com>
This commit is contained in:
parent
bf2d7049a1
commit
af6c86ad19
8 changed files with 1611 additions and 1508 deletions
1526
.gitignore
vendored
1526
.gitignore
vendored
File diff suppressed because it is too large
Load diff
|
@ -2,14 +2,15 @@ language: csharp
|
|||
|
||||
sudo: false # use the new container-based Travis infrastructure
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
before_install:
|
||||
- chmod +x build.sh
|
||||
|
||||
before_script:
|
||||
# Start a virtual framebuffer as described: https://docs.travis-ci.com/user/gui-and-headless-browsers/
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 3 # give xvfb some time to start
|
||||
|
||||
script:
|
||||
- travis_wait ./build.sh NuGet
|
||||
|
|
22
appveyor.yml
22
appveyor.yml
|
@ -1,11 +1,11 @@
|
|||
i m a g e : V i s u a l S t u d i o 2 0 1 7
|
||||
i n i t :
|
||||
- g i t c o n f i g - - g l o b a l c o r e . a u t o c r l f i n p u t
|
||||
b u i l d _ s c r i p t :
|
||||
- c m d : b u i l d . c m d N u G e t
|
||||
- c m d : . p a k e t \ p a k e t . e x e i n s t a l l
|
||||
- c m d : g i t d i f f - - e x i t - c o d e
|
||||
t e s t : o f f
|
||||
v e r s i o n : 0 . 0 . 1 . { b u i l d }
|
||||
a r t i f a c t s :
|
||||
- p a t h : ' b i n \ * . n u p k g '
|
||||
image: Visual Studio 2017
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
build_script:
|
||||
- cmd: build.cmd NuGet
|
||||
- cmd: .paket\paket.exe install
|
||||
- cmd: git diff --exit-code
|
||||
test: off
|
||||
version: 0.0.1.{build}
|
||||
artifacts:
|
||||
- path: 'bin\*.nupkg'
|
||||
|
|
56
build.cmd
56
build.cmd
|
@ -1,28 +1,28 @@
|
|||
@ e c h o o f f
|
||||
c l s
|
||||
|
||||
. p a k e t \ p a k e t . b o o t s t r a p p e r . e x e
|
||||
i f e r r o r l e v e l 1 (
|
||||
e x i t / b % e r r o r l e v e l %
|
||||
)
|
||||
|
||||
. p a k e t \ p a k e t . e x e r e s t o r e
|
||||
i f e r r o r l e v e l 1 (
|
||||
e x i t / b % e r r o r l e v e l %
|
||||
)
|
||||
|
||||
I F N O T E X I S T b u i l d . f s x (
|
||||
. p a k e t \ p a k e t . e x e u p d a t e
|
||||
p a c k a g e s \ F A K E \ t o o l s \ F A K E . e x e i n i t . f s x
|
||||
)
|
||||
|
||||
S E T B u i l d T a r g e t =
|
||||
i f " % B u i l d R u n n e r % " = = " M y G e t " (
|
||||
S E T B u i l d T a r g e t = N u G e t
|
||||
|
||||
: : R e p l a c e t h e e x i s t i n g r e l e a s e n o t e s f i l e w i t h o n e f o r t h i s b u i l d o n l y
|
||||
e c h o # # # % P a c k a g e V e r s i o n % > R E L E A S E _ N O T E S . m d
|
||||
e c h o * g i t b u i l d > > R E L E A S E _ N O T E S . m d
|
||||
)
|
||||
|
||||
p a c k a g e s \ F A K E \ t o o l s \ F A K E . e x e b u i l d . f s x % * % B u i l d T a r g e t %
|
||||
@echo off
|
||||
cls
|
||||
|
||||
.paket\paket.bootstrapper.exe
|
||||
if errorlevel 1 (
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
.paket\paket.exe restore
|
||||
if errorlevel 1 (
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
IF NOT EXIST build.fsx (
|
||||
.paket\paket.exe update
|
||||
packages\FAKE\tools\FAKE.exe init.fsx
|
||||
)
|
||||
|
||||
SET BuildTarget=
|
||||
if "%BuildRunner%" == "MyGet" (
|
||||
SET BuildTarget=NuGet
|
||||
|
||||
:: Replace the existing release notes file with one for this build only
|
||||
echo ### %PackageVersion% > RELEASE_NOTES.md
|
||||
echo * git build >> RELEASE_NOTES.md
|
||||
)
|
||||
|
||||
packages\FAKE\tools\FAKE.exe build.fsx %* %BuildTarget%
|
||||
|
|
72
build.sh
72
build.sh
|
@ -1,36 +1,36 @@
|
|||
# ! / u s r / b i n / e n v b a s h
|
||||
|
||||
s e t - e u
|
||||
s e t - o p i p e f a i l
|
||||
|
||||
c d ` d i r n a m e $ 0 `
|
||||
|
||||
F S I A R G S = " "
|
||||
O S = $ { O S : - " u n k n o w n " }
|
||||
i f [ [ " $ O S " ! = " W i n d o w s _ N T " ] ]
|
||||
t h e n
|
||||
F S I A R G S = " - - f s i a r g s - d : M O N O "
|
||||
f i
|
||||
|
||||
f u n c t i o n r u n ( ) {
|
||||
i f [ [ " $ O S " ! = " W i n d o w s _ N T " ] ]
|
||||
t h e n
|
||||
m o n o " $ @ "
|
||||
e l s e
|
||||
" $ @ "
|
||||
f i
|
||||
}
|
||||
|
||||
r u n . p a k e t / p a k e t . b o o t s t r a p p e r . e x e
|
||||
|
||||
i f [ [ " $ O S " ! = " W i n d o w s _ N T " ] ] & &
|
||||
[ ! - e ~ / . c o n f i g / . m o n o / c e r t s ]
|
||||
t h e n
|
||||
m o z r o o t s - - i m p o r t - - s y n c - - q u i e t
|
||||
f i
|
||||
|
||||
r u n . p a k e t / p a k e t . e x e r e s t o r e
|
||||
|
||||
[ ! - e b u i l d . f s x ] & & r u n . p a k e t / p a k e t . e x e u p d a t e
|
||||
[ ! - e b u i l d . f s x ] & & r u n p a c k a g e s / F A K E / t o o l s / F A K E . e x e i n i t . f s x
|
||||
r u n p a c k a g e s / F A K E / t o o l s / F A K E . e x e " $ @ " $ F S I A R G S b u i l d . f s x
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
FSIARGS=""
|
||||
OS=${OS:-"unknown"}
|
||||
if [[ "$OS" != "Windows_NT" ]]
|
||||
then
|
||||
FSIARGS="--fsiargs -d:MONO"
|
||||
fi
|
||||
|
||||
function run() {
|
||||
if [[ "$OS" != "Windows_NT" ]]
|
||||
then
|
||||
mono "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
run .paket/paket.bootstrapper.exe
|
||||
|
||||
if [[ "$OS" != "Windows_NT" ]] &&
|
||||
[ ! -e ~/.config/.mono/certs ]
|
||||
then
|
||||
mozroots --import --sync --quiet
|
||||
fi
|
||||
|
||||
run .paket/paket.exe restore
|
||||
|
||||
[ ! -e build.fsx ] && run .paket/paket.exe update
|
||||
[ ! -e build.fsx ] && run packages/FAKE/tools/FAKE.exe init.fsx
|
||||
run packages/FAKE/tools/FAKE.exe "$@" $FSIARGS build.fsx
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>OpenTK</AssemblyName>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RootNamespace>OpenTK</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);WIN32;CARBON;X11;SDL2;OPENGL;OPENGLES;MINIMAL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -27,6 +27,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace OpenTK.Platform.Linux
|
||||
|
@ -71,32 +73,164 @@ namespace OpenTK.Platform.Linux
|
|||
|
||||
private readonly object sync = new object();
|
||||
|
||||
private readonly FileSystemWatcher watcher = new FileSystemWatcher();
|
||||
|
||||
private readonly DeviceCollection<LinuxJoystickDetails> Sticks =
|
||||
new DeviceCollection<LinuxJoystickDetails>();
|
||||
|
||||
private bool disposed;
|
||||
private volatile bool disposed;
|
||||
|
||||
private const string InputDevicePath = "/proc/bus/input/devices";
|
||||
/// <summary>The evdev path on recent Xorg versions</summary>
|
||||
private static readonly string JoystickPath = "/dev/input";
|
||||
/// <summary>The legacy evdev fallback Xorg path</summary>
|
||||
private static readonly string JoystickPathLegacy = "/dev";
|
||||
|
||||
/// <summary>Thread used to monitor ProcFS for changes in order to support hotplugging of joysticks</summary>
|
||||
private readonly Thread ProcFSMonitorThread;
|
||||
|
||||
/// <summary>Gets the actual evdev path used by the XOrg server</summary>
|
||||
/// <remarks>See <see href="https://www.kernel.org/doc/Documentation/input/input.txt">the Linux kernel documentation</see> for further details.</remarks>
|
||||
private string EvdevPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Directory.Exists(JoystickPath) ? JoystickPath :
|
||||
Directory.Exists(JoystickPathLegacy) ? JoystickPathLegacy :
|
||||
String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public LinuxJoystick()
|
||||
{
|
||||
string path =
|
||||
Directory.Exists(JoystickPath) ? JoystickPath :
|
||||
Directory.Exists(JoystickPathLegacy) ? JoystickPathLegacy :
|
||||
String.Empty;
|
||||
|
||||
if (!String.IsNullOrEmpty(path))
|
||||
ProcFSMonitorThread = new Thread(ProcessEvents);
|
||||
ProcFSMonitorThread.IsBackground = true;
|
||||
ProcFSMonitorThread.Start();
|
||||
//Populate the initial list of joysticks
|
||||
if (!String.IsNullOrEmpty(EvdevPath))
|
||||
{
|
||||
watcher.Path = path;
|
||||
|
||||
watcher.Created += JoystickAdded;
|
||||
watcher.Deleted += JoystickRemoved;
|
||||
watcher.EnableRaisingEvents = true;
|
||||
|
||||
OpenJoysticks(path);
|
||||
OpenJoysticks(EvdevPath);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MD5 Hasher = MD5.Create();
|
||||
|
||||
private string procHash;
|
||||
|
||||
private void ProcessEvents()
|
||||
{
|
||||
if (!File.Exists(InputDevicePath))
|
||||
{
|
||||
//BSD may not have the Linux ProcFS, no use running a pointless loop
|
||||
return;
|
||||
}
|
||||
|
||||
while (!disposed)
|
||||
{
|
||||
/*
|
||||
* Rather hacky filesystemwatcher equivilant, as it doesn't work on ProcFS
|
||||
* As the thing is psuedo-files, we can't use the last modified date either
|
||||
* We have to load the thing into a stream and hash. At least it's only a few bytes.....
|
||||
*
|
||||
* Note: We could possibly override the Mono filesystem watcher backend, but this is a global variable only
|
||||
* and so may break user code
|
||||
*/
|
||||
Thread.Sleep(750); //750ms is the Mono default for the file hashing version of FileSystemWatcher backend
|
||||
List<int> connectedDevices;
|
||||
|
||||
using (FileStream stream = new FileStream(InputDevicePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
string newHash = BitConverter.ToString(Hasher.ComputeHash(stream));
|
||||
if (newHash == procHash)
|
||||
{
|
||||
//Hash of /proc/bus/input/devices has not changed, hence no new devices added / removed
|
||||
continue;
|
||||
}
|
||||
stream.Seek( 0, 0);
|
||||
connectedDevices = GetConnectedDevices(stream);
|
||||
procHash = newHash;
|
||||
OpenJoysticks(EvdevPath);
|
||||
}
|
||||
|
||||
List<int> xboxPadCandidates = new List<int>();
|
||||
|
||||
foreach (LinuxJoystickDetails stick in Sticks)
|
||||
{
|
||||
if (connectedDevices.Contains(stick.PathIndex))
|
||||
{
|
||||
if (stick.Caps.AxisCount == 6 && stick.Caps.ButtonCount == 11 && stick.Caps.HatCount == 2)
|
||||
{
|
||||
xboxPadCandidates.Add(stick.PathIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
stick.Caps.SetIsConnected(true);
|
||||
stick.State.SetIsConnected(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stick.State.SetIsConnected(false);
|
||||
stick.Caps.SetIsConnected(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (xboxPadCandidates.Count < 4)
|
||||
{
|
||||
/*
|
||||
* The XBox wireless reciever generates 4 sticks which may not have anything connected to them
|
||||
* If there are less than 4, this can't be this (or it's been fixed evdev side in the meantime)
|
||||
* so let's set them as connected
|
||||
*/
|
||||
foreach (int idx in xboxPadCandidates)
|
||||
{
|
||||
LinuxJoystickDetails stick = Sticks.FromHardwareId(idx);
|
||||
if (stick != null)
|
||||
{
|
||||
stick.Caps.SetIsConnected(true);
|
||||
stick.State.SetIsConnected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the list of connected device IDs</summary>
|
||||
/// <param name="stream">The filestream to /proc/bus/input/devices</param>
|
||||
/// <returns>The list of connected device IDs, or an empty list if no devices are connected</returns>
|
||||
private List<int> GetConnectedDevices(FileStream stream)
|
||||
{
|
||||
List<int> connectedDevices = new List<int>();
|
||||
using (StreamReader reader = new StreamReader(stream))
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
string currentLine = reader.ReadLine();
|
||||
if (string.IsNullOrEmpty(currentLine))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (char.ToLower(currentLine[0]))
|
||||
{
|
||||
case 'h':
|
||||
string[] handlers = currentLine.Substring(currentLine.IndexOf('=') + 1).Trim().Split(' ');
|
||||
|
||||
foreach (string handler in handlers)
|
||||
{
|
||||
if (handler.StartsWith("event", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
//As device is in /proc/bus/input/devices it is connected, so pull out the evdev ID
|
||||
int evdevID = int.Parse(handler.Substring(5));
|
||||
connectedDevices.Add(evdevID);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return connectedDevices;
|
||||
}
|
||||
|
||||
|
||||
private void OpenJoysticks(string path)
|
||||
{
|
||||
lock (sync)
|
||||
|
@ -126,31 +260,6 @@ namespace OpenTK.Platform.Linux
|
|||
return -1;
|
||||
}
|
||||
|
||||
private void JoystickAdded(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
OpenJoystick(e.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void JoystickRemoved(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
string file = Path.GetFileName(e.FullPath);
|
||||
int number = GetJoystickNumber(file);
|
||||
if (number != -1)
|
||||
{
|
||||
var stick = Sticks.FromHardwareId(number);
|
||||
if (stick != null)
|
||||
{
|
||||
CloseJoystick(stick);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Guid CreateGuid(EvdevInputId id, string name)
|
||||
{
|
||||
// Note: the first 8bytes of the Guid are byteswapped
|
||||
|
@ -352,6 +461,7 @@ namespace OpenTK.Platform.Linux
|
|||
return new JoystickHatState(HatPositions[x, y]);
|
||||
}
|
||||
|
||||
|
||||
private void PollJoystick(LinuxJoystickDetails js)
|
||||
{
|
||||
unsafe
|
||||
|
@ -367,10 +477,7 @@ namespace OpenTK.Platform.Linux
|
|||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Only mark the joystick as connected when we actually start receiving events.
|
||||
// Otherwise, the Xbox wireless receiver will register 4 joysticks even if no
|
||||
// actual joystick is connected to the receiver.
|
||||
//As we've received an event, this must be connected!
|
||||
js.Caps.SetIsConnected(true);
|
||||
js.State.SetIsConnected(true);
|
||||
|
||||
|
@ -446,9 +553,6 @@ namespace OpenTK.Platform.Linux
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly string JoystickPath = "/dev/input";
|
||||
private static readonly string JoystickPathLegacy = "/dev";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
@ -462,8 +566,6 @@ namespace OpenTK.Platform.Linux
|
|||
if (manual)
|
||||
{
|
||||
}
|
||||
|
||||
watcher.Dispose();
|
||||
foreach (LinuxJoystickDetails js in Sticks)
|
||||
{
|
||||
CloseJoystick(js);
|
||||
|
|
Loading…
Reference in a new issue