Skip to content

Commit

Permalink
Improved gamepad navigation, stick support
Browse files Browse the repository at this point in the history
Former-commit-id: e3d214c
  • Loading branch information
tkashkin committed Oct 14, 2018
1 parent 757feac commit d2dc268
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 13 deletions.
37 changes: 36 additions & 1 deletion src/ui/views/GamesView/GamesView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ namespace GameHub.UI.Views.GamesView

#if MANETTE
private Manette.Monitor manette_monitor = new Manette.Monitor();
private bool gamepad_connected = false;
private bool gamepad_axes_to_keys_thread_running = false;
#endif

construct
Expand Down Expand Up @@ -353,6 +355,7 @@ namespace GameHub.UI.Views.GamesView
on_gamepad_connected(manette_device);
}
manette_monitor.device_connected.connect(on_gamepad_connected);
manette_monitor.device_disconnected.connect(on_gamepad_disconnected);
#endif

load_games();
Expand Down Expand Up @@ -834,6 +837,14 @@ namespace GameHub.UI.Views.GamesView
device.button_press_event.connect(on_gamepad_button_press_event);
device.button_release_event.connect(on_gamepad_button_release_event);
device.absolute_axis_event.connect(on_gamepad_absolute_axis_event);
gamepad_connected = true;
gamepad_axes_to_keys_thread();
}

private void on_gamepad_disconnected(Manette.Device device)
{
debug("[Gamepad] '%s' disconnected", device.get_name());
gamepad_connected = false;
}

private void on_gamepad_button_press_event(Manette.Device device, Manette.Event e)
Expand All @@ -855,14 +866,38 @@ namespace GameHub.UI.Views.GamesView
if(Gamepad.Buttons.has_key(btn))
{
var b = Gamepad.Buttons.get(btn);
b.emit_kb_event(type);
b.emit_key_event(type);
debug("[Gamepad] Button %s: %s (%s) [%d]", (type == EventType.KEY_PRESS ? "pressed" : "released"), b.name, b.long_name, btn);
}
}

private void on_gamepad_absolute_axis_event(Manette.Event e)
{
uint16 axis;
double value;
if(!e.get_absolute(out axis, out value)) return;

if(Gamepad.Axes.has_key(axis))
{
Gamepad.Axes.get(axis).value = value;
}
}

private void gamepad_axes_to_keys_thread()
{
if(gamepad_axes_to_keys_thread_running) return;
Utils.thread("GamepadAxesToKeysThread", () => {
gamepad_axes_to_keys_thread_running = true;
while(gamepad_connected)
{
foreach(var axis in Gamepad.Axes.values)
{
axis.emit_key_event();
}
Thread.usleep(Gamepad.KEY_EVENT_EMIT_INTERVAL);
}
gamepad_axes_to_keys_thread_running = false;
});
}
#endif
}
Expand Down
132 changes: 126 additions & 6 deletions src/utils/Gamepad.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ using Gee;

namespace GameHub.Utils.Gamepad
{
public const int KEY_EVENT_EMIT_INTERVAL = 50000;
public const int KEY_UP_EMIT_TIMEOUT = 50000;

public static HashMap<uint16, Button> Buttons;
public static HashMap<uint16, Axis> Axes;

public static Button BTN_A;
public static Button BTN_B;
Expand Down Expand Up @@ -54,9 +58,15 @@ namespace GameHub.Utils.Gamepad
public static Button SC_GRIP_LEFT;
public static Button SC_GRIP_RIGHT;

public static Axis AXIS_LS_X;
public static Axis AXIS_LS_Y;
public static Axis AXIS_RS_X;
public static Axis AXIS_RS_Y;

public static void init()
{
Buttons = new Gee.HashMap<uint16, Button>();
Buttons = new HashMap<uint16, Button>();
Axes = new HashMap<uint16, Axis>();

BTN_A = b(0x130, "A", null, "Return");
BTN_B = b(0x131, "B", null, "Escape");
Expand Down Expand Up @@ -88,6 +98,11 @@ namespace GameHub.Utils.Gamepad

SC_GRIP_LEFT = b(0x150, "LG", "Left Grip");
SC_GRIP_RIGHT = b(0x151, "RG", "Right Grip");

AXIS_LS_X = a(0x0, "LS X", "Left Stick X", "Left", "Right");
AXIS_LS_Y = a(0x1, "LS Y", "Left Stick Y", "Up", "Down");
AXIS_RS_X = a(0x2, "RS X", "Right Stick X");
AXIS_RS_Y = a(0x3, "RS Y", "Right Stick Y");
}

private static Button b(uint16 code, string name, string? long_name=null, string? key_name=null)
Expand All @@ -97,6 +112,13 @@ namespace GameHub.Utils.Gamepad
return btn;
}

private static Axis a(uint16 code, string name, string? long_name=null, string? negative_key_name=null, string? positive_key_name=null, double key_threshold=0.5)
{
var axis = new Axis(code, name, long_name, negative_key_name, positive_key_name, key_threshold);
Axes.set(code, axis);
return axis;
}

public class Button: Object
{
public uint16 code { get; construct; }
Expand All @@ -109,12 +131,110 @@ namespace GameHub.Utils.Gamepad
Object(code: code, name: name, long_name: long_name ?? name, key_name: key_name);
}

