Skip to content

Commit

Permalink
ui: Support changing controller/keyboard mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
antangelo committed Aug 21, 2023
1 parent edecb41 commit a33bec2
Show file tree
Hide file tree
Showing 16 changed files with 1,054 additions and 209 deletions.
81 changes: 81 additions & 0 deletions controller_binding_spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
enable_rumble:
type: bool
default: true
# steel_battalion_mapping:
# ...
controller_mapping:
a:
type: integer
default: 0
b:
type: integer
default: 1
x:
type: integer
default: 2
y:
type: integer
default: 3
back:
type: integer
default: 4
guide:
type: integer
default: 5
start:
type: integer
default: 6
lstick_btn:
type: integer
default: 7
rstick_btn:
type: integer
default: 8
lshoulder:
type: integer
default: 9
rshoulder:
type: integer
default: 10
dpad_up:
type: integer
default: 11
dpad_down:
type: integer
default: 12
dpad_left:
type: integer
default: 13
dpad_right:
type: integer
default: 14
axis_left_x:
type: integer
default: 0
axis_left_y:
type: integer
default: 1
axis_right_x:
type: integer
default: 2
axis_right_y:
type: integer
default: 3
axis_trigger_left:
type: integer
default: 4
axis_trigger_right:
type: integer
default: 5
invert_axis_left_x:
type: bool
default: false
invert_axis_left_y:
type: bool
default: false
invert_axis_right_x:
type: bool
default: false
invert_axis_right_y:
type: bool
default: false
2 changes: 1 addition & 1 deletion genconfig
17 changes: 15 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2900,20 +2900,33 @@ qemu_version = custom_target('qemu-version.h',
build_by_default: true,
build_always_stale: true)

