Path: blob/master/Core/Debugger/WebSocket/InputSubscriber.cpp
3187 views
// Copyright (c) 2021- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <unordered_map>1920#include "Common/StringUtils.h"21#include "Core/Debugger/WebSocket/InputSubscriber.h"22#include "Core/Debugger/WebSocket/WebSocketUtils.h"23#include "Core/HLE/sceCtrl.h"24#include "Core/HW/Display.h"2526// This is also used in InputBroadcaster.27const std::unordered_map<std::string, uint32_t> buttonLookup = {28{ "cross", CTRL_CROSS },29{ "circle", CTRL_CIRCLE },30{ "triangle", CTRL_TRIANGLE },31{ "square", CTRL_SQUARE },32{ "up", CTRL_UP },33{ "down", CTRL_DOWN },34{ "left", CTRL_LEFT },35{ "right", CTRL_RIGHT },36{ "start", CTRL_START },37{ "select", CTRL_SELECT },38{ "home", CTRL_HOME },39{ "screen", CTRL_SCREEN },40{ "note", CTRL_NOTE },41{ "ltrigger", CTRL_LTRIGGER },42{ "rtrigger", CTRL_RTRIGGER },43{ "hold", CTRL_HOLD },44{ "wlan", CTRL_WLAN },45{ "remote_hold", CTRL_REMOTE_HOLD },46{ "vol_up", CTRL_VOL_UP },47{ "vol_down", CTRL_VOL_DOWN },48{ "disc", CTRL_DISC },49{ "memstick", CTRL_MEMSTICK },50{ "forward", CTRL_FORWARD },51{ "back", CTRL_BACK },52{ "playpause", CTRL_PLAYPAUSE },53// Obscure unmapped keys, see issue #1746454{ "l2", CTRL_L2 },55{ "l3", CTRL_L3 },56{ "r2", CTRL_R2 },57{ "r3", CTRL_R3 },58};5960struct WebSocketInputState : public DebuggerSubscriber {61void ButtonsSend(DebuggerRequest &req);62void ButtonsPress(DebuggerRequest &req);63void AnalogSend(DebuggerRequest &req);6465void Broadcast(net::WebSocketServer *ws) override;6667protected:68struct PressInfo {69std::string ticket;70uint32_t button;71uint32_t duration;7273std::string Event();74};7576std::vector<PressInfo> pressTickets_;77int lastCounter_ = -1;78};7980std::string WebSocketInputState::PressInfo::Event() {81JsonWriter j;82j.begin();83j.writeString("event", "input.buttons.press");84if (!ticket.empty()) {85j.writeRaw("ticket", ticket);86}87j.end();88return j.str();89}9091const std::unordered_map<std::string, uint32_t> &WebSocketInputButtonLookup() {92return buttonLookup;93}9495DebuggerSubscriber *WebSocketInputInit(DebuggerEventHandlerMap &map) {96auto p = new WebSocketInputState();97map["input.buttons.send"] = std::bind(&WebSocketInputState::ButtonsSend, p, std::placeholders::_1);98map["input.buttons.press"] = std::bind(&WebSocketInputState::ButtonsPress, p, std::placeholders::_1);99map["input.analog.send"] = std::bind(&WebSocketInputState::AnalogSend, p, std::placeholders::_1);100101return p;102}103104// Alter PSP button press flags (input.buttons.send)105//106// Parameters:107// - buttons: object containing button names as string keys, boolean press state as value.108//109// Button names (some are not respected by PPSSPP):110// - cross: button on bottom side of right pad.111// - circle: button on right side of right pad.112// - triangle: button on top side of right pad.113// - square: button on left side of right pad.114// - up: d-pad up button.115// - down: d-pad down button.116// - left: d-pad left button.117// - right: d-pad right button.118// - start: rightmost button at bottom of device.119// - select: second to the right at bottom of device.120// - home: leftmost button at bottom of device.121// - screen: brightness control button at bottom of device.122// - note: mute control button at bottom of device.123// - ltrigger: left shoulder trigger button.124// - rtrigger: right shoulder trigger button.125// - hold: hold setting of power switch.126// - wlan: wireless networking switch.127// - remote_hold: hold switch on headset.128// - vol_up: volume up button next to home at bottom of device.129// - vol_down: volume down button next to home at bottom of device.130// - disc: UMD disc sensor.131// - memstick: memory stick sensor.132// - forward: forward button on headset.133// - back: back button on headset.134// - playpause: play/pause button on headset.135//136// Response (same event name) with no extra data.137void WebSocketInputState::ButtonsSend(DebuggerRequest &req) {138const JsonNode *jsonButtons = req.data.get("buttons");139if (!jsonButtons) {140return req.Fail("Missing 'buttons' parameter");141}142if (jsonButtons->value.getTag() != JSON_OBJECT) {143return req.Fail("Invalid 'buttons' parameter type");144}145146uint32_t downFlags = 0;147uint32_t upFlags = 0;148149for (const JsonNode *button : jsonButtons->value) {150auto info = buttonLookup.find(button->key);151if (info == buttonLookup.end()) {152return req.Fail(StringFromFormat("Unsupported 'buttons' object key '%s'", button->key));153}154if (button->value.getTag() == JSON_TRUE) {155downFlags |= info->second;156} else if (button->value.getTag() == JSON_FALSE) {157upFlags |= info->second;158} else if (button->value.getTag() != JSON_NULL) {159return req.Fail(StringFromFormat("Unsupported 'buttons' object type for key '%s'", button->key));160}161}162163__CtrlUpdateButtons(downFlags, upFlags);164165req.Respond();166}167168// Press and release a button (input.buttons.press)169//170// Parameters:171// - button: required string indicating button name (see input.buttons.send.)172// - duration: optional integer indicating frames to press for, defaults to 1.173//174// Response (same event name) with no extra data once released.175void WebSocketInputState::ButtonsPress(DebuggerRequest &req) {176std::string button;177if (!req.ParamString("button", &button))178return;179180PressInfo press;181press.duration = 1;182if (!req.ParamU32("duration", &press.duration, false, DebuggerParamType::OPTIONAL))183return;184if ((int)press.duration < 0)185return req.Fail("Parameter 'duration' must not be negative");186const JsonNode *value = req.data.get("ticket");187press.ticket = value ? json_stringify(value) : "";188189auto info = buttonLookup.find(button);190if (info == buttonLookup.end()) {191return req.Fail(StringFromFormat("Unsupported button value '%s'", button.c_str()));192}193press.button = info->second;194195__CtrlUpdateButtons(press.button, 0);196pressTickets_.push_back(press);197}198199void WebSocketInputState::Broadcast(net::WebSocketServer *ws) {200int counter = __DisplayGetNumVblanks();201if (pressTickets_.empty() || lastCounter_ == counter)202return;203lastCounter_ = counter;204205for (PressInfo &press : pressTickets_) {206press.duration--;207if (press.duration == -1) {208__CtrlUpdateButtons(0, press.button);209ws->Send(press.Event());210}211}212auto negative = [](const PressInfo &press) -> bool {213return press.duration < 0;214};215pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), negative), pressTickets_.end());216}217218static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) {219const JsonNode *node = req.data.get(name);220if (!node) {221req.Fail(StringFromFormat("Missing '%s' parameter", name));222return false;223}224if (node->value.getTag() != JSON_NUMBER) {225req.Fail(StringFromFormat("Invalid '%s' parameter type", name));226return false;227}228229double val = node->value.toNumber();230if (val < -1.0 || val > 1.0) {231req.Fail(StringFromFormat("Parameter '%s' must be between -1.0 and 1.0", name));232return false;233}234235*value = (float)val;236return true;237}238239// Set coordinates of analog stick (input.analog.send)240//241// Parameters:242// - x: required number from -1.0 to 1.0.243// - y: required number from -1.0 to 1.0.244// - stick: optional string, either "left" (default) or "right".245//246// Response (same event name) with no extra data.247void WebSocketInputState::AnalogSend(DebuggerRequest &req) {248std::string stick = "left";249if (!req.ParamString("stick", &stick, DebuggerParamType::OPTIONAL))250return;251if (stick != "left" && stick != "right")252return req.Fail(StringFromFormat("Parameter 'stick' must be 'left' or 'right', not '%s'", stick.c_str()));253float x, y;254if (!AnalogValue(req, &x, "x") || !AnalogValue(req, &y, "y"))255return;256257// TODO: Route into the control mapper's PSPKey function or similar instead.258__CtrlSetAnalogXY(stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT, x, y);259260req.Respond();261}262263264