// hack, but works (on X11)
public void emit_kb_event(EventType type)
public void emit_key_event(EventType type)
{
Gamepad.emit_key_event(key_name, type);
}
}

public class Axis: Object
{
public uint16 code { get; construct; }
public string name { get; construct; }
public string long_name { get; construct; }
public string? negative_key_name { get; construct; }
public string? positive_key_name { get; construct; }
public double key_threshold { get; construct; }

private double _value = 0;
private int _value_sign = 0;
private int _pressed_sign = 0;
private bool _sign_changed = false;

private Timer timer = new Timer();

public double value
{
get
{
return _value;
}
set
{
int sign = value < -key_threshold ? -1 : (value > key_threshold ? 1 : 0);
_sign_changed = _value_sign == sign;
_value_sign = sign;
_value = value;
}
}

public Axis(uint16 code, string name, string? long_name=null, string? negative_key_name=null, string? positive_key_name=null, double key_threshold=0.5)
{
Object(code: code, name: name, long_name: long_name ?? name, negative_key_name: negative_key_name, positive_key_name: positive_key_name, key_threshold: key_threshold);
}

public void emit_key_event()
{
if(negative_key_name == null && positive_key_name == null) return;

ulong last_update;
timer.elapsed(out last_update);
if(_value_sign == 0 && last_update >= Gamepad.KEY_UP_EMIT_TIMEOUT)
{
if(_pressed_sign < 0) Gamepad.emit_key_event(negative_key_name, EventType.KEY_RELEASE);
if(_pressed_sign > 0) Gamepad.emit_key_event(positive_key_name, EventType.KEY_RELEASE);
timer.stop();
_value = 0;
_value_sign = 0;
_pressed_sign = 0;
_sign_changed = false;
return;
}

if(!_sign_changed) return;

if(_value_sign < 0)
{
Gamepad.emit_key_event(positive_key_name, EventType.KEY_RELEASE);
Gamepad.emit_key_event(negative_key_name, EventType.KEY_PRESS);
_pressed_sign = -1;
}
else if(_value_sign > 0)
{
Gamepad.emit_key_event(negative_key_name, EventType.KEY_RELEASE);
Gamepad.emit_key_event(positive_key_name, EventType.KEY_PRESS);
_pressed_sign = 1;
}
else
{
if(_pressed_sign < 0) Gamepad.emit_key_event(negative_key_name, EventType.KEY_RELEASE);
if(_pressed_sign > 0) Gamepad.emit_key_event(positive_key_name, EventType.KEY_RELEASE);
_pressed_sign = 0;
}

_sign_changed = false;
timer.start();
}
}

// hack, but works (on X11)
private static void emit_key_event(string? key_name, EventType type)
{
if(key_name == null) return;

bool active = false;

foreach(var wnd in Gtk.Window.list_toplevels())
{
if(key_name == null) return;
var event = type == EventType.KEY_RELEASE ? "keyup" : "keydown";
Utils.run({ "xdotool", event, key_name });
if(wnd.is_active)
{
active = true;
break;
}
}

if(!active) return;

Utils.run({ "xdotool", type == EventType.KEY_RELEASE ? "keyup" : "keydown", key_name }, null, null, false, false);
}
}
12 changes: 6 additions & 6 deletions src/utils/Utils.vala
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ namespace GameHub.Utils
}
}

public static string run(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false)
public static string run(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false, bool log=true)
{
string stdout;

Expand All @@ -74,7 +74,7 @@ namespace GameHub.Utils

try
{
debug("[Utils.run] {'%s'}; dir: '%s'", string.joinv("' '", cmd), cdir);
if(log) debug("[Utils.run] {'%s'}; dir: '%s'", string.joinv("' '", cmd), cdir);
Process.spawn_sync(cdir, cmd, cenv, SpawnFlags.SEARCH_PATH, null, out stdout);
stdout = stdout.strip();
if(stdout.length > 0) print(stdout + "\n");
Expand All @@ -86,7 +86,7 @@ namespace GameHub.Utils
return stdout;
}

public static async void run_async(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false, bool wait=true)
public static async void run_async(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false, bool wait=true, bool log=true)
{
Pid pid;

Expand All @@ -102,7 +102,7 @@ namespace GameHub.Utils

try
{
debug("[Utils.run_async] Running {'%s'} in '%s'", string.joinv("' '", cmd), cdir);
if(log) debug("[Utils.run_async] Running {'%s'} in '%s'", string.joinv("' '", cmd), cdir);
Process.spawn_async(cdir, cmd, cenv, SpawnFlags.SEARCH_PATH | SpawnFlags.STDERR_TO_DEV_NULL | SpawnFlags.DO_NOT_REAP_CHILD, null, out pid);

ChildWatch.add(pid, (pid, status) => {
Expand All @@ -118,12 +118,12 @@ namespace GameHub.Utils
if(wait) yield;
}

public static async string run_thread(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false)
public static async string run_thread(string[] cmd, string? dir=null, string[]? env=null, bool override_runtime=false, bool log=true)
{
string stdout = "";

Utils.thread("Utils.run_thread", () => {
stdout = Utils.run(cmd, dir, env, override_runtime);
stdout = Utils.run(cmd, dir, env, override_runtime, log);
Idle.add(run_thread.callback);
});

Expand Down

0 comments on commit d2dc268

Please sign in to comment.