[gnome-games/wip/abhinavsingh/keyboard-config] keyboard: Add keyboard mapping
- From: Abhinav Singh <abhinavsingh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games/wip/abhinavsingh/keyboard-config] keyboard: Add keyboard mapping
- Date: Sun, 27 Aug 2017 22:57:51 +0000 (UTC)
commit 2b529a387f267832b1f717ac14a8e3b440b46961
Author: theawless <theawless gmail com>
Date: Sun Aug 27 21:59:21 2017 +0530
keyboard: Add keyboard mapping
data/Makefile.am | 3 +
data/org.gnome.Games.gresource.xml | 3 +
data/ui/keyboard-configurer.ui | 106 ++++++++++++++++
data/ui/keyboard-mapper.ui | 37 ++++++
data/ui/keyboard-tester.ui | 18 +++
data/ui/preferences-page-controllers.ui | 73 +++++++++---
src/Makefile.am | 6 +
src/keyboard/keyboard-mapping-builder.vala | 60 +++++++++
src/keyboard/keyboard-mapping-manager.vala | 86 +++++++++++++
src/retro/retro-input-manager.vala | 14 ++-
src/ui/keyboard-configurer.vala | 190 ++++++++++++++++++++++++++++
src/ui/keyboard-mapper.vala | 76 +++++++++++
src/ui/keyboard-tester.vala | 43 +++++++
src/ui/preferences-page-controllers.vala | 23 ++++
14 files changed, 722 insertions(+), 16 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index fff2a95..03ac46f 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -41,6 +41,9 @@ EXTRA_DIST = \
ui/gamepad-mapper.ui \
ui/gamepad-tester.ui \
ui/game-icon-view.ui \
+ ui/keyboard-configurer.ui \
+ ui/keyboard-mapper.ui \
+ ui/keyboard-tester.ui \
ui/media-menu-button.ui \
ui/media-selector.ui \
ui/preferences-page-controllers.ui \
diff --git a/data/org.gnome.Games.gresource.xml b/data/org.gnome.Games.gresource.xml
index b8ba7b5..a175bad 100644
--- a/data/org.gnome.Games.gresource.xml
+++ b/data/org.gnome.Games.gresource.xml
@@ -20,6 +20,9 @@
<file preprocess="xml-stripblanks">ui/gamepad-mapper.ui</file>
<file preprocess="xml-stripblanks">ui/gamepad-tester.ui</file>
<file preprocess="xml-stripblanks">ui/game-icon-view.ui</file>
+ <file preprocess="xml-stripblanks">ui/keyboard-configurer.ui</file>
+ <file preprocess="xml-stripblanks">ui/keyboard-mapper.ui</file>
+ <file preprocess="xml-stripblanks">ui/keyboard-tester.ui</file>
<file preprocess="xml-stripblanks">ui/media-menu-button.ui</file>
<file preprocess="xml-stripblanks">ui/media-selector.ui</file>
<file preprocess="xml-stripblanks">ui/preferences-page-controllers.ui</file>
diff --git a/data/ui/keyboard-configurer.ui b/data/ui/keyboard-configurer.ui
new file mode 100644
index 0000000..a47c3b7
--- /dev/null
+++ b/data/ui/keyboard-configurer.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GamesKeyboardConfigurer" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="halign">fill</property>
+ <property name="valign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox" id="keyboard_mapper_holder">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="name">keyboard_mapper</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="keyboard_tester_holder">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="name">keyboard_tester</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="action_bar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="reset_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Reset</property>
+ <signal name="clicked" handler="on_reset_clicked"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="configure_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Configure</property>
+ <signal name="clicked" handler="on_configure_clicked"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-back">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="back_image">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cancel</property>
+ <signal name="clicked" handler="on_cancel_clicked"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/data/ui/keyboard-mapper.ui b/data/ui/keyboard-mapper.ui
new file mode 100644
index 0000000..9f83161
--- /dev/null
+++ b/data/ui/keyboard-mapper.ui
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GamesKeyboardMapper" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GamesGamepadView" id="gamepad_view">
+ <property name="visible">True</property>
+ <property name="halign">fill</property>
+ <property name="valign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="action_bar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="skip_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Skip</property>
+ <signal name="clicked" handler="on_skip_clicked"/>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child type="center">
+ <object class="GtkLabel" id="info_message">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/keyboard-tester.ui b/data/ui/keyboard-tester.ui
new file mode 100644
index 0000000..4320ee7
--- /dev/null
+++ b/data/ui/keyboard-tester.ui
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GamesKeyboardTester" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GamesGamepadView" id="gamepad_view">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">fill</property>
+ <property name="valign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/preferences-page-controllers.ui b/data/ui/preferences-page-controllers.ui
index 09bd294..14f3a14 100644
--- a/data/ui/preferences-page-controllers.ui
+++ b/data/ui/preferences-page-controllers.ui
@@ -23,35 +23,78 @@
<property name="can_focus">False</property>
<property name="visible">True</property>
<child>
- <object class="GtkFrame">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="border_width">12</property>
- <property name="valign">start</property>
- <property name="shadow_type">none</property>
+ <property name="orientation">vertical</property>
<child>
- <object class="GtkAlignment">
+ <object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="top_padding">12</property>
+ <property name="border_width">12</property>
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <property name="shadow_type">none</property>
<child>
- <object class="GtkListBox" id="gamepads_list_box">
+ <object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="selection_mode">none</property>
- <signal name="row-activated" handler="gamepads_list_box_row_activated"/>
+ <property name="top_padding">12</property>
+ <child>
+ <object class="GtkListBox" id="gamepads_list_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="gamepads_list_box_row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Gamepads</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
</object>
</child>
</object>
</child>
- <child type="label">
- <object class="GtkLabel">
+ <child>
+ <object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Gamepads</property>
- <attributes>
- <attribute name="weight" value="bold"/>
- </attributes>
+ <property name="border_width">12</property>
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">12</property>
+ <child>
+ <object class="GtkListBox" id="keyboard_list_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ <signal name="row-activated" handler="keyboard_list_box_row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Keyboard</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/src/Makefile.am b/src/Makefile.am
index 645eb45..8091a8c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -118,6 +118,9 @@ gnome_games_SOURCES = \
grilo/grilo-cover.vala \
grilo/grilo-media.vala \
\
+ keyboard/keyboard-mapping-builder.vala \
+ keyboard/keyboard-mapping-manager.vala \
+ \
retro/retro-core-source.vala \
retro/retro-error.vala \
retro/retro-gamepad.vala \
@@ -153,6 +156,9 @@ gnome_games_SOURCES = \
ui/game-icon-view.vala \
ui/game-thumbnail.vala \
ui/gamepad-view-configuration.vala \
+ ui/keyboard-configurer.vala \
+ ui/keyboard-mapper.vala \
+ ui/keyboard-tester.vala \
ui/media-selector.vala \
ui/media-menu-button.vala \
ui/preferences-page.vala \
diff --git a/src/keyboard/keyboard-mapping-builder.vala b/src/keyboard/keyboard-mapping-builder.vala
new file mode 100644
index 0000000..5b8b5bd
--- /dev/null
+++ b/src/keyboard/keyboard-mapping-builder.vala
@@ -0,0 +1,60 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.KeyboardMappingBuilder : Object {
+ public Retro.GamepadConfiguration mapping { private set; get; }
+ private static uint16[] GAMEPAD_KEYS;
+
+ static construct {
+ GAMEPAD_KEYS = new uint16[Retro.JoypadId.COUNT];
+ GAMEPAD_KEYS[Retro.JoypadId.B] = EventCode.BTN_A;
+ GAMEPAD_KEYS[Retro.JoypadId.Y] = EventCode.BTN_Y;
+ GAMEPAD_KEYS[Retro.JoypadId.SELECT] = EventCode.BTN_SELECT;
+ GAMEPAD_KEYS[Retro.JoypadId.START] = EventCode.BTN_START;
+ GAMEPAD_KEYS[Retro.JoypadId.UP] = EventCode.BTN_DPAD_UP;
+ GAMEPAD_KEYS[Retro.JoypadId.DOWN] = EventCode.BTN_DPAD_DOWN;
+ GAMEPAD_KEYS[Retro.JoypadId.LEFT] = EventCode.BTN_DPAD_LEFT;
+ GAMEPAD_KEYS[Retro.JoypadId.RIGHT] = EventCode.BTN_DPAD_RIGHT;
+ GAMEPAD_KEYS[Retro.JoypadId.A] = EventCode.BTN_B;
+ GAMEPAD_KEYS[Retro.JoypadId.X] = EventCode.BTN_X;
+ GAMEPAD_KEYS[Retro.JoypadId.L] = EventCode.BTN_TL;
+ GAMEPAD_KEYS[Retro.JoypadId.R] = EventCode.BTN_TR;
+ GAMEPAD_KEYS[Retro.JoypadId.L2] = EventCode.BTN_TL2;
+ GAMEPAD_KEYS[Retro.JoypadId.R2] = EventCode.BTN_TR2;
+ GAMEPAD_KEYS[Retro.JoypadId.L3] = EventCode.BTN_THUMBL;
+ GAMEPAD_KEYS[Retro.JoypadId.R3] = EventCode.BTN_THUMBR;
+ }
+
+ construct {
+ mapping = new Retro.GamepadConfiguration ();
+ }
+
+ public bool set_input_mapping (GamepadInput input, uint16 keycode) {
+ var joypad_id = joypad_id_from_event_code (input.code);
+ if (joypad_id == -1)
+ return false;
+
+ for (var i = 0; i < Retro.JoypadId.COUNT; ++i) {
+ var key = mapping.get_button_key ((Retro.JoypadId) i);
+ if (key == keycode)
+ return false;
+ }
+ mapping.set_button_key ((Retro.JoypadId) joypad_id, keycode);
+
+ return true;
+ }
+
+ public static uint16 event_code_from_joypad_id (Retro.JoypadId button) {
+ if ((int) button < Retro.JoypadId.COUNT)
+ return GAMEPAD_KEYS[button];
+
+ return EventCode.EV_MAX;
+ }
+
+ public static int joypad_id_from_event_code (uint16 key) {
+ for (var i = 0; i < GAMEPAD_KEYS.length; ++i)
+ if (key == GAMEPAD_KEYS[(Retro.JoypadId) i])
+ return i;
+
+ return -1;
+ }
+}
diff --git a/src/keyboard/keyboard-mapping-manager.vala b/src/keyboard/keyboard-mapping-manager.vala
new file mode 100644
index 0000000..a690223
--- /dev/null
+++ b/src/keyboard/keyboard-mapping-manager.vala
@@ -0,0 +1,86 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.KeyboardMappingManager : Object {
+ private const string MAPPING_FILE_NAME = "keyboard-mapping.txt";
+ private const string GROUP_NAME = "KeyboardMapping";
+
+ private Retro.GamepadConfiguration _mapping;
+ public Retro.GamepadConfiguration mapping {
+ set {
+ is_default_mapping = false;
+ _mapping = value;
+ }
+ get { return _mapping; }
+ }
+
+ private KeyFile mapping_key_file;
+ private File mapping_file;
+ public bool is_default_mapping { private set; get; }
+ private static KeyboardMappingManager? instance;
+
+ construct {
+ var config_dir = Application.get_config_dir ();
+ var path = Path.build_filename (config_dir, MAPPING_FILE_NAME);
+
+ mapping_file = File.new_for_path (path);
+ mapping_key_file = new KeyFile ();
+ }
+
+ private KeyboardMappingManager () {
+ reset_mapping ();
+ load_mapping ();
+ }
+
+ public static KeyboardMappingManager get_instance () {
+ if (instance == null)
+ instance = new KeyboardMappingManager ();
+
+ return instance;
+ }
+
+ public void reset_mapping () {
+ mapping = new Retro.GamepadConfiguration ();
+ mapping.set_to_default ();
+ is_default_mapping = true;
+ }
+
+ public void save_mapping () {
+ if (!mapping_file.query_exists ())
+ mapping_file.create (FileCreateFlags.NONE);
+
+ var enumc = (EnumClass) typeof (Retro.JoypadId).class_ref ();
+ foreach (var enumv in enumc.values) {
+ var button = enumv.value_nick;
+ var key = mapping.get_button_key ((Retro.JoypadId) enumv.value);
+ mapping_key_file.set_integer (GROUP_NAME, button, key);
+ }
+
+ mapping_key_file.save_to_file (mapping_file.get_path ());
+ }
+
+ private void load_mapping () {
+ if (!mapping_file.query_exists ()) {
+ debug ("User keyboard mapping file doesn't exist.");
+
+ return;
+ }
+
+ mapping_key_file.load_from_file (mapping_file.get_path (), KeyFileFlags.NONE);
+ mapping = new Retro.GamepadConfiguration ();
+ is_default_mapping = false;
+
+ var enumc = (EnumClass) typeof (Retro.JoypadId).class_ref ();
+ foreach (var enumv in enumc.values) {
+ var button = enumv.value_nick;
+ var key = mapping_key_file.get_integer (GROUP_NAME, button);
+ mapping.set_button_key ((Retro.JoypadId) enumv.value, (uint16) key);
+ }
+ }
+
+ public void delete_mapping () {
+ if (!mapping_file.query_exists ())
+ return;
+
+ mapping_file.delete ();
+ }
+}
diff --git a/src/retro/retro-input-manager.vala b/src/retro/retro-input-manager.vala
index 26fdc52..c56d404 100644
--- a/src/retro/retro-input-manager.vala
+++ b/src/retro/retro-input-manager.vala
@@ -1,16 +1,28 @@
// This file is part of GNOME Games. License: GPL-3.0+.
private class Games.RetroInputManager : Retro.InputDeviceManager, Retro.Rumble {
+ private Gtk.Widget widget;
private Retro.VirtualGamepad keyboard;
private GamepadMonitor gamepad_monitor;
+ private KeyboardMappingManager keyboard_mapping_manager;
private Retro.InputDevice?[] input_devices;
private int keyboard_port;
private bool present_analog_sticks;
+ construct {
+ keyboard_mapping_manager = KeyboardMappingManager.get_instance ();
+ keyboard_mapping_manager.notify["mapping"].connect (() => {
+ keyboard = new Retro.VirtualGamepad.with_configuration (widget,
keyboard_mapping_manager.mapping);
+ set_keyboard (widget);
+ set_controller_device (keyboard_port, keyboard);
+ });
+ }
+
public RetroInputManager (Gtk.Widget widget, bool present_analog_sticks) {
this.present_analog_sticks = present_analog_sticks;
+ this.widget = widget;
- keyboard = new Retro.VirtualGamepad (widget);
+ keyboard = new Retro.VirtualGamepad.with_configuration (widget,
keyboard_mapping_manager.mapping);
set_keyboard (widget);
gamepad_monitor = GamepadMonitor.get_instance ();
diff --git a/src/ui/keyboard-configurer.vala b/src/ui/keyboard-configurer.vala
new file mode 100644
index 0000000..c56c4ff
--- /dev/null
+++ b/src/ui/keyboard-configurer.vala
@@ -0,0 +1,190 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/keyboard-configurer.ui")]
+private class Games.KeyboardConfigurer : Gtk.Box {
+ private const GamepadInput[] KEYBOARD_GAMEPAD_INPUTS = {
+ { EventCode.EV_KEY, EventCode.BTN_A },
+ { EventCode.EV_KEY, EventCode.BTN_B },
+ { EventCode.EV_KEY, EventCode.BTN_X },
+ { EventCode.EV_KEY, EventCode.BTN_Y },
+ { EventCode.EV_KEY, EventCode.BTN_START },
+ { EventCode.EV_KEY, EventCode.BTN_SELECT },
+ { EventCode.EV_KEY, EventCode.BTN_THUMBL },
+ { EventCode.EV_KEY, EventCode.BTN_THUMBR },
+ { EventCode.EV_KEY, EventCode.BTN_TL },
+ { EventCode.EV_KEY, EventCode.BTN_TR },
+ { EventCode.EV_KEY, EventCode.BTN_DPAD_UP },
+ { EventCode.EV_KEY, EventCode.BTN_DPAD_LEFT },
+ { EventCode.EV_KEY, EventCode.BTN_DPAD_DOWN },
+ { EventCode.EV_KEY, EventCode.BTN_DPAD_RIGHT },
+ { EventCode.EV_KEY, EventCode.BTN_TL2 },
+ { EventCode.EV_KEY, EventCode.BTN_TR2 },
+ };
+
+ private const GamepadInputPath[] KEYBOARD_GAMEPAD_INPUT_PATHS = {
+ { { EventCode.EV_KEY, EventCode.BTN_A }, "a" },
+ { { EventCode.EV_KEY, EventCode.BTN_B }, "b" },
+ { { EventCode.EV_KEY, EventCode.BTN_DPAD_DOWN }, "dpdown" },
+ { { EventCode.EV_KEY, EventCode.BTN_DPAD_LEFT }, "dpleft" },
+ { { EventCode.EV_KEY, EventCode.BTN_DPAD_RIGHT }, "dpright" },
+ { { EventCode.EV_KEY, EventCode.BTN_DPAD_UP }, "dpup" },
+ { { EventCode.EV_KEY, EventCode.BTN_SELECT }, "back" },
+ { { EventCode.EV_KEY, EventCode.BTN_TL }, "leftshoulder" },
+ { { EventCode.EV_KEY, EventCode.BTN_TR }, "rightshoulder" },
+ { { EventCode.EV_KEY, EventCode.BTN_START }, "start" },
+ { { EventCode.EV_KEY, EventCode.BTN_THUMBL }, "leftstick" },
+ { { EventCode.EV_KEY, EventCode.BTN_THUMBR }, "rightstick" },
+ { { EventCode.EV_KEY, EventCode.BTN_TL2 }, "lefttrigger" },
+ { { EventCode.EV_KEY, EventCode.BTN_TR2 }, "righttrigger" },
+ { { EventCode.EV_KEY, EventCode.BTN_Y }, "x" },
+ { { EventCode.EV_KEY, EventCode.BTN_X }, "y" },
+ };
+
+ private const GamepadViewConfiguration KEYBOARD_GAMEPAD_VIEW_CONFIGURATION = {
+ "resource:///org/gnome/Games/gamepads/standard-gamepad.svg", KEYBOARD_GAMEPAD_INPUT_PATHS
+ };
+
+ private enum State {
+ TEST,
+ CONFIGURE,
+ }
+
+ public signal void back ();
+
+ private State _state;
+ private State state {
+ set {
+ _state = value;
+ immersive_mode = (state == State.CONFIGURE);
+
+ switch (value) {
+ case State.TEST:
+ reset_button.set_sensitive (!mapping_manager.is_default_mapping);
+
+ back_button.show ();
+ cancel_button.hide ();
+ action_bar.show ();
+ header_bar.title = _("Testing Keyboard");
+ header_bar.get_style_context ().remove_class ("selection-mode");
+ stack.set_visible_child_name ("keyboard_tester");
+
+ tester.start ();
+ mapper.stop ();
+ mapper.finished.disconnect (on_mapper_finished);
+
+ break;
+ case State.CONFIGURE:
+ back_button.hide ();
+ cancel_button.show ();
+ action_bar.hide ();
+ header_bar.title = _("Configuring Keyboard");
+ header_bar.get_style_context ().add_class ("selection-mode");
+ stack.set_visible_child_name ("keyboard_mapper");
+
+ tester.stop ();
+ mapper.start ();
+ mapper.finished.connect (on_mapper_finished);
+
+ break;
+ }
+ }
+ get { return _state; }
+ }
+
+ [GtkChild (name = "header_bar")]
+ private Gtk.HeaderBar _header_bar;
+ public Gtk.HeaderBar header_bar {
+ private set {}
+ get { return _header_bar; }
+ }
+
+ public bool immersive_mode { private set; get; }
+
+ [GtkChild]
+ private Gtk.Stack stack;
+ [GtkChild]
+ private Gtk.Box keyboard_mapper_holder;
+ [GtkChild]
+ private Gtk.Box keyboard_tester_holder;
+ [GtkChild]
+ private Gtk.ActionBar action_bar;
+ [GtkChild]
+ private Gtk.Button reset_button;
+ [GtkChild]
+ private Gtk.Button back_button;
+ [GtkChild]
+ private Gtk.Button cancel_button;
+
+ private Retro.VirtualGamepad keyboard;
+ private KeyboardMapper mapper;
+ private KeyboardTester tester;
+ private KeyboardMappingManager mapping_manager;
+
+ construct {
+ mapping_manager = KeyboardMappingManager.get_instance ();
+ mapping_manager.notify["mapping"].connect (set_keyboard);
+ }
+
+ public KeyboardConfigurer () {
+ mapper = new KeyboardMapper (KEYBOARD_GAMEPAD_VIEW_CONFIGURATION, KEYBOARD_GAMEPAD_INPUTS);
+ keyboard_mapper_holder.pack_start (mapper);
+ tester = new KeyboardTester (KEYBOARD_GAMEPAD_VIEW_CONFIGURATION);
+ keyboard_tester_holder.pack_start (tester);
+ set_keyboard ();
+
+ state = State.TEST;
+ }
+
+ private void set_keyboard () {
+ keyboard = new Retro.VirtualGamepad.with_configuration (this, mapping_manager.mapping);
+ mapper.keyboard = keyboard;
+ tester.keyboard = keyboard;
+ }
+
+ [GtkCallback]
+ private void on_reset_clicked () {
+ reset_mapping ();
+ }
+
+ [GtkCallback]
+ private void on_configure_clicked () {
+ state = State.CONFIGURE;
+ }
+
+ [GtkCallback]
+ private void on_back_clicked () {
+ back ();
+ }
+
+ [GtkCallback]
+ private void on_cancel_clicked () {
+ state = State.TEST;
+ }
+
+ private void reset_mapping () {
+ var message_dialog = new ResetGamepadMappingDialog ();
+ message_dialog.set_transient_for ((Gtk.Window) get_toplevel ());
+ message_dialog.response.connect ((response) => {
+ switch (response) {
+ case Gtk.ResponseType.ACCEPT:
+ mapping_manager.reset_mapping ();
+ mapping_manager.delete_mapping ();
+ reset_button.set_sensitive (false);
+
+ break;
+ default:
+ break;
+ }
+
+ message_dialog.destroy();
+ });
+ message_dialog.show ();
+ }
+
+ private void on_mapper_finished (Retro.GamepadConfiguration mapping) {
+ mapping_manager.mapping = mapping;
+ mapping_manager.save_mapping ();
+
+ state = State.TEST;
+ }
+}
diff --git a/src/ui/keyboard-mapper.vala b/src/ui/keyboard-mapper.vala
new file mode 100644
index 0000000..68d5d55
--- /dev/null
+++ b/src/ui/keyboard-mapper.vala
@@ -0,0 +1,76 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/keyboard-mapper.ui")]
+private class Games.KeyboardMapper : Gtk.Box {
+ public signal void finished (Retro.GamepadConfiguration mapping);
+
+ [GtkChild]
+ private GamepadView gamepad_view;
+ [GtkChild]
+ private Gtk.Label info_message;
+
+ public Retro.VirtualGamepad keyboard { get; set; }
+ private KeyboardMappingBuilder mapping_builder;
+ private GamepadInput[] mapping_inputs;
+ private GamepadInput input;
+ private uint current_input_index;
+
+ construct {
+ info_message.label = _("Press suitable key on your keyboard");
+ mapping_builder = new KeyboardMappingBuilder ();
+ }
+
+ public KeyboardMapper (GamepadViewConfiguration configuration, GamepadInput[] mapping_inputs) {
+ this.mapping_inputs = mapping_inputs;
+ try {
+ gamepad_view.set_configuration (configuration);
+ }
+ catch (Error e) {
+ critical ("Could not set up gamepad view: %s", e.message);
+ }
+ }
+
+ public void start () {
+ current_input_index = 0;
+ connect_to_keyboard ();
+
+ next_input ();
+ }
+
+ public void stop () {
+ disconnect_from_keyboard ();
+ }
+
+ [GtkCallback]
+ private void on_skip_clicked () {
+ next_input ();
+ }
+
+ private void connect_to_keyboard () {
+ keyboard.widget.key_release_event.connect (on_keyboard_event);
+ }
+
+ private void disconnect_from_keyboard () {
+ keyboard.widget.key_release_event.disconnect (on_keyboard_event);
+ }
+
+ private bool on_keyboard_event (Gdk.EventKey key) {
+ var success = mapping_builder.set_input_mapping (input, key.hardware_keycode);
+ if (success)
+ next_input ();
+
+ return true;
+ }
+
+ private void next_input () {
+ if (current_input_index == mapping_inputs.length) {
+ finished (mapping_builder.mapping);
+
+ return;
+ }
+
+ gamepad_view.reset ();
+ input = mapping_inputs[current_input_index++];
+ gamepad_view.highlight (input, true);
+ }
+}
diff --git a/src/ui/keyboard-tester.vala b/src/ui/keyboard-tester.vala
new file mode 100644
index 0000000..c409308
--- /dev/null
+++ b/src/ui/keyboard-tester.vala
@@ -0,0 +1,43 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/keyboard-tester.ui")]
+private class Games.KeyboardTester : Gtk.Box {
+ [GtkChild]
+ private GamepadView gamepad_view;
+
+ public Retro.VirtualGamepad keyboard { get; set; }
+
+ private uint update_gamepad_view_timeout_id;
+
+ public KeyboardTester (GamepadViewConfiguration configuration) {
+ try {
+ gamepad_view.set_configuration (configuration);
+ }
+ catch (Error e) {
+ critical ("Could not set up gamepad view: %s", e.message);
+ }
+ }
+
+ public void start () {
+ gamepad_view.reset ();
+ update_gamepad_view_timeout_id = GLib.Timeout.add (10, update_gamepad_view);
+ }
+
+ private bool update_gamepad_view () {
+ if (gamepad_view == null)
+ return false;
+
+ for (var i = 0; i < Retro.JoypadId.COUNT; ++i) {
+ var joypad_id = (Retro.JoypadId) i;
+ var highlight = keyboard.get_button_pressed (joypad_id);
+ var code = KeyboardMappingBuilder.event_code_from_joypad_id (joypad_id);
+ gamepad_view.highlight ({ EventCode.EV_KEY, code }, highlight);
+ }
+
+ return true;
+ }
+
+ public void stop () {
+ Source.remove (update_gamepad_view_timeout_id);
+ }
+}
diff --git a/src/ui/preferences-page-controllers.vala b/src/ui/preferences-page-controllers.vala
index 4f619a9..4afb4d0 100644
--- a/src/ui/preferences-page-controllers.vala
+++ b/src/ui/preferences-page-controllers.vala
@@ -8,6 +8,8 @@ private class Games.PreferencesPageControllers: Gtk.Stack, PreferencesPage {
[GtkChild]
private Gtk.ListBox gamepads_list_box;
[GtkChild]
+ private Gtk.ListBox keyboard_list_box;
+ [GtkChild]
private Gtk.Box extra_stack_child_holder;
[GtkChild]
private Gtk.HeaderBar default_header_bar;
@@ -26,6 +28,7 @@ private class Games.PreferencesPageControllers: Gtk.Stack, PreferencesPage {
gamepad_monitor.gamepad_unplugged.connect (rebuild_gamepad_list);
gamepad_monitor.gamepad_plugged.connect (rebuild_gamepad_list);
build_gamepad_list ();
+ build_keyboard_list ();
}
public void visible_page_changed () {
@@ -75,6 +78,26 @@ private class Games.PreferencesPageControllers: Gtk.Stack, PreferencesPage {
set_visible_child_name ("extra_stack_child");
}
+ private void build_keyboard_list () {
+ var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ box.pack_start (new Gtk.Label ("Keyboard"), false, false);
+ box.margin = 6;
+ box.show_all ();
+ keyboard_list_box.add (box);
+ }
+
+ [GtkCallback]
+ private void keyboard_list_box_row_activated (Gtk.ListBoxRow row_item) {
+ var configurer = new KeyboardConfigurer();
+ back_handler_id = configurer.back.connect (on_back);
+ header_bar_binding = configurer.bind_property ("header-bar", this, "header-bar",
+ BindingFlags.SYNC_CREATE);
+ immersive_mode_binding = configurer.bind_property ("immersive-mode", this, "immersive-mode",
+ BindingFlags.SYNC_CREATE);
+ extra_stack_child_holder.pack_start (configurer);
+ set_visible_child_name ("extra_stack_child");
+ }
+
private void on_back (Object? emitter) {
header_bar_binding = null;
immersive_mode_binding = null;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]