mkxp-sunshine/src/keybindings.cpp
2026-05-23 22:59:45 +03:00

295 lines
6.8 KiB
C++

/*
** keybindings.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2014 Jonas Kulla <Nyocurio@gmail.com>
**
** mkxp 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.
**
** mkxp 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 mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include "keybindings.h"
#include "config.h"
#include "util.h"
#include <stdio.h>
struct KbBindingData{
SDL_Scancode source;
Input::ButtonCode target;
void add(BDescVec &d) const{
SourceDesc src;
src.type = Key;
src.d.scan = source;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
};
struct JsBindingData{
int source;
Input::ButtonCode target;
void add(BDescVec &d) const{
SourceDesc src;
src.type = JButton;
src.d.jb = source;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
};
struct GcBindingData{
int source;
Input::ButtonCode target;
void add(BDescVec &d) const{
SourceDesc src;
src.type = CButton;
src.d.jb = source;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
};
/* Common */
static const KbBindingData defaultKbBindings[] ={
{ SDL_SCANCODE_LEFT, Input::Left },
{ SDL_SCANCODE_RIGHT, Input::Right },
{ SDL_SCANCODE_UP, Input::Up },
{ SDL_SCANCODE_DOWN, Input::Down },
{ SDL_SCANCODE_Z, Input::Action },
{ SDL_SCANCODE_SPACE, Input::Action },
{ SDL_SCANCODE_X, Input::Cancel },
{ SDL_SCANCODE_ESCAPE, Input::Cancel },
{ SDL_SCANCODE_A, Input::Menu },
{ SDL_SCANCODE_RETURN, Input::Menu },
{ SDL_SCANCODE_S, Input::Items },
{ SDL_SCANCODE_LSHIFT, Input::Run },
{ SDL_SCANCODE_C, Input::Deactivate },
{ SDL_SCANCODE_LCTRL, Input::DebugAction},
{ SDL_SCANCODE_Q, Input::L },
{ SDL_SCANCODE_W, Input::R },
};
static elementsN(defaultKbBindings);
static const GcBindingData defaultGcBindings[] ={
{ SDL_GAMEPAD_BUTTON_DPAD_LEFT, Input::Left },
{ SDL_GAMEPAD_BUTTON_DPAD_RIGHT, Input::Right },
{ SDL_GAMEPAD_BUTTON_DPAD_UP, Input::Up },
{ SDL_GAMEPAD_BUTTON_DPAD_DOWN, Input::Down },
{ SDL_GAMEPAD_BUTTON_SOUTH, Input::Action },
{ SDL_GAMEPAD_BUTTON_EAST, Input::Cancel },
{ SDL_GAMEPAD_BUTTON_WEST, Input::Run },
{ SDL_GAMEPAD_BUTTON_NORTH, Input::Items },
{ SDL_GAMEPAD_BUTTON_START, Input::Menu },
{ SDL_GAMEPAD_BUTTON_BACK, Input::Deactivate },
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, Input::L },
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, Input::R },
};
static elementsN(defaultGcBindings);
static void addGcAxisBinding(BDescVec &d, uint8_t axis, AxisDir dir, Input::ButtonCode target){
SourceDesc src;
src.type = CAxis;
src.d.ja.axis = axis;
src.d.ja.dir = dir;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
BDescVec genDefaultBindings(){
BDescVec d;
for (size_t i = 0; i < defaultKbBindingsN; ++i)
defaultKbBindings[i].add(d);
for (size_t i = 0; i < defaultGcBindingsN; ++i)
defaultGcBindings[i].add(d);
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_LEFTX, Negative, Input::Left );
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_LEFTX, Positive, Input::Right );
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_LEFTY, Negative, Input::Up );
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_LEFTY, Positive, Input::Down );
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, Positive, Input::Deactivate);
addGcAxisBinding(d, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, Positive, Input::Run );
return d;
}
#define FORMAT_VER 0
struct Header{
uint32_t formVer;
uint32_t count;
};
static void buildPath(const std::string &dir, char *out, size_t outSize){
snprintf(out, outSize, "%skeybindings.dat", dir.c_str());
}
static bool writeBindings(const BDescVec &d, const std::string &dir){
if (dir.empty())
return false;
char path[1024];
buildPath(dir, path, sizeof(path));
FILE *f = fopen(path, "wb");
if (!f)
return false;
Header hd;
hd.formVer = FORMAT_VER;
hd.count = d.size();
if (fwrite(&hd, sizeof(hd), 1, f) < 1){
fclose(f);
return false;
}
if (fwrite(&d[0], sizeof(d[0]), hd.count, f) < hd.count){
fclose(f);
return false;
}
fclose(f);
return true;
}
void storeBindings(const BDescVec &d, const Config &conf){
if (writeBindings(d, conf.customDataPath))
return;
writeBindings(d, conf.commonDataPath);
}
#define READ(ptr, size, n, f) if (fread(ptr, size, n, f) < n) return false
static bool verifyDesc(const BindingDesc &desc){
const Input::ButtonCode codes[] ={
Input::None,
Input::Down, Input::Left, Input::Right, Input::Up,
Input::Action,
Input::Cancel,
Input::Menu,
Input::Items,
Input::Run,
Input::Deactivate,
Input::DebugAction,
Input::L, Input::R,
Input::F5, Input::F6, Input::F7, Input::F8, Input::F9
};
elementsN(codes);
size_t i;
for (i = 0; i < codesN; ++i)
if (desc.target == codes[i])
break;
if (i == codesN)
return false;
const SourceDesc &src = desc.src;
switch (src.type){
case Invalid:
return true;
case Key:
return src.d.scan < SDL_SCANCODE_COUNT;
case CButton:
case JButton:
return true;
case JHat:
/* Only accept single directional binds */
return src.d.jh.pos == SDL_HAT_LEFT || src.d.jh.pos == SDL_HAT_RIGHT ||
src.d.jh.pos == SDL_HAT_UP || src.d.jh.pos == SDL_HAT_DOWN;
case CAxis:
case JAxis:
return src.d.ja.dir == Negative || src.d.ja.dir == Positive;
default:
return false;
}
}
static bool readBindings(BDescVec &out, const std::string &dir){
if (dir.empty())
return false;
char path[1024];
buildPath(dir, path, sizeof(path));
FILE *f = fopen(path, "rb");
if (!f)
return false;
Header hd;
if (fread(&hd, sizeof(hd), 1, f) < 1){
fclose(f);
return false;
}
if (hd.formVer != FORMAT_VER)
return false;
/* Arbitrary max value */
if (hd.count > 1024)
return false;
out.resize(hd.count);
if (fread(&out[0], sizeof(out[0]), hd.count, f) < hd.count){
fclose(f);
return false;
}
for (size_t i = 0; i < hd.count; ++i)
if (!verifyDesc(out[i]))
return false;
return true;
}
BDescVec loadBindings(const Config &conf){
BDescVec d;
if (readBindings(d, conf.customDataPath))
return d;
if (readBindings(d, conf.commonDataPath))
return d;
return genDefaultBindings();
}