genconfig_cmd = [
config_genconfig_cmd = [
python, files('genconfig/gen_config.py'),
meson.current_source_dir() / 'config_spec.yml', 'xemu-config.h'
]

xemu_config = custom_target('xemu-config.h',
output: 'xemu-config.h',
input: [ files('config_spec.yml') ],
command: genconfig_cmd,
command: config_genconfig_cmd,
depend_files: files('config_spec.yml'))

controller_genconfig_cmd = [
python, files('genconfig/gen_config.py'),
meson.current_source_dir() / 'controller_binding_spec.yml', 'xemu-controller-binding.h',
'--struct', 'controller_binding'
]

xemu_controller_binding = custom_target('xemu-controller-binding.h',
output: 'xemu-controller-binding.h',
input: [ files('controller_binding_spec.yml') ],
command: controller_genconfig_cmd,
depend_files: files('controller_binding_spec.yml'))

genh += qemu_version
genh += xemu_version
genh += xemu_config
genh += xemu_controller_binding

hxdep = []
hx_headers = [
Expand Down
2 changes: 2 additions & 0 deletions ui/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ xemu_ss.add(files(
'xemu-monitor.c',
'xemu-net.c',
'xemu-settings.cc',
'xemu-controllers.cc',
'xemu-toml.cc',

'xemu.c',
'xemu-data.c',
Expand Down
189 changes: 189 additions & 0 deletions ui/xemu-controllers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* xemu Controller Binding Management
*
* Copyright (C) 2020-2023 Matt Borgerson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "qemu/osdep.h"
#include "ui/xemu-toml.hh"
#include "xemu-controllers.h"
#include "xemu-settings.h"
#include <assert.h>
#include <locale.h>
#include <string>
#include <toml++/toml.h>

#define DEFINE_CONFIG_TREE
#include "xemu-controller-binding.h"

std::pair<bool, bool>
ControllerKeyboardRebindingMap::ConsumeRebindEvent(SDL_Event *event)
{
if (event->type == SDL_KEYDOWN) {
*(xemu_keyboard_scancode_map[table_row]) = event->key.keysym.scancode;

return { true, true };
}

return { false, false };
}
#include <iostream>

std::pair<bool, bool>
ControllerGamepadRebindingMap::ConsumeRebindEvent(SDL_Event *event)
{
if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
if (state->sdl_joystick_id == event->cdevice.which) {
return { false, true };
}
} else if (event->type == SDL_CONTROLLERBUTTONUP && table_row < 15 &&
seen_key_down) {
// Bind on controller up ensures the UI does not immediately respond
// once the new binding is applied

if (state->sdl_joystick_id != event->cbutton.which) {
return { false, false };
}

int *button_map[15] = {
&state->controller_map.controller_mapping.a,
&state->controller_map.controller_mapping.b,
&state->controller_map.controller_mapping.x,
&state->controller_map.controller_mapping.y,
&state->controller_map.controller_mapping.back,
&state->controller_map.controller_mapping.guide,
&state->controller_map.controller_mapping.start,
&state->controller_map.controller_mapping.lstick_btn,
&state->controller_map.controller_mapping.rstick_btn,
&state->controller_map.controller_mapping.lshoulder,
&state->controller_map.controller_mapping.rshoulder,
&state->controller_map.controller_mapping.dpad_up,
&state->controller_map.controller_mapping.dpad_down,
&state->controller_map.controller_mapping.dpad_left,
&state->controller_map.controller_mapping.dpad_right,
};

*(button_map[table_row]) = event->cbutton.button;

return { true, true };
} else if (event->type == SDL_CONTROLLERBUTTONDOWN && table_row < 15) {
// If we are rebinding with a controller, we should not consume the key
// up event from activating the button
seen_key_down = true;
} else if (event->type == SDL_CONTROLLERAXISMOTION && table_row >= 15) {
// FIXME: Allow face buttons to map to axes
if (state->sdl_joystick_id != event->caxis.which) {
return { false, false };
}

int *axis_map[6] = {
&state->controller_map.controller_mapping.axis_left_x,
&state->controller_map.controller_mapping.axis_left_y,
&state->controller_map.controller_mapping.axis_right_x,
&state->controller_map.controller_mapping.axis_right_y,
&state->controller_map.controller_mapping.axis_trigger_left,
&state->controller_map.controller_mapping.axis_trigger_right,
};

*(axis_map[table_row - 15]) = event->caxis.axis;

return { true, true };
}

return { false, false };
}

static const char *get_binding_path()
{
static const char *binding_path = nullptr;

if (!binding_path) {
binding_path =
g_strdup_printf("%s/bindings", xemu_settings_get_base_path());
qemu_mkdir(binding_path);
}

return binding_path;
}

void xemu_settings_load_controller_binding(const char *guid,
struct controller_binding *cb)
{
const char *base_path = get_binding_path();
controller_binding_tree.reset_to_defaults();

assert(strlen(guid) > 0);
char *binding_path = g_strdup_printf("%s/%s.toml", base_path, guid);

bool success = true;

auto err =
xemu_toml::load_toml_config(binding_path, controller_binding_tree);
g_free(binding_path);
controller_binding_tree.store_to_struct(cb);

if (err) {
success = false;
auto error_msg = std::visit(
xemu_toml::overloaded{
[](xemu_toml::file_not_found) {
return std::string("Binding file not found, starting with "
"default settings.\n");
},
[](xemu_toml::failed_to_open) {
return std::string("Failed to open binding file for "
"reading. Check permissions.\n");
},
[](xemu_toml::failed_to_read) {
return std::string("Failed to read binding file.\n");
},
[](toml::parse_error &err) {
std::ostringstream oss;
oss << "Error parsing config file at " << err.source().begin
<< ":\n"
<< " " << err.description() << "\n"
<< "Please fix the error or delete the file to "
"continue.\n";
return oss.str();
},
},
*err);

fprintf(stderr, "%s", error_msg.c_str());
}

if (!success) {
xemu_settings_save_controller_binding(guid, cb);
}
}

void xemu_settings_save_controller_binding(const char *guid,
struct controller_binding *cb)
{
const char *base_path = get_binding_path();

assert(strlen(guid) > 0);
char *binding_path = g_strdup_printf("%s/%s.toml", base_path, guid);

auto err =
xemu_toml::save_toml_config(binding_path, controller_binding_tree, cb);
g_free(binding_path);
if (err) {
fprintf(
stderr,
"Failed to open binding file for writing. Check permissions.\n");
}
}

0 comments on commit a33bec2

Please sign in to comment.