Lightweight cross-platform joystick input library
リビジョン | 2aff5840f06ea2008a09f02a351acc4ebc976eb1 (tree) |
---|---|
日時 | 2021-06-12 07:15:08 |
作者 | AlaskanEmily <emily@alas...> |
コミッター | AlaskanEmily |
Add Linux evdev backend.
@@ -5,6 +5,13 @@ import os | ||
5 | 5 | import sys |
6 | 6 | import glob |
7 | 7 | |
8 | +def GetLinkableLibrary(lib): | |
9 | + if os.name == 'nt': | |
10 | + if len(lib) == 1: | |
11 | + return lib[0] | |
12 | + else: | |
13 | + return lib[1] | |
14 | + | |
8 | 15 | AddOption('--target', dest = 'target', nargs=1, action='store', help = |
9 | 16 | "Override target architecture.") |
10 | 17 |
@@ -26,6 +33,9 @@ AddOption('--enable-dinput', dest = 'enable-dinput', nargs=1, action='store', he | ||
26 | 33 | AddOption('--enable-bsd', dest = 'enable-bsd', nargs=1, action='store', help = |
27 | 34 | "Use the BSD backend libusbhid, enabled by default on BSD systems") |
28 | 35 | |
36 | +AddOption('--enable-evdev', dest = 'enable-evdev', nargs=1, action='store', help = | |
37 | +"Use the evdev backend, enabled by default on Linux and FreeBSD systems") | |
38 | + | |
29 | 39 | AddOption('--build-shared', dest = 'build-shared', nargs=1, action='store', help = |
30 | 40 | "Build shared library, disabled by default.") |
31 | 41 |
@@ -57,6 +67,7 @@ def DefaultDriverOption(Driver, Default): | ||
57 | 67 | DefaultDriverOption('xinput', os.name == 'nt') |
58 | 68 | DefaultDriverOption('dinput', os.name == 'nt') |
59 | 69 | DefaultDriverOption('bsd', 'bsd' in sys.platform.lower()) |
70 | +DefaultDriverOption('evdev', 'freebsd' in sys.platform.lower() or 'linux' in sys.platform.lower()) | |
60 | 71 | |
61 | 72 | if os.name == 'nt': |
62 | 73 | rejoy_build_shared = True |
@@ -73,66 +84,77 @@ if GetOption('build-shared-modules') == 'y': | ||
73 | 84 | else: |
74 | 85 | rejoy_build_shared_modules = False |
75 | 86 | |
87 | +if rejoy_build_shared: | |
88 | + if os.name == 'nt': | |
89 | + environment.Append(CPPDEFINES=["REJOY_INTERNAL_DLL=1"]) | |
90 | + environment.Append(CPPDEFINES=["REJOY_SHARED=1"]) | |
91 | +else: | |
92 | + environment.Append(CPPDEFINES=["REJOY_SHARED=0"]) | |
93 | + | |
76 | 94 | rejoy_util = SConscript(dirs=["util"], exports=["CC", "environment"]) |
77 | 95 | |
78 | 96 | environment.Append(CPPPATH=os.path.join(os.getcwd(), "util")) |
79 | 97 | |
80 | 98 | # Default source |
81 | 99 | rejoy_source = ["rejoy.cpp", "rejoy_c.cpp"] |
82 | -rejoy_libs = [rejoy_util] | |
100 | +rejoy_libs = [] | |
83 | 101 | |
84 | 102 | # Dict of libraries indexed by driver backend. |
85 | 103 | rejoy_lib_dict = { |
86 | 104 | "dinput":["dinput8", "dxguid.lib", "user32"], |
87 | 105 | "xinput":["xinput"], |
88 | - "bsd":["usbhid"] | |
106 | + "bsd":["usbhid"], | |
107 | + "evdev":["dl", "pthread"] | |
89 | 108 | } |
90 | 109 | |
91 | 110 | # Drivers which require the Unix source to be included. |
92 | -rejoy_unix_drivers = [ | |
111 | +rejoy_unix_drivers = ( | |
93 | 112 | "bsd", |
94 | 113 | "joy", |
95 | - "linux" | |
96 | -] | |
114 | + "evdev" | |
115 | +) | |
97 | 116 | |
98 | 117 | # Add up the drivers, also checking for requirement of unix. |
99 | 118 | use_unix = False |
100 | 119 | for driver in rejoy_drivers: |
101 | - if driver in rejoy_lib_dict: | |
102 | - rejoy_libs += rejoy_lib_dict[driver] | |
103 | 120 | if driver in rejoy_unix_drivers: |
104 | 121 | use_unix = True |
122 | + break | |
123 | + | |
124 | +if use_unix: | |
125 | + rejoy_unix = SConscript(dirs=["unix"], exports=["environment"]) | |
126 | + | |
127 | +depends_on_unix = [] | |
128 | +for driver in rejoy_drivers: | |
129 | + if driver in rejoy_lib_dict: | |
130 | + rejoy_libs += rejoy_lib_dict[driver] | |
105 | 131 | # Check if there is a SConscript file to read or not. |
106 | 132 | if os.path.isfile(os.path.join(driver, "SConscript")): |
107 | - rejoy_libs.append(SConscript(dirs=[driver], exports=["CC", "environment", "rejoy_build_shared"])) | |
133 | + lib = SConscript(dirs=[driver], exports=["CC", "environment", "rejoy_build_shared"]) | |
134 | + rejoy_libs.append(lib) | |
135 | + if driver in rejoy_unix_drivers: | |
136 | + environment.Depends(lib, rejoy_unix) | |
137 | + environment.Depends(lib, rejoy_util) | |
108 | 138 | else: |
109 | 139 | rejoy_source += glob.glob(os.path.join(driver, "*.cpp")) |
110 | 140 | rejoy_source += glob.glob(os.path.join(driver, "*.c")) |
111 | 141 | environment.Append(CPPDEFINES=["REJOY_DRIVER_" + driver.upper() + "=1"]) |
112 | 142 | |
143 | +rejoy_dep_libs = rejoy_libs + [rejoy_util] | |
113 | 144 | if use_unix: |
114 | - rejoy_source += glob.glob(os.path.join("unix", "*.cpp")) | |
115 | - rejoy_source += glob.glob(os.path.join("unix", "*.c")) | |
145 | + rejoy_dep_libs.append(rejoy_unix) | |
116 | 146 | |
117 | 147 | if rejoy_build_shared: |
118 | - if os.name == 'nt': | |
119 | - environment.Append(CPPDEFINES=["REJOY_INTERNAL_DLL=1"]) | |
120 | - environment.Append(CPPDEFINES=["REJOY_SHARED=1"]) | |
121 | - rejoy = environment.SharedLibrary('rejoy', rejoy_source, LIBS=rejoy_libs) | |
148 | + rejoy = environment.SharedLibrary('rejoy', rejoy_source, LIBS=rejoy_dep_libs) | |
122 | 149 | else: |
123 | - environment.Append(CPPDEFINES=["REJOY_SHARED=0"]) | |
124 | - rejoy = environment.StaticLibrary('rejoy', rejoy_source, LIBS=rejoy_libs) | |
125 | - | |
126 | -if os.name == 'nt': | |
127 | - if len(rejoy) == 1: | |
128 | - rejoy = rejoy[0] | |
129 | - else: | |
130 | - rejoy = rejoy[1] | |
150 | + rejoy = environment.StaticLibrary('rejoy', rejoy_source, LIBS=rejoy_dep_libs) | |
131 | 151 | |
132 | -demo_libs = [rejoy] | |
133 | 152 | if not rejoy_build_shared: |
134 | - demo_libs += rejoy_libs | |
135 | - | |
153 | + demo_libs = [rejoy] + rejoy_libs + [rejoy_util] | |
154 | + if use_unix: | |
155 | + demo_libs.append(rejoy_unix) | |
156 | +else: | |
157 | + demo_libs = [rejoy] | |
136 | 158 | |
137 | 159 | demo = environment.Program(['rejoy_demo.cpp'], LIBS=demo_libs) |
138 | 160 |
@@ -0,0 +1,11 @@ | ||
1 | +[package] | |
2 | +name = "rejoy_evdev" | |
3 | +version = "0.0.1" | |
4 | +authors = ["AlaskanEmily"] | |
5 | + | |
6 | +[dependencies] | |
7 | +ioctl-sys = "0.7.*" | |
8 | + | |
9 | +[lib] | |
10 | +crate-type = ["staticlib", "cdylib"] | |
11 | + |
@@ -0,0 +1,65 @@ | ||
1 | +# Any copyright is dedicated to the Public Domain. | |
2 | +# http://creativecommons.org/publicdomain/zero/1.0/ | |
3 | +import os | |
4 | +import glob | |
5 | + | |
6 | +Import("environment rejoy_build_shared") | |
7 | + | |
8 | +# We use cxxflags.txt as a pseudo-source. | |
9 | +# This is used by both Cargo and SCons to force a rebuild if CXXFLAGS change. | |
10 | +# It might be possible to avoid this if we could build the shim within SCons, | |
11 | +# however since it requires some files generated by the Rust build script we | |
12 | +# would need to invoke the Rust build process multiple times. | |
13 | +# | |
14 | +# This also allows us to (potentially) parse the args here in Python and then | |
15 | +# give them as line-separated strings to Rust, then std::process::Command::args | |
16 | +# will properly understand them. | |
17 | + | |
18 | +env = os.environ | |
19 | + | |
20 | +# Set the CXX/CC value to be consumed by Cargo | |
21 | +env["CXX"] = str(environment["CXX"]) | |
22 | +env["CC"] = str(environment["CC"]) | |
23 | + | |
24 | +# Pass the location of rejoy_util in an environment variable. | |
25 | + | |
26 | +DELIMIT = '\n' | |
27 | +CXXFLAGSPATH = "cxxflags.txt" | |
28 | + | |
29 | +# Write the CXX flags, if they have changed. | |
30 | +cxxflags_file = None | |
31 | +try: | |
32 | + cxxflags_file = open(CXXFLAGSPATH, "r") | |
33 | + cxxflags = cxxflags.read() | |
34 | +except: | |
35 | + cxxflags = "" | |
36 | +finally: | |
37 | + if cxxflags_file: | |
38 | + cxxflags_file.close() | |
39 | + | |
40 | +# Construct the new flags | |
41 | +new_cxxflags = str(environment["CXXFLAGS"]).split() | |
42 | +if rejoy_build_shared and "-fPIC" not in new_cxxflags and "-fpic" not in new_cxxflags: | |
43 | + new_cxxflags.append("-fPIC") | |
44 | +old_cxxflags = cxxflags.split(DELIMIT) | |
45 | + | |
46 | +if new_cxxflags != old_cxxflags: | |
47 | + cxxflags_file = open(CXXFLAGSPATH, "w") | |
48 | + cxxflags_file.write(DELIMIT.join(new_cxxflags)) | |
49 | + cxxflags_file.close() | |
50 | + | |
51 | +rust_lib="librejoy_evdev" + environment['LIBSUFFIX'] | |
52 | +src = [] | |
53 | + | |
54 | +for s in ("rejoy_evdev.hpp", "rejoy_evdev.cpp", "build.rs", CXXFLAGSPATH): | |
55 | + src.append(os.path.join(os.getcwd(), s)) | |
56 | +for s in glob.glob("Cargo.*") + glob.glob("src/*.rs"): | |
57 | + src.append(os.path.join(os.getcwd(), s)) | |
58 | + | |
59 | +rejoy_evdev_rs = environment.Command( | |
60 | + target=os.path.join(os.getcwd(), "target", "debug", rust_lib), | |
61 | + source=src, | |
62 | + action="cargo build", | |
63 | + chdir=os.getcwd()) | |
64 | + | |
65 | +Return("rejoy_evdev_rs") |
@@ -0,0 +1,122 @@ | ||
1 | +// Copyright (c) 2021 Alaskan Emily, Transnat Games | |
2 | +// | |
3 | +// This Source Code Form is subject to the terms of the Mozilla Public | |
4 | +// License, v. 2.0. If a copy of the MPL was not distributed with this | |
5 | +// file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
6 | + | |
7 | +#include "rejoy_evdev.hpp" | |
8 | +#include "../rejoy_private.hpp" | |
9 | +#include "../unix/rejoy_unix_core.h" | |
10 | +#include <unistd.h> | |
11 | +#include <fcntl.h> | |
12 | + | |
13 | +/////////////////////////////////////////////////////////////////////////////// | |
14 | +// Manual bindings. These are irregular, one-off functions. | |
15 | +extern "C" { | |
16 | + | |
17 | +/////////////////////////////////////////////////////////////////////////////// | |
18 | +// Gamepads functions | |
19 | +struct Rejoy_Evdev_Gamepad *Rejoy_Evdev_OpenGamepad(int fd); | |
20 | +struct Rejoy_Evdev_Gamepad *Rejoy_Evdev_CopyGamepad( | |
21 | + const struct Rejoy_Evdev_Gamepad *data); | |
22 | +void Rejoy_Evdev_FinalizeGamepad(struct Rejoy_Evdev_Gamepad *data); | |
23 | +void Rejoy_Evdev_UpdateGamepad(struct Rejoy_Evdev_Gamepad *data, int fd); | |
24 | + | |
25 | +/////////////////////////////////////////////////////////////////////////////// | |
26 | +// Rust Vec bindings. | |
27 | +void *Rejoy_Evdev_VecAppend(void *vec, void *val); | |
28 | +unsigned Rejoy_Evdev_VecGetLen(const void *vec); | |
29 | +Rejoy::EvdevGamepad *Rejoy_Evdev_VecGetValue(void *vec, unsigned i); | |
30 | +void Rejoy_Evdev_FinalizeVec(void *vec); | |
31 | + | |
32 | +/////////////////////////////////////////////////////////////////////////////// | |
33 | + | |
34 | +} // extern "C | |
35 | + | |
36 | +/////////////////////////////////////////////////////////////////////////////// | |
37 | + | |
38 | +namespace Rejoy { | |
39 | + | |
40 | +/////////////////////////////////////////////////////////////////////////////// | |
41 | + | |
42 | +static inline void DeleteGamepads(void *vec) { | |
43 | + const unsigned len = Rejoy_Evdev_VecGetLen(vec); | |
44 | + for(unsigned i = 0; i < len; i++){ | |
45 | + delete Rejoy_Evdev_VecGetValue(vec, i); | |
46 | + } | |
47 | + Rejoy_Evdev_FinalizeVec(vec); | |
48 | +} | |
49 | + | |
50 | +/////////////////////////////////////////////////////////////////////////////// | |
51 | + | |
52 | +EvdevGamepad::EvdevGamepad(const EvdevGamepad &other) | |
53 | + : UnixGamepad(other) | |
54 | + , m_data(Rejoy_Evdev_CopyGamepad(other.m_data)) {} | |
55 | + | |
56 | +/////////////////////////////////////////////////////////////////////////////// | |
57 | + | |
58 | +EvdevGamepad::~EvdevGamepad() { | |
59 | + Rejoy_Evdev_FinalizeGamepad(m_data); | |
60 | +} | |
61 | + | |
62 | +/////////////////////////////////////////////////////////////////////////////// | |
63 | + | |
64 | +void EvdevGamepad::update() { | |
65 | + Rejoy_Evdev_UpdateGamepad(m_data, fd()); | |
66 | +} | |
67 | + | |
68 | +/////////////////////////////////////////////////////////////////////////////// | |
69 | + | |
70 | +void EvdevDriver::enumerateGamepad(int fd){ | |
71 | + // TODO: This could be handled totally on the Rust side pretty easily. | |
72 | + if(m_num_gamepads) | |
73 | + return; | |
74 | + if(struct Rejoy_Evdev_Gamepad *const data = Rejoy_Evdev_OpenGamepad(fd)){ | |
75 | + EvdevGamepad *const gamepad = new EvdevGamepad(fd, data); | |
76 | + m_gamepad_list = Rejoy_Evdev_VecAppend(m_gamepad_list, gamepad); | |
77 | + m_num_gamepads++; | |
78 | + } | |
79 | + else{ | |
80 | + // close(fd); | |
81 | + } | |
82 | +} | |
83 | + | |
84 | +/////////////////////////////////////////////////////////////////////////////// | |
85 | + | |
86 | +EvdevDriver::~EvdevDriver() { | |
87 | + DeleteGamepads(m_gamepad_list); | |
88 | +} | |
89 | + | |
90 | +/////////////////////////////////////////////////////////////////////////////// | |
91 | + | |
92 | +void EvdevDriver::update() { | |
93 | + DeleteGamepads(m_gamepad_list); | |
94 | + m_gamepad_list = NULL; | |
95 | + m_num_gamepads = 0; | |
96 | + Rejoy_Unix_IterateGlob("/dev/input/event*", | |
97 | + O_RDWR | O_NONBLOCK, | |
98 | + this, | |
99 | + EvdevDriver::EnumateGamepad); | |
100 | +} | |
101 | + | |
102 | +/////////////////////////////////////////////////////////////////////////////// | |
103 | + | |
104 | +Gamepad *EvdevDriver::getGamepad(unsigned i) { | |
105 | + if(i < Rejoy_Evdev_VecGetLen(m_gamepad_list)){ | |
106 | + Gamepad *gamepad = Rejoy_Evdev_VecGetValue(m_gamepad_list, i); | |
107 | + return gamepad; | |
108 | + } | |
109 | + else | |
110 | + return NULL; | |
111 | +} | |
112 | + | |
113 | +/////////////////////////////////////////////////////////////////////////////// | |
114 | + | |
115 | +REJOY_STATIC_INIT(Evdev); | |
116 | + | |
117 | +/////////////////////////////////////////////////////////////////////////////// | |
118 | + | |
119 | +} // namespace Rejoy | |
120 | + | |
121 | +/////////////////////////////////////////////////////////////////////////////// | |
122 | + |
@@ -0,0 +1,81 @@ | ||
1 | +// Copyright (c) 2021 Alaskan Emily, Transnat Games | |
2 | +// | |
3 | +// This Source Code Form is subject to the terms of the Mozilla Public | |
4 | +// License, v. 2.0. If a copy of the MPL was not distributed with this | |
5 | +// file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
6 | + | |
7 | +#ifndef REJOY_EVDEV_HPP | |
8 | +#define REJOY_EVDEV_HPP | |
9 | +#pragma once | |
10 | + | |
11 | +/////////////////////////////////////////////////////////////////////////////// | |
12 | + | |
13 | +#include "../rejoy.hpp" | |
14 | +#include "../unix/rejoy_unix.hpp" | |
15 | +#include "rejoy_evdev_bind.h" | |
16 | + | |
17 | +/////////////////////////////////////////////////////////////////////////////// | |
18 | + | |
19 | +namespace Rejoy { | |
20 | + | |
21 | +/////////////////////////////////////////////////////////////////////////////// | |
22 | +// Tagged as struct so that it can be used in SLIST's | |
23 | +class EvdevGamepad : public Rejoy::UnixGamepad { | |
24 | + struct Rejoy_Evdev_Gamepad *const m_data; | |
25 | +public: | |
26 | + inline EvdevGamepad(int fd, struct Rejoy_Evdev_Gamepad *data) | |
27 | + : UnixGamepad(fd, | |
28 | + Rejoy_Evdev_GetNumAxes(data), | |
29 | + Rejoy_Evdev_GetNumButtons(data), | |
30 | + Rejoy_Evdev_GetNumHats(data)) | |
31 | + , m_data(data) {} | |
32 | + | |
33 | + explicit EvdevGamepad(const EvdevGamepad &other); | |
34 | + | |
35 | + ~EvdevGamepad(); | |
36 | + | |
37 | + virtual void update(); | |
38 | + virtual const char *name() const { | |
39 | + return Rejoy_Evdev_GetName(m_data); | |
40 | + } | |
41 | + virtual short getAxis(unsigned i) const { | |
42 | + return Rejoy_Evdev_GetAxis(m_data, i); | |
43 | + } | |
44 | + virtual bool getButton(unsigned i) const { | |
45 | + return Rejoy_Evdev_GetButton(m_data, i); | |
46 | + } | |
47 | + virtual unsigned getHat(unsigned i) const { | |
48 | + return Rejoy_Evdev_GetHat(m_data, i); | |
49 | + } | |
50 | +}; | |
51 | + | |
52 | +/////////////////////////////////////////////////////////////////////////////// | |
53 | + | |
54 | +class EvdevDriver : public Rejoy::Driver { | |
55 | + // A Rust slice of EvdevGamepad. | |
56 | + void *m_gamepad_list; | |
57 | + | |
58 | + void enumerateGamepad(int fd); | |
59 | + static void EnumateGamepad(void *that, const char *name, int fd){ | |
60 | + (void)name; | |
61 | + static_cast<EvdevDriver*>(that)->enumerateGamepad(fd); | |
62 | + } | |
63 | +public: | |
64 | + EvdevDriver() | |
65 | + : Driver("evdev") | |
66 | + , m_gamepad_list(NULL) {} | |
67 | + | |
68 | + ~EvdevDriver(); | |
69 | + | |
70 | + virtual void update(); | |
71 | + virtual Gamepad *getGamepad(unsigned i); | |
72 | +}; | |
73 | + | |
74 | +/////////////////////////////////////////////////////////////////////////////// | |
75 | + | |
76 | +} // namespace Rejoy | |
77 | + | |
78 | +/////////////////////////////////////////////////////////////////////////////// | |
79 | + | |
80 | +#endif // REJOY_EVDEV_HPP | |
81 | + |
@@ -0,0 +1,325 @@ | ||
1 | +use std::{fmt, iter}; | |
2 | + | |
3 | +/// Bitmap container type with a fixed size and variable bit length elements. | |
4 | +/// TODO: This is clearly not optimal. | |
5 | + | |
6 | +pub trait Bitmap { | |
7 | + fn get_bit(&self, i: usize) -> u8; | |
8 | + fn set_bit(&mut self, i: usize, val: u8) -> bool; | |
9 | + fn as_ptr(&self) -> *const u8; | |
10 | + fn as_mut_ptr(&mut self) -> *mut u8; | |
11 | + fn element_size(&self) -> usize; | |
12 | + fn len(&self) -> usize; | |
13 | +} | |
14 | + | |
15 | +#[inline] | |
16 | +pub fn get_slice<B>(b: &B, i: usize) -> u32 | |
17 | +where | |
18 | + B: Bitmap | |
19 | +{ | |
20 | + let mut n: u32 = 0; | |
21 | + let esize = b.element_size(); | |
22 | + let base = i * esize; | |
23 | + for e in 0 .. esize { | |
24 | + n |= (b.get_bit(base + e) as u32) << e; | |
25 | + } | |
26 | + n | |
27 | +} | |
28 | + | |
29 | +#[inline] | |
30 | +pub fn set_slice<B>(b: &mut B, i: usize, val: u32) | |
31 | +where | |
32 | + B: Bitmap | |
33 | +{ | |
34 | + let esize = b.element_size(); | |
35 | + let base = i * esize; | |
36 | + for e in 0 .. esize { | |
37 | + b.set_bit(base + e, (val >> e) as u8 & 1); | |
38 | + } | |
39 | +} | |
40 | + | |
41 | +#[derive(Clone, Debug, PartialEq)] | |
42 | +pub struct BitmapRef<B>(pub B, pub usize); | |
43 | + | |
44 | +impl<B> BitmapRef<B> { | |
45 | + #[inline] | |
46 | + pub fn new(that: B) -> Self { | |
47 | + BitmapRef(that, 0) | |
48 | + } | |
49 | +} | |
50 | + | |
51 | + | |
52 | +impl<'a, B> BitmapRef<&'a B> | |
53 | +where | |
54 | + B: Bitmap | |
55 | +{ | |
56 | + #[inline] | |
57 | + pub fn get(&self) -> u32 { | |
58 | + get_slice(self.0, self.1) | |
59 | + } | |
60 | + | |
61 | + #[inline] | |
62 | + pub fn end(&self) -> bool { | |
63 | + self.0.len() < self.1 | |
64 | + } | |
65 | + | |
66 | + #[inline] | |
67 | + pub fn step(&mut self, n: usize) { | |
68 | + self.1 = std::cmp::min(self.1 + n, self.0.len()); | |
69 | + } | |
70 | +} | |
71 | + | |
72 | +impl<'a, B> BitmapRef<&'a mut B> | |
73 | +where | |
74 | + B: Bitmap | |
75 | +{ | |
76 | + #[inline] | |
77 | + pub fn get(&self) -> u32 { | |
78 | + get_slice(self.0, self.1) | |
79 | + } | |
80 | + | |
81 | + #[inline] | |
82 | + pub fn end(&self) -> bool { | |
83 | + self.0.len() < self.1 | |
84 | + } | |
85 | + | |
86 | + #[inline] | |
87 | + pub fn step(&mut self, n: usize) { | |
88 | + self.1 = std::cmp::min(self.1 + n, self.0.len()); | |
89 | + } | |
90 | +} | |
91 | + | |
92 | +impl<'a, B> BitmapRef<&'a mut B> | |
93 | +where | |
94 | + B: Bitmap | |
95 | +{ | |
96 | + pub fn set(&mut self, val: u32) { | |
97 | + set_slice(self.0, self.1, val) | |
98 | + } | |
99 | +} | |
100 | + | |
101 | +impl<'a, B> iter::Iterator for BitmapRef<&'a B> | |
102 | +where | |
103 | + B: Bitmap | |
104 | +{ | |
105 | + type Item = u32; | |
106 | + fn next(&mut self) -> Option<Self::Item> { | |
107 | + if self.1 >= self.0.len() - 1 { | |
108 | + None | |
109 | + } | |
110 | + else{ | |
111 | + let u = self.get(); | |
112 | + self.step(1); | |
113 | + Some(u) | |
114 | + } | |
115 | + } | |
116 | + #[inline] | |
117 | + fn count(self) -> usize { | |
118 | + self.0.len() - self.1 | |
119 | + } | |
120 | + | |
121 | + #[inline] | |
122 | + fn nth(&mut self, n: usize) -> Option<Self::Item> { | |
123 | + self.step(n); | |
124 | + self.next() | |
125 | + } | |
126 | +} | |
127 | + | |
128 | +impl<'a, B> iter::Iterator for BitmapRef<&'a mut B> | |
129 | +where | |
130 | + B: Bitmap | |
131 | +{ | |
132 | + type Item = u32; | |
133 | + fn next(&mut self) -> Option<Self::Item> { | |
134 | + if self.1 >= self.0.len() - 1 { | |
135 | + None | |
136 | + } | |
137 | + else{ | |
138 | + let u = self.get(); | |
139 | + self.step(1); | |
140 | + Some(u) | |
141 | + } | |
142 | + } | |
143 | + #[inline] | |
144 | + fn count(self) -> usize { | |
145 | + self.0.len() - self.1 | |
146 | + } | |
147 | + | |
148 | + #[inline] | |
149 | + fn nth(&mut self, n: usize) -> Option<Self::Item> { | |
150 | + self.step(n); | |
151 | + self.next() | |
152 | + } | |
153 | +} | |
154 | + | |
155 | + | |
156 | +#[derive(Clone, Debug, PartialEq)] | |
157 | +pub struct BitmapIterator<B, T>(BitmapRef<B>, std::marker::PhantomData<T>); | |
158 | + | |
159 | +impl<B, T> BitmapIterator<B, T> { | |
160 | + #[inline] | |
161 | + pub fn new(that: B) -> Self { | |
162 | + BitmapIterator(BitmapRef::new(that), std::marker::PhantomData) | |
163 | + } | |
164 | +} | |
165 | + | |
166 | +impl<B, T> std::convert::From<BitmapRef<B>> for BitmapIterator<B, T> { | |
167 | + #[inline] | |
168 | + fn from(that: BitmapRef<B>) -> Self { | |
169 | + BitmapIterator(that, std::marker::PhantomData) | |
170 | + } | |
171 | +} | |
172 | + | |
173 | +impl<'a, B, T> BitmapIterator<&'a B, T> | |
174 | +where | |
175 | + B: Bitmap, | |
176 | + T: From<u32>, | |
177 | +{ | |
178 | + #[inline] | |
179 | + pub fn get_val(&self) -> T { | |
180 | + self.0.get().into() | |
181 | + } | |
182 | +} | |
183 | + | |
184 | +impl<'a, B, T> BitmapIterator<&'a mut B, T> | |
185 | +where | |
186 | + B: Bitmap, | |
187 | + T: From<u32>, | |
188 | +{ | |
189 | + #[inline] | |
190 | + pub fn get_val(&self) -> T { | |
191 | + self.0.get().into() | |
192 | + } | |
193 | +} | |
194 | + | |
195 | +impl<'a, B, T> BitmapIterator<&'a mut B, T> | |
196 | +where | |
197 | + B: Bitmap, | |
198 | + T: Into<u32>, | |
199 | +{ | |
200 | + #[inline] | |
201 | + pub fn set_val(&mut self, val: T) { | |
202 | + self.0.set(val.into()); | |
203 | + } | |
204 | +} | |
205 | + | |
206 | +impl <'a, B, T> iter::Iterator for BitmapIterator<&'a B, T> | |
207 | +where | |
208 | + B: Bitmap, | |
209 | + T: From<u32>, | |
210 | +{ | |
211 | + type Item = T; | |
212 | + #[inline] | |
213 | + fn next(&mut self) -> Option<Self::Item> { | |
214 | + self.0.next().map(|a| a.into()) | |
215 | + } | |
216 | + #[inline] | |
217 | + fn count(self) -> usize { | |
218 | + self.0.count() | |
219 | + } | |
220 | +} | |
221 | + | |
222 | +impl <'a, B, T> iter::Iterator for BitmapIterator<&'a mut B, T> | |
223 | +where | |
224 | + B: Bitmap, | |
225 | + T: From<u32>, | |
226 | +{ | |
227 | + type Item = T; | |
228 | + #[inline] | |
229 | + fn next(&mut self) -> Option<Self::Item> { | |
230 | + self.0.next().map(|a| a.into()) | |
231 | + } | |
232 | + #[inline] | |
233 | + fn count(self) -> usize { | |
234 | + self.0.count() | |
235 | + } | |
236 | +} | |
237 | + | |
238 | +impl fmt::Display for dyn Bitmap { | |
239 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
240 | + let esize = self.element_size(); | |
241 | + f.write_str("{")?; | |
242 | + for i in 0 .. self.len() { | |
243 | + f.write_str("0b")?; | |
244 | + for e in 0 .. esize { | |
245 | + f.write_str( | |
246 | + match self.get_bit(e + (i * esize)) { | |
247 | + 0 => "0", | |
248 | + _ => "1", | |
249 | + })?; | |
250 | + } | |
251 | + f.write_str(",")?; | |
252 | + } | |
253 | + Ok(()) | |
254 | + } | |
255 | +} | |
256 | + | |
257 | +macro_rules! bitmap_size { | |
258 | + ($esize:expr, $count:expr) => { ((($esize * $count) + 7) / 8) as usize } | |
259 | +} | |
260 | + | |
261 | +macro_rules! bitmap { | |
262 | + ($name:ident [ $esize:expr ; $count:expr ] ) => { | |
263 | + | |
264 | + #[derive(Clone, PartialEq, Debug)] | |
265 | + #[repr(transparent)] | |
266 | + pub struct $name([u8; bitmap_size!($esize, $count)]); | |
267 | + | |
268 | + impl $name { | |
269 | + #[inline] | |
270 | + pub fn new() -> Self { | |
271 | + $name([0; bitmap_size!($esize, $count)]) | |
272 | + } | |
273 | + #[inline] | |
274 | + pub fn iter<'a>(&'a self) -> crate::bitmap::BitmapRef<&'a Self> { | |
275 | + crate::bitmap::BitmapRef(self, 0) | |
276 | + } | |
277 | + #[inline] | |
278 | + pub fn iter_mut<'a>(&'a mut self) -> crate::bitmap::BitmapRef<&'a mut Self> { | |
279 | + crate::bitmap::BitmapRef(self, 0) | |
280 | + } | |
281 | + #[inline] | |
282 | + pub fn ptr_size() -> usize { bitmap_size!($esize, $count) } | |
283 | + } | |
284 | + | |
285 | + impl Default for $name { | |
286 | + #[inline] | |
287 | + fn default() -> Self { $name::new() } | |
288 | + } | |
289 | + | |
290 | + impl crate::bitmap::Bitmap for $name { | |
291 | + #[inline] | |
292 | + fn get_bit(&self, i: usize) -> u8 { | |
293 | + (self.0[i >> 3] >> (i & 7)) & 1 | |
294 | + } | |
295 | + fn set_bit(&mut self, i: usize, val: u8) -> bool { | |
296 | + let mask = 1 << (i & 7); | |
297 | + let old = (self.0[i >> 3] & mask) != 0; | |
298 | + if val != 0 { | |
299 | + self.0[i >> 3] |= mask; | |
300 | + } | |
301 | + else{ | |
302 | + self.0[i >> 3] &= !mask; | |
303 | + } | |
304 | + old | |
305 | + } | |
306 | + #[inline] | |
307 | + fn element_size(&self) -> usize { $esize } | |
308 | + #[inline] | |
309 | + fn len(&self) -> usize { $count } | |
310 | + #[inline] | |
311 | + fn as_ptr(&self) -> *const u8 { self.0.as_ptr() } | |
312 | + #[inline] | |
313 | + fn as_mut_ptr(&mut self) -> *mut u8 { self.0.as_mut_ptr() } | |
314 | + } | |
315 | + | |
316 | + impl<'a> std::iter::IntoIterator for &'a $name { | |
317 | + type Item = u32; | |
318 | + type IntoIter = crate::bitmap::BitmapRef<Self>; | |
319 | + fn into_iter(self) -> Self::IntoIter { | |
320 | + crate::bitmap::BitmapRef(self, 0) | |
321 | + } | |
322 | + } | |
323 | + } | |
324 | +} | |
325 | + |
@@ -0,0 +1,312 @@ | ||
1 | +use std::cmp; | |
2 | +use std::ffi::CString; | |
3 | +use std::fs::File; | |
4 | +use std::io::{self, Read}; | |
5 | +use std::vec::Vec; | |
6 | +use ioctl::{self, AbsInfo, AbsValue}; | |
7 | +use bitmap::{Bitmap, BitmapIterator}; | |
8 | +use std::os::unix::io::RawFd as Fd; | |
9 | +use std::os::unix::io::{IntoRawFd, FromRawFd}; | |
10 | + | |
11 | +// Generated bindings | |
12 | +include!{concat!(env!("OUT_DIR"), "/rejoy_evdev_bind.rs")} | |
13 | + | |
14 | +extern "C" { | |
15 | +// For consistency with other drivers, use the same scaling function. | |
16 | +fn Rejoy_ScaleValue(from: i32, to: i32, t: i32) -> i32; | |
17 | +} | |
18 | + | |
19 | +macro_rules! btn_ranges { | |
20 | + ($($name:ident : $start:literal .. $end:literal,)*) => { | |
21 | + $( | |
22 | + const $name: std::ops::Range<u16> = std::ops::Range { | |
23 | + start: $start, | |
24 | + end: $end, | |
25 | + }; | |
26 | + )* | |
27 | + const TOTAL_BTN_COUNT: usize = 0 $( + $end - $start )*; | |
28 | + #[inline] | |
29 | + fn for_each_btn<F>(mut op: F) | |
30 | + where | |
31 | + F: FnMut(u16) | |
32 | + { | |
33 | + $( | |
34 | + for i in $name { | |
35 | + op(i); | |
36 | + } | |
37 | + )* | |
38 | + } | |
39 | + } | |
40 | +} | |
41 | + | |
42 | +// Ranges of useful BTN values. | |
43 | +btn_ranges!( | |
44 | + BTN_JOYSTICK: 0x120 .. 0x130, | |
45 | + BTN_GAMEPAD: 0x130 .. 0x13F, | |
46 | + BTN_WHEEL: 0x150 .. 0x152, | |
47 | + ); | |
48 | + | |
49 | +const REJOY_C_TIMEVAL_SIZE: usize = super::REJOY_C_TIMEVAL_SIZE as usize; | |
50 | +const INPUT_EVENT_SIZE: usize = REJOY_C_TIMEVAL_SIZE + 8; | |
51 | + | |
52 | +#[derive(Debug)] | |
53 | +#[repr(C)] | |
54 | +struct InputEvent { | |
55 | + time: [u8; REJOY_C_TIMEVAL_SIZE], | |
56 | + typ: u16, | |
57 | + code: u16, | |
58 | + value: i32, | |
59 | +} | |
60 | + | |
61 | +#[derive(Clone, PartialEq, Debug)] | |
62 | +struct AbsPair(AbsValue, AbsInfo); | |
63 | + | |
64 | +// TODO: This will be used when we handle SYN_DROPPED. | |
65 | +/* | |
66 | +impl AbsPair { | |
67 | + #[inline] | |
68 | + fn update(&mut self, fd: Fd) { | |
69 | + if let Ok(info) = ioctl::evdev_get_abs(fd, self.0) { | |
70 | + self.1 = info | |
71 | + } | |
72 | + } | |
73 | + #[inline] | |
74 | + fn value<'a>(&'a self) -> &'a AbsInfo { | |
75 | + &self.1 | |
76 | + } | |
77 | +} | |
78 | +*/ | |
79 | + | |
80 | +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] | |
81 | +struct Button(u8); | |
82 | + | |
83 | +const REJOY_MAX_BUTTONS: usize = super::REJOY_MAX_BUTTONS as usize; | |
84 | +const SOME_BUTTON: Button = Button(2); | |
85 | +const NONE_BUTTON: Button = Button(0); | |
86 | + | |
87 | +impl Button { | |
88 | + #[inline] | |
89 | + fn present(&self) -> bool { | |
90 | + (self.0 & 2) != 0 | |
91 | + } | |
92 | + #[inline] | |
93 | + fn value(&self) -> bool { | |
94 | + (self.0 & 1) != 0 | |
95 | + } | |
96 | + #[inline] | |
97 | + fn update(&mut self, val: bool) { | |
98 | + self.0 = if val { self.0 | 1 } else { self.0 & 2 }; | |
99 | + } | |
100 | +} | |
101 | + | |
102 | +impl std::convert::From<u32> for Button { | |
103 | + #[inline] | |
104 | + fn from(that: u32) -> Self { | |
105 | + Button(that as u8) | |
106 | + } | |
107 | +} | |
108 | + | |
109 | +impl std::convert::From<Button> for u32 { | |
110 | + #[inline] | |
111 | + fn from(that: Button) -> Self { | |
112 | + that.0 as u32 | |
113 | + } | |
114 | +} | |
115 | + | |
116 | +bitmap!{ Buttons [2; TOTAL_BTN_COUNT] } | |
117 | + | |
118 | +/* | |
119 | +#[inline] | |
120 | +fn cmp_abs_pair<T>(a: &(AbsValue, T), b: &(AbsValue, T)) -> std::cmp::Ordering { | |
121 | + AbsValue::cmp(a, b) | |
122 | +} | |
123 | +*/ | |
124 | + | |
125 | +#[derive(Clone, PartialEq, Debug)] | |
126 | +pub struct Gamepad { | |
127 | + name: CString, | |
128 | + axes: Vec<(AbsValue, AbsInfo)>, | |
129 | + hats: Vec<(AbsValue, AbsInfo)>, | |
130 | + buttons: Buttons, | |
131 | +} | |
132 | + | |
133 | +impl Gamepad { | |
134 | + fn open(fd: Fd) -> io::Result<Self> { | |
135 | + let name = CString::new(ioctl::evdev_get_name(fd)?)?; | |
136 | + let mut hats = Vec::new(); | |
137 | + let mut axes = Vec::new(); | |
138 | + ioctl::AbsValue::for_each(|a| | |
139 | + if let Ok(info) = ioctl::evdev_get_abs(fd, a) { | |
140 | + if info.min == info.max { | |
141 | + return; // Not an interesting axis. | |
142 | + } | |
143 | + if a.is_hat() { | |
144 | + hats.push((a, info)); | |
145 | + } | |
146 | + else if a.is_axis() { | |
147 | + axes.push((a, info)); | |
148 | + } | |
149 | + }); | |
150 | + if hats.is_empty() && axes.is_empty() { | |
151 | + Err(io::Error::new(io::ErrorKind::Other, "Not a gamepad/joystick")) | |
152 | + } | |
153 | + else{ | |
154 | + // Test for buttons. | |
155 | + let mut buttons = Buttons::new(); | |
156 | + let mut button_iter = buttons.iter_mut(); | |
157 | + if let Ok(button_test) = ioctl::evdev_get_btn(fd) { | |
158 | + for_each_btn(|i| { | |
159 | + let bit = button_test.get_bit(i as usize); | |
160 | + let b = if bit != 0 { SOME_BUTTON } else { NONE_BUTTON }; | |
161 | + button_iter.set(b.into()); | |
162 | + button_iter.step(1); | |
163 | + }); | |
164 | + } | |
165 | + Ok( Gamepad{ name, axes, hats, buttons } ) | |
166 | + } | |
167 | + } | |
168 | + fn button_iter<'a>(&'a self) -> BitmapIterator<&'a Buttons, Button> { | |
169 | + BitmapIterator::<&'a Buttons, Button>::new(&self.buttons) | |
170 | + } | |
171 | + fn button_iter_mut<'a>(&'a mut self) -> BitmapIterator<&'a mut Buttons, Button> { | |
172 | + BitmapIterator::<&'a mut Buttons, Button>::new(&mut self.buttons) | |
173 | + } | |
174 | + fn get_button(&self, i: usize) -> bool { | |
175 | + self.button_iter().filter(|b| b.present()).nth(i).map(|b| b.value()).unwrap_or(false) | |
176 | + } | |
177 | + #[inline] | |
178 | + fn get_num_buttons(&self) -> usize { | |
179 | + let n = self.button_iter().filter(|b| b.present()).count(); | |
180 | + cmp::min(n, REJOY_MAX_BUTTONS) | |
181 | + } | |
182 | + #[inline] | |
183 | + fn get_axis(&self, i: usize) -> i16 { | |
184 | + let info = self.axes[i].1; | |
185 | + unsafe { Rejoy_ScaleValue(info.min, info.max, info.value) as i16 } | |
186 | + } | |
187 | + #[inline] | |
188 | + fn get_num_axes(&self) -> usize { self.axes.len() } | |
189 | + #[inline] | |
190 | + fn get_hat(&self, _i: usize) -> u32 { | |
191 | + 0 | |
192 | + } | |
193 | + #[inline] | |
194 | + fn get_num_hats(&self) -> usize { self.hats.len() } | |
195 | + fn update(&mut self, fd: Fd) { | |
196 | + assert!(std::mem::size_of::<InputEvent>() == INPUT_EVENT_SIZE); | |
197 | + let mut f = unsafe { File::from_raw_fd(fd) }; | |
198 | + let mut buffer: [u8; INPUT_EVENT_SIZE] = [0; INPUT_EVENT_SIZE]; | |
199 | + | |
200 | + // Read as many events as possible. | |
201 | + let mut result = f.read(&mut buffer); | |
202 | + while let Ok(INPUT_EVENT_SIZE) = result { | |
203 | + let event_ptr = buffer.as_ptr() as *const InputEvent; | |
204 | + let event = unsafe{ &*event_ptr }; | |
205 | + match event.typ { | |
206 | + ioctl::EV_KEY => { | |
207 | + let mut button_iter = self.button_iter_mut(); | |
208 | + for_each_btn(|i| { | |
209 | + if i == event.code { | |
210 | + let mut b = button_iter.get_val(); | |
211 | + if !b.present() { | |
212 | + if cfg!(debug_assertions) { | |
213 | + eprintln!("Invalid button {} set", i); | |
214 | + } | |
215 | + } | |
216 | + else { | |
217 | + b.update(event.value != 0); | |
218 | + } | |
219 | + button_iter.set_val(b.into()); | |
220 | + } | |
221 | + button_iter.next(); | |
222 | + }); | |
223 | + }, | |
224 | + ioctl::EV_MSC => { | |
225 | + // println!("{:?}", event); | |
226 | + }, | |
227 | + ioctl::EV_ABS => { | |
228 | + let a = ioctl::ABS_VALUE_INT[event.code as usize]; | |
229 | + if a.is_hat() { | |
230 | + if let Some(mut abspair) = self.hats.iter_mut().find(|p| p.0 == a) { | |
231 | + abspair.1.value = event.value; | |
232 | + } | |
233 | + } | |
234 | + else if a.is_axis() { | |
235 | + if let Some(mut abspair) = self.axes.iter_mut().find(|p| p.0 == a) { | |
236 | + abspair.1.value = event.value; | |
237 | + } | |
238 | + } | |
239 | + else if cfg!(debug_assertions) { | |
240 | + eprintln!("Unexpected ABS type {}", a); | |
241 | + } | |
242 | + }, | |
243 | + ioctl::EV_SYN => { | |
244 | + // TODO: We should do a full re-read of the gamepad's | |
245 | + // state when we get SYN_DROPPED. | |
246 | + }, | |
247 | + _ => { | |
248 | + if cfg!(debug_assertions) { | |
249 | + eprintln!("Unkown event type {}", event.typ); | |
250 | + } | |
251 | + }, | |
252 | + } | |
253 | + result = f.read(&mut buffer); | |
254 | + } | |
255 | + // Drop the file descriptor again. | |
256 | + f.into_raw_fd(); | |
257 | + | |
258 | + if !cfg!(debug_assertions) { | |
259 | + return; | |
260 | + } | |
261 | + | |
262 | + match result { | |
263 | + Ok(len) => | |
264 | + if len != INPUT_EVENT_SIZE && len != 0 { | |
265 | + eprintln!("Invalid read size {}", len); | |
266 | + }, | |
267 | + Err(e) => | |
268 | + if e.kind() != io::ErrorKind::WouldBlock { | |
269 | + eprintln!("{} err: {}", fd, e); | |
270 | + }, | |
271 | + } | |
272 | + } | |
273 | +} | |
274 | + | |
275 | +#[allow(non_camel_case_types)] | |
276 | +type Rejoy_Evdev_Gamepad = Gamepad; | |
277 | + | |
278 | +#[allow(non_snake_case)] | |
279 | +#[no_mangle] | |
280 | +pub extern "C" fn Rejoy_Evdev_OpenGamepad(fd: Fd) -> *mut Gamepad { | |
281 | + match Gamepad::open(fd) { | |
282 | + Ok(gamepad) => { | |
283 | + let gamepad_box = Box::new(gamepad); | |
284 | + Box::leak(gamepad_box) as *mut Gamepad | |
285 | + }, | |
286 | + Err(e) => { | |
287 | + if cfg!(debug_assertions) { eprintln!("Could not open gamepad: {}", e); } | |
288 | + std::ptr::null_mut() | |
289 | + }, | |
290 | + } | |
291 | +} | |
292 | + | |
293 | +#[allow(non_snake_case)] | |
294 | +#[no_mangle] | |
295 | +pub extern "C" fn Rejoy_Evdev_CopyGamepad(gamepad_ptr: *const Gamepad) -> *mut Gamepad { | |
296 | + let gamepad = unsafe { (*gamepad_ptr).clone() }; | |
297 | + let gamepad_box = Box::new(gamepad); | |
298 | + Box::leak(gamepad_box) as *mut Gamepad | |
299 | +} | |
300 | + | |
301 | +#[allow(non_snake_case)] | |
302 | +#[no_mangle] | |
303 | +pub unsafe extern "C" fn Rejoy_Evdev_FinalizeGamepad(gamepad_ptr: *mut Gamepad) { | |
304 | + let _ = Box::from_raw(gamepad_ptr); | |
305 | +} | |
306 | + | |
307 | +#[allow(non_snake_case)] | |
308 | +#[no_mangle] | |
309 | +pub unsafe extern "C" fn Rejoy_Evdev_UpdateGamepad(gamepad_ptr: *mut Gamepad, fd: Fd) { | |
310 | + (*gamepad_ptr).update(fd); | |
311 | +} | |
312 | + |
@@ -0,0 +1,272 @@ | ||
1 | +// This Source Code Form is subject to the terms of the Mozilla Public | |
2 | +// License, v. 2.0. If a copy of the MPL was not distributed with this | |
3 | +// file, You can obtain one at https://mozilla.org/MPL/2.0/. | |
4 | + | |
5 | +//! ioctl glue for using evdev. | |
6 | + | |
7 | +use ioctl_sys; | |
8 | + | |
9 | +use bitmap::Bitmap; | |
10 | + | |
11 | +use std::default::Default; | |
12 | +use std::{io, fmt}; | |
13 | +use std::os::unix::io::RawFd as Fd; | |
14 | + | |
15 | +pub const KEY_MAX: u32 = 0x3FF; | |
16 | +pub const KEY_CNT: usize = KEY_MAX as usize + 1; | |
17 | +#[allow(unused)] | |
18 | +pub const SYN_REPORT: u16 = 0; | |
19 | +#[allow(unused)] | |
20 | +pub const SYN_CONFIG: u16 = 1; | |
21 | +#[allow(unused)] | |
22 | +pub const SYN_DROPPED: u16 = 3; | |
23 | +pub const EV_SYN: u16 = 0; | |
24 | +pub const EV_KEY: u16 = 1; | |
25 | +#[allow(unused)] | |
26 | +pub const EV_REL: u16 = 2; | |
27 | +pub const EV_ABS: u16 = 3; | |
28 | +pub const EV_MSC: u16 = 4; | |
29 | +#[allow(unused)] | |
30 | +pub const EV_SW: u16 = 5; | |
31 | +#[allow(unused)] | |
32 | +pub const EV_LED: u16 = 17; | |
33 | +#[allow(unused)] | |
34 | +pub const EV_FF: u16 = 31; | |
35 | + | |
36 | + | |
37 | +bitmap!{ Buttons [1; KEY_CNT] } | |
38 | + | |
39 | +#[derive(PartialEq, Eq, Clone, PartialOrd, Ord)] | |
40 | +#[allow(non_camel_case_types, unused)] | |
41 | +pub enum AbsAxisDir { x, y, other } | |
42 | +#[derive(PartialEq, Eq, Clone, PartialOrd, Ord)] | |
43 | +#[allow(non_camel_case_types, unused)] | |
44 | +pub enum AbsAxisType { hat, stick, slider, other } | |
45 | + | |
46 | +macro_rules! abs_attribute { | |
47 | + ($func:ident $what:ident $typ:ident $($attr:ident $name:ident,)*) => ( | |
48 | + #[allow(unused)] | |
49 | + pub fn $func(self) -> bool { | |
50 | + match self { | |
51 | + $( | |
52 | + AbsValue::$name => $typ::$what == $typ::$attr, | |
53 | + )* | |
54 | + } | |
55 | + } | |
56 | + ) | |
57 | +} | |
58 | + | |
59 | +macro_rules! abs_match_hat { | |
60 | + (hat ? $t:literal : $f:tt ) => {{ $t }}; | |
61 | + (stick ? $t:literal : $f:tt ) => {{ $f }}; | |
62 | + (slider ? $t:literal : $f:tt ) => {{ $f }}; | |
63 | + (other ? $t:literal : $f:tt ) => {{ $f }}; | |
64 | +} | |
65 | + | |
66 | +macro_rules! abs_match_stick { | |
67 | + (hat ? $t:literal : $f:tt ) => {{ $f }}; | |
68 | + (stick ? $t:literal : $f:tt ) => {{ $t }}; | |
69 | + (slider ? $t:literal : $f:tt ) => {{ $f }}; | |
70 | + (other ? $t:literal : $f:tt ) => {{ $f }}; | |
71 | +} | |
72 | + | |
73 | +macro_rules! abs_match_slider { | |
74 | + (hat ? $t:literal : $f:tt ) => {{ $f }}; | |
75 | + (stick ? $t:literal : $f:tt ) => {{ $f }}; | |
76 | + (slider ? $t:literal : $f:tt ) => {{ $t }}; | |
77 | + (other ? $t:literal : $f:tt ) => {{ $f }}; | |
78 | +} | |
79 | + | |
80 | +macro_rules! abs_match_other { | |
81 | + (hat ? $t:literal : $f:tt ) => {{ $f }}; | |
82 | + (stick ? $t:literal : $f:tt ) => {{ $f }}; | |
83 | + (slider ? $t:literal : $f:tt ) => {{ $f }}; | |
84 | + (other ? $t:literal : $f:tt ) => {{ $t }}; | |
85 | +} | |
86 | + | |
87 | +#[allow(unused)] | |
88 | +macro_rules! abs_match_axis { | |
89 | + (hat ? $t:literal : $f:tt ) => {{ $f }}; | |
90 | + (stick ? $t:literal : $f:tt ) => {{ $t }}; | |
91 | + (slider ? $t:literal : $f:tt ) => {{ $t }}; | |
92 | + (other ? $t:literal : $f:tt ) => {{ $f }}; | |
93 | +} | |
94 | + | |
95 | +macro_rules! abs_values { | |
96 | + | |
97 | + ($($axis:ident $typ:ident $name:ident,)*) => { | |
98 | + #[repr(u8)] | |
99 | + #[allow(non_camel_case_types, unused)] | |
100 | + #[derive(PartialEq, Eq, Clone, Copy, Debug, Ord, PartialOrd)] | |
101 | + pub enum AbsValue { | |
102 | + $($name,)* | |
103 | + } | |
104 | + pub const ABS_VALUE_MAX: usize = 0 $( + (AbsValue::$name as usize * 0) + 1)* ; | |
105 | + #[allow(unused)] | |
106 | + pub const ABS_NUM_HATS: usize = 0 $( + abs_match_hat!($typ ? 1 : 0) )* ; | |
107 | + #[allow(unused)] | |
108 | + pub const ABS_NUM_STICKS: usize = 0 $( + abs_match_stick!($typ ? 1 : 0) )* ; | |
109 | + #[allow(unused)] | |
110 | + pub const ABS_NUM_SLIDERS: usize = 0 $( + abs_match_slider!($typ ? 1 : 0) )* ; | |
111 | + #[allow(unused)] | |
112 | + pub const ABS_NUM_AXES: usize = ABS_NUM_STICKS + ABS_NUM_SLIDERS; | |
113 | + #[allow(unused)] | |
114 | + pub const ABS_NUM_OTHER: usize = 0 $( + abs_match_other!($typ ? 1 : 0) )* ; | |
115 | + // This kind of sucks but here we are. | |
116 | + pub const ABS_VALUE_INT: &'static [AbsValue; ABS_VALUE_MAX] = &[ | |
117 | + $(AbsValue::$name,)* | |
118 | + ]; | |
119 | + impl AbsValue { | |
120 | + abs_attribute!(is_x x AbsAxisDir $($axis $name,)*); | |
121 | + abs_attribute!(is_y y AbsAxisDir $($axis $name,)*); | |
122 | + abs_attribute!(is_stick stick AbsAxisType $($typ $name,)*); | |
123 | + abs_attribute!(is_hat hat AbsAxisType $($typ $name,)*); | |
124 | + abs_attribute!(is_slider slider AbsAxisType $($typ $name,)*); | |
125 | + | |
126 | + #[inline] | |
127 | + #[allow(unused)] | |
128 | + pub fn is_axis(self) -> bool { | |
129 | + self.is_stick() || self.is_slider() | |
130 | + } | |
131 | + | |
132 | + #[inline] | |
133 | + #[allow(unused)] | |
134 | + pub fn for_each<F>(mut op: F) | |
135 | + where | |
136 | + F: FnMut(AbsValue) | |
137 | + { | |
138 | + $(op(AbsValue::$name);)* | |
139 | + } | |
140 | + } | |
141 | + | |
142 | + impl fmt::Display for AbsValue { | |
143 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
144 | + f.write_str(match *self { | |
145 | + $( | |
146 | + AbsValue::$name => stringify!($name), | |
147 | + )* | |
148 | + }) | |
149 | + } | |
150 | + } | |
151 | + } | |
152 | +} | |
153 | + | |
154 | +abs_values!( | |
155 | + x stick AbsX, | |
156 | + y stick AbsY, | |
157 | + other stick AbsZ, | |
158 | + x stick AbsRX, | |
159 | + y stick AbsRY, | |
160 | + other stick AbsRZ, | |
161 | + other slider AbsThrottle, | |
162 | + x slider AbsRudder, | |
163 | + x other AbsWheel, | |
164 | + other slider AbsGas, | |
165 | + other slider AbsBrake, | |
166 | + x hat AbsHat0X, | |
167 | + y hat AbsHat1Y, | |
168 | + x hat AbsHat1X, | |
169 | + y hat AbsHat2Y, | |
170 | + x hat AbsHat2X, | |
171 | + y hat AbsHat3Y, | |
172 | + x hat AbsHat3X, | |
173 | + other other AbsPressure, | |
174 | + other other AbsDistance, | |
175 | + x other AbsTiltX, | |
176 | + y other AbsTiltY, | |
177 | + other other AbsToolWidth, | |
178 | + other slider AbsVolume, | |
179 | + other other AbsMisc, | |
180 | +); | |
181 | + | |
182 | +#[repr(C)] | |
183 | +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] | |
184 | +/// Data for EVIOCSABS/EVIOCGABS | |
185 | +pub struct AbsInfo { | |
186 | + /// Current value. Not clamped. | |
187 | + pub value: i32, | |
188 | + /// Logical minimum. | |
189 | + pub min: i32, | |
190 | + /// Logical maximum. | |
191 | + pub max: i32, | |
192 | + /// "Fuzz" value for input filtering. | |
193 | + pub fuzz: i32, | |
194 | + /// Deadzone threshold. Not useful for joysticks. | |
195 | + pub flat: i32, | |
196 | + /// Resolution for the values | |
197 | + pub res: i32, | |
198 | +} | |
199 | + | |
200 | +#[inline] | |
201 | +fn io_result_fn<T, F>(res: i32, that: T, op: F) -> io::Result<T> | |
202 | +where | |
203 | + F: FnOnce(i32) -> bool, | |
204 | +{ | |
205 | + if op(res) { | |
206 | + Ok(that) | |
207 | + } | |
208 | + else{ | |
209 | + Err(std::io::Error::last_os_error()) | |
210 | + } | |
211 | +} | |
212 | + | |
213 | +fn io_result<T>(res: i32, that: T) -> io::Result<T> { | |
214 | + io_result_fn(res, that, |i| i == 0) | |
215 | +} | |
216 | + | |
217 | +pub fn evdev_get_abs(fd: Fd, abs: AbsValue) -> io::Result<AbsInfo> { | |
218 | + let mut info: AbsInfo = Default::default(); | |
219 | + let info_addr: *mut AbsInfo = &mut info; | |
220 | + let res = unsafe { | |
221 | + ioctl_sys::ioctl(fd, | |
222 | + ior!(b'E', 0x40 + abs as u8, std::mem::size_of::<AbsInfo>()).into(), | |
223 | + info_addr) | |
224 | + }; | |
225 | + io_result(res, info) | |
226 | +} | |
227 | + | |
228 | +const NAME_BUFFER_LEN: usize = 255; | |
229 | + | |
230 | +pub fn evdev_get_name(fd: Fd) -> io::Result<String> { | |
231 | + let mut buffer: [u8; NAME_BUFFER_LEN] = [0; NAME_BUFFER_LEN]; | |
232 | + let buffer_slice = &mut buffer[0 .. NAME_BUFFER_LEN]; | |
233 | + let res = unsafe { | |
234 | + ioctl_sys::ioctl(fd, | |
235 | + ior!(b'E', 0x06, NAME_BUFFER_LEN).into(), | |
236 | + buffer_slice.as_mut_ptr()) | |
237 | + }; | |
238 | + io_result_fn(res, buffer_slice, |i| i > 0).map(|n| { | |
239 | + String::from_utf8_lossy(&n[0 .. (res as usize) - 1]).into() | |
240 | + }) | |
241 | +} | |
242 | + | |
243 | +pub fn evdev_get_btn(fd: Fd) -> io::Result<Buttons> { | |
244 | + let mut buttons = Buttons::new(); | |
245 | + let res = unsafe { | |
246 | + get_bits(fd, EV_KEY.into(), buttons.as_mut_ptr(), Buttons::ptr_size()) | |
247 | + }; | |
248 | + io_result_fn(res, buttons, |i| i >= 0) | |
249 | +} | |
250 | + | |
251 | +unsafe fn get_bits(fd: Fd, what: u32, bmp: *mut u8, len: usize) -> i32 { | |
252 | + ioctl_sys::ioctl(fd, | |
253 | + ioc!(ioctl_sys::READ, b'E', 0x20 + what, len).into(), | |
254 | + bmp) | |
255 | +} | |
256 | + | |
257 | +// TODO: props! | |
258 | +#[allow(unused)] | |
259 | +const EVDEV_NUM_PROPS: usize = 32; | |
260 | + | |
261 | +#[allow(unused)] | |
262 | +pub fn evdev_get_props(fd: Fd) -> io::Result<[u8; EVDEV_NUM_PROPS]> { | |
263 | + let mut buffer: [u8; EVDEV_NUM_PROPS] = [0; EVDEV_NUM_PROPS]; | |
264 | + let buffer_slice = &mut buffer[0 .. EVDEV_NUM_PROPS]; | |
265 | + let res = unsafe { | |
266 | + ioctl_sys::ioctl(fd, | |
267 | + ior!(b'E', 0x09, EVDEV_NUM_PROPS).into(), | |
268 | + buffer_slice.as_mut_ptr()) | |
269 | + }; | |
270 | + io_result(res, buffer) | |
271 | +} | |
272 | + |
@@ -0,0 +1,75 @@ | ||
1 | +#[macro_use] | |
2 | +extern crate ioctl_sys; | |
3 | + | |
4 | +use std::ptr; | |
5 | + | |
6 | +// Generated constants from the C headers. | |
7 | +include!{concat!(env!("OUT_DIR"), "/rejoy_evdev_defs.rs")} | |
8 | + | |
9 | +#[macro_use] | |
10 | +mod bitmap; | |
11 | + | |
12 | +#[macro_use] | |
13 | +mod ioctl; | |
14 | +mod gamepad; | |
15 | + | |
16 | +// Handwritten bindings. | |
17 | +type CVec = Vec<*mut core::ffi::c_void>; | |
18 | + | |
19 | +/// C/C++ Binding to Vec::push | |
20 | +/// | |
21 | +/// Specially handles a NULL input to create a new Vec. | |
22 | +#[allow(non_snake_case)] | |
23 | +#[no_mangle] | |
24 | +pub extern "C" fn Rejoy_Evdev_VecAppend(ptr: *mut core::ffi::c_void, val: *mut core::ffi::c_void) -> *mut core::ffi::c_void { | |
25 | + (if ptr != ptr::null_mut() { | |
26 | + let vec_ptr = ptr as *mut CVec; | |
27 | + unsafe { (*vec_ptr).push(val); } | |
28 | + vec_ptr | |
29 | + } | |
30 | + else{ | |
31 | + let vec: CVec = vec![val]; | |
32 | + let vec_box = Box::new(vec); | |
33 | + Box::leak(vec_box) as *mut CVec | |
34 | + }) as *mut core::ffi::c_void | |
35 | +} | |
36 | + | |
37 | +/// C/C++ Binding to Vec::len | |
38 | +/// | |
39 | +/// Specially handles a NULL input to be zero-length. | |
40 | +#[allow(non_snake_case)] | |
41 | +#[no_mangle] | |
42 | +pub extern "C" fn Rejoy_Evdev_VecGetLen(ptr: *const core::ffi::c_void) -> u32 { | |
43 | + if ptr == ptr::null_mut() { | |
44 | + 0 | |
45 | + } | |
46 | + else{ | |
47 | + let vec_ptr = ptr as *const CVec; | |
48 | + unsafe { (*vec_ptr).len() as u32 } | |
49 | + } | |
50 | +} | |
51 | + | |
52 | +/// C/C++ Binding to Vec[] | |
53 | +/// | |
54 | +/// Specially handles a NULL input to be filled with NULL's. | |
55 | +#[allow(non_snake_case)] | |
56 | +#[no_mangle] | |
57 | +pub extern "C" fn Rejoy_Evdev_VecGetValue(ptr: *const core::ffi::c_void, i: u32) -> *mut core::ffi::c_void { | |
58 | + if ptr == ptr::null_mut() { | |
59 | + ptr::null_mut() | |
60 | + } | |
61 | + else{ | |
62 | + let vec_ptr = ptr as *const CVec; | |
63 | + unsafe { (*vec_ptr)[i as usize] } | |
64 | + } | |
65 | +} | |
66 | + | |
67 | +/// C/C++ Binding to dropping a Vec | |
68 | +#[allow(non_snake_case)] | |
69 | +#[no_mangle] | |
70 | +pub extern "C" fn Rejoy_Evdev_FinalizeVec(ptr: *mut core::ffi::c_void) { | |
71 | + if ptr != ptr::null_mut() { | |
72 | + let vec_ptr = ptr as *mut CVec; | |
73 | + let _ = unsafe { Box::from_raw(vec_ptr) }; | |
74 | + } | |
75 | +} |
@@ -91,6 +91,10 @@ REJOY_DRIVER_INIT(XInput); | ||
91 | 91 | REJOY_DRIVER_INIT(BSD); |
92 | 92 | #endif |
93 | 93 | |
94 | +#ifdef REJOY_DRIVER_EVDEV | |
95 | +REJOY_DRIVER_INIT(Evdev); | |
96 | +#endif | |
97 | + | |
94 | 98 | /////////////////////////////////////////////////////////////////////////////// |
95 | 99 | // This is used by DriverList when statically starting up. |
96 | 100 | static struct DriverList *driver_list = NULL; |
@@ -110,11 +114,16 @@ void Init(const char **drivers){ | ||
110 | 114 | if(rejoy_find_driver(drivers, "xinput")) |
111 | 115 | InitXInput(); |
112 | 116 | #endif |
113 | - | |
117 | + | |
114 | 118 | #ifdef REJOY_DRIVER_BSD |
115 | 119 | if(rejoy_find_driver(drivers, "bsd") || rejoy_find_driver(drivers, "uhid")) |
116 | 120 | InitBSD(); |
117 | 121 | #endif |
122 | + | |
123 | +#ifdef REJOY_DRIVER_EVDEV | |
124 | + if(rejoy_find_driver(drivers, "evdev") || rejoy_find_driver(drivers, "evdev")) | |
125 | + InitEvdev(); | |
126 | +#endif | |
118 | 127 | } |
119 | 128 | |
120 | 129 | /////////////////////////////////////////////////////////////////////////////// |
@@ -133,6 +142,10 @@ void Shutdown() { | ||
133 | 142 | ShutdownBSD(); |
134 | 143 | #endif |
135 | 144 | |
145 | +#ifdef REJOY_DRIVER_EVDEV | |
146 | + ShutdownEvdev(); | |
147 | +#endif | |
148 | + | |
136 | 149 | driver_list = NULL; |
137 | 150 | |
138 | 151 | } |
@@ -51,7 +51,7 @@ int main(int argc, char **argv){ | ||
51 | 51 | } |
52 | 52 | |
53 | 53 | while(true){ |
54 | - REJOY_SLEEP(100); | |
54 | + REJOY_SLEEP(64); | |
55 | 55 | for(unsigned i = 0; i < num_drivers; i++){ |
56 | 56 | Rejoy::Driver *&driver = drivers[i]; |
57 | 57 | const unsigned num_gamepads = driver->getNumGamepads(); |
@@ -0,0 +1,10 @@ | ||
1 | +# Any copyright is dedicated to the Public Domain. | |
2 | +# http://creativecommons.org/publicdomain/zero/1.0/ | |
3 | +import os | |
4 | + | |
5 | +Import("environment") | |
6 | + | |
7 | +source = ["rejoy_unix.cpp", "rejoy_unix_core.c"] | |
8 | +rejoy_unix = environment.StaticLibrary("rejoy_unix", source) | |
9 | + | |
10 | +Return("rejoy_unix") |
@@ -92,6 +92,28 @@ void Rejoy_Unix_IterateGlob(const char *glob_path, | ||
92 | 92 | |
93 | 93 | cb(arg, path, fd); |
94 | 94 | } |
95 | + else{ | |
96 | + printf("Could not open %s (err %i): \n", path, errno); | |
97 | +#define ERRCASE(NAME, DSCR) case NAME: puts( #NAME ": " DSCR); break | |
98 | + switch(errno){ | |
99 | + ERRCASE(EACCES, "permission denied"); | |
100 | + ERRCASE(EINTR, "timed out"); | |
101 | + ERRCASE(EMFILE, "too many files open"); | |
102 | + ERRCASE(ENODEV, "invalid device"); | |
103 | + ERRCASE(ELOOP, "symlink loop"); | |
104 | +#ifdef ENOTDIR | |
105 | + ERRCASE(ENOTDIR, "invalid directory component"); | |
106 | +#endif | |
107 | +#ifdef EOVERFLOW | |
108 | + ERRCASE(EOVERFLOW, "file too large"); | |
109 | +#endif | |
110 | +#ifdef EFBIG | |
111 | + ERRCASE(EFBIG, "file too large"); | |
112 | +#endif | |
113 | + ERRCASE(EWOULDBLOCK, "opening would block"); | |
114 | + default: puts("unknown error."); | |
115 | + } | |
116 | + } | |
95 | 117 | } |
96 | 118 | } |
97 | 119 | globfree(&glob_data); |
@@ -14,12 +14,19 @@ else: | ||
14 | 14 | env["PATH"] = extra_path |
15 | 15 | |
16 | 16 | asflags=None |
17 | +print("Building for " + str(target)) | |
17 | 18 | if target == "x86" or target == "amd64": |
18 | 19 | if os.name == "nt": |
19 | 20 | if target == "amd64": |
20 | 21 | asflags = "-f win64 -D REJOY_WINDOWS" |
21 | 22 | elif target == "x86": |
22 | 23 | asflags = "-f win32" |
24 | + elif "darwin" in os.name.lower(): | |
25 | + asflags = "-f macho" | |
26 | + elif target == "x86": | |
27 | + asflags = "-f elf32" | |
28 | + elif target == "amd64": | |
29 | + asflags = "-f elf64" | |
23 | 30 | util_env = Environment(ENV = os.environ, TOOLS=["default", "nasm"], TARGET_ARCH=target) |
24 | 31 | # Try to find yasm or nasm. |
25 | 32 | conf = util_env.Configure() |
@@ -3,7 +3,7 @@ | ||
3 | 3 | |
4 | 4 | bits 64 |
5 | 5 | |
6 | -segment text | |
6 | +segment .text | |
7 | 7 | |
8 | 8 | global Rejoy_ScaleValue |
9 | 9 |
@@ -42,5 +42,5 @@ Rejoy_ScaleValue: | ||
42 | 42 | |
43 | 43 | %endif |
44 | 44 | |
45 | - add eax, r10d | |
45 | + add ax, 0x8000 | |
46 | 46 | ret |
@@ -11,9 +11,11 @@ | ||
11 | 11 | REJOY_CDECL(int) Rejoy_ScaleValue(int from, int to, int t){ |
12 | 12 | const int item_range = to - from; |
13 | 13 | int64_t scaled_data = t; |
14 | + if(item_range == 0) | |
15 | + return to; | |
14 | 16 | scaled_data -= from; |
15 | 17 | scaled_data *= REJOY_SHRT_RANGE; |
16 | 18 | scaled_data /= item_range; |
17 | - scaled_data += REJOY_SHRT_RANGE; | |
19 | + scaled_data += SHRT_MIN; | |
18 | 20 | return (int)scaled_data; |
19 | 21 | } |
@@ -13,21 +13,21 @@ global _Rejoy_ScaleValue | ||
13 | 13 | ; Rejoy_ScaleValue(int from, int to, int t) |
14 | 14 | Rejoy_ScaleValue: |
15 | 15 | _Rejoy_ScaleValue: |
16 | - mov eax, [esp-12] | |
17 | - sub eax, [esp-4] | |
16 | + mov eax, [esp+12] | |
17 | + sub eax, [esp+4] | |
18 | 18 | |
19 | 19 | mov ecx, 0x0000FFFF |
20 | 20 | imul ecx |
21 | 21 | |
22 | 22 | ; Get the item range. |
23 | - mov ecx, [esp-8] | |
24 | - sub ecx, [esp-4] | |
23 | + mov ecx, [esp+8] | |
24 | + sub ecx, [esp+4] | |
25 | 25 | ; It's OK that this happens late in the process since it's an edge case anyway. |
26 | 26 | jz div_by_zero |
27 | 27 | |
28 | 28 | idiv ecx |
29 | - add eax, 0x0000FFFF | |
29 | + add ax, 0x8000 | |
30 | 30 | ret |
31 | 31 | div_by_zero: |
32 | - xor eax, eax | |
32 | + mov eax, [esp+4] | |
33 | 33 | ret |