Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Ilari Liusvaara
32daef9154 Key broadcasting WIP 2014-03-19 18:02:01 +02:00
9 changed files with 522 additions and 19 deletions

View file

@ -0,0 +1,8 @@
#ifndef _keybroadcast__hpp__included__
#define _keybroadcast__hpp__included__
void keybroadcast_notify_foreground(bool fg);
void keybroadcast_set_master(uint16_t port);
void keybroadcast_set_slave(uint16_t port);
#endif

View file

@ -303,6 +303,11 @@ struct platform
* Parameter sync: If true, execute function call synchronously, else asynchronously.
*/
static void queue(void (*f)(void* arg), void* arg, bool sync) throw(std::bad_alloc);
/**
* Set handle to mirror all non-exclusive key events to.
*/
static void set_mirror_fn(std::function<void(keyboard::modifier_set& mods, keyboard::key& key,
keyboard::event& event)> fn);
/**
* Run all queues.
*/

View file

@ -54,6 +54,7 @@ public:
void load(const JSON::node& state);
JSON::node save();
std::string get_summary();
void master_enable(bool state);
private:
struct axis_info
{
@ -109,6 +110,7 @@ private:
unsigned next_hat;
unsigned jid;
mutex_class mutex;
bool enabled;
};
class set
@ -127,6 +129,7 @@ public:
void set_axismode_cb(std::function<void(unsigned jnum, unsigned num, int mode, double tolerance)> fn);
void set_newitem_cb(std::function<void(unsigned jnum, unsigned num, int type)> fn);
std::string get_summary();
void master_enable(bool state);
private:
set(const set&);
set& operator=(const set&);
@ -137,6 +140,7 @@ private:
std::function<void(unsigned jnum, unsigned num, int type)> newitem_fn;
std::vector<pad*> _gamepads;
mutex_class mutex;
bool enabled;
};
}

View file

@ -245,6 +245,10 @@ public:
* Stringify.
*/
operator std::string() const throw(std::bad_alloc);
/**
* Get internal set.
*/
std::set<modifier*> get_set() const throw(std::bad_alloc) { return set; }
/**
* Equality check.
*
@ -330,16 +334,22 @@ public:
*
* Parameter _chngmask: The change mask.
* Parameter _type: Type of the event.
* Parameter _raw: The raw event value.
*/
event(uint32_t _chngmask, keytype _type) throw()
event(uint32_t _chngmask, keytype _type, int32_t _raw) throw()
{
chngmask = _chngmask;
type = _type;
raw = _raw;
}
/**
* Destructor.
*/
virtual ~event() throw();
/**
* Get original raw state reported.
*/
int32_t get_raw() const throw() { return raw; }
/**
* Get analog state. The format is dependent on key type.
*/
@ -357,6 +367,7 @@ public:
private:
uint32_t chngmask;
keytype type;
int32_t raw;
};
/**
@ -369,8 +380,9 @@ public:
* Construct a new key event.
*
* Parameter chngmask: The change mask.
* Parameter _raw: The raw event value.
*/
event_key(uint32_t chngmask);
event_key(uint32_t chngmask, int32_t _raw);
/**
* Destructor.
*/
@ -397,8 +409,9 @@ public:
* Parameter state: The analog state.
* Parameter chngmask: The change mask.
* Parameter cal: The calibration structure.
* Parameter _raw: The raw event value.
*/
event_axis(int32_t state, uint32_t chngmask);
event_axis(int32_t state, uint32_t chngmask, int32_t _raw);
/**
* Destructor.
*/
@ -424,8 +437,9 @@ public:
* Construct a new hat event.
*
* Parameter chngmask: The change mask to use.
* Parameter _raw: The raw event value.
*/
event_hat(uint32_t chngmask);
event_hat(uint32_t chngmask, int32_t _raw);
/**
* Destructor.
*/
@ -449,8 +463,9 @@ public:
*
* Parameter state: The game-relative position to use.
* Parameter cal: The calibration structure.
* Parameter _raw: The raw event value.
*/
event_mouse(int32_t state, const mouse_calibration& cal);
event_mouse(int32_t state, const mouse_calibration& cal, int32_t _raw);
/**
* Destructor.
*/

393
src/core/keybroadcast.cpp Normal file
View file

@ -0,0 +1,393 @@
#include "core/window.hpp"
#include "core/keybroadcast.hpp"
#include "core/keymapper.hpp"
#include "library/string.hpp"
#include "library/minmax.hpp"
#include <sstream>
#if defined(_WIN32) || defined(_WIN64)
//Why the fuck does windows have nonstandard socket API???
#define _WIN32_WINNT 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
struct sockaddr_un { int sun_family; char sun_path[108]; };
#else
#include <sys/socket.h>
#include <netdb.h>
#include <sys/un.h>
#endif
namespace
{
#if defined(_WIN32) || defined(_WIN64)
typedef SOCKET sock_handle_t;
sock_handle_t invalid_socket = INVALID_SOCKET;
#else
typedef int sock_handle_t;
sock_handle_t invalid_socket = -1;
#endif
class k_link
{
public:
k_link(sock_handle_t handle)
{
link_handle = handle;
dead_flag = true;
}
~k_link()
{
low_close();
}
//Returns true if link has something to transmit, else false.
bool can_tx()
{
umutex_class h(lock);
return !msg_tx_queue.empty();
}
//Write some data to link. If can_tx() returned true, this will try to transmit something.
void handle_tx()
{
umutex_class h(lock);
if(dead_flag)
return;
if(msg_tx_queue.empty())
return;
const std::string& txmsg = msg_tx_queue.front();
char buf[512];
size_t totransmit = txmsg.length() - first_msg_tx_count;
if(totransmit > sizeof(buf))
totransmit = sizeof(buf);
size_t tocopy = totransmit;
if(totransmit < sizeof(buf))
buf[totransmit++] = '\0';
if(tocopy > 0)
std::copy(txmsg.begin() + first_msg_tx_count, txmsg.begin() + first_msg_tx_count +
tocopy, buf);
//Actually try to transmit. If entiere message is transmitted, remove it.
first_msg_tx_count += low_write(buf, totransmit);
if(first_msg_tx_count >= txmsg.length() + 1)
msg_tx_queue.pop_front();
}
//Read some data from link.
void handle_rx()
{
umutex_class h(lock);
if(dead_flag)
return;
char buf[512];
size_t recvd = low_read(buf, sizeof(buf));
size_t off = 0;
for(unsigned i = 0; i < recvd; i++) {
if(buf[i] == '\0') {
//End of message.
size_t off2 = partial_rx_msg.size();
partial_rx_msg.resize(off2 + i - off);
std::copy(buf + off, buf + i, partial_rx_msg.begin() + off2);
msg_rx_queue.push_back(std::string(partial_rx_msg.begin(),
partial_rx_msg.end()));
off = i + 1;
}
}
//Copy incomplete parts.
size_t off2 = partial_rx_msg.size();
partial_rx_msg.resize(off2 + recvd - off);
std::copy(buf + off, buf + recvd, partial_rx_msg.begin() + off2);
}
//Returns true if there is a pending message.
bool recv_ready()
{
umutex_class h(lock);
return !msg_rx_queue.empty();
}
//Read a pending message.
std::string recv()
{
umutex_class h(lock);
if(msg_rx_queue.empty())
return "";
std::string msg = msg_rx_queue.front();
msg_rx_queue.pop_front();
return msg;
}
//Send a message to link.
void send(const std::string& str)
{
umutex_class h(lock);
msg_tx_queue.push_back(str);
}
//Is dead?
bool dead()
{
return dead_flag;
}
//Get link handle.
sock_handle_t handle()
{
return link_handle;
}
private:
size_t low_read(char* buf, size_t maxread);
size_t low_write(const char* buf, size_t maxwrite);
void low_close();
k_link(const k_link&);
k_link& operator=(const k_link&);
sock_handle_t link_handle;
std::list<std::string> msg_rx_queue;
std::list<std::string> msg_tx_queue;
size_t first_msg_tx_count;
std::vector<char> partial_rx_msg;
bool dead_flag;
mutex_class lock;
};
k_link* client_link;
mutex_class client_link_lock;
cv_class client_link_change;
bool client_link_exit;
bool client_link_exited;
void slave_thread()
{
client_link_exited = false;
while(!client_link_exit) {
{
umutex_class h(client_link_lock);
if(!client_link);
}
}
client_link_exited = true;
}
class k_server
{
public:
//Fill the sets with available socket handles.
void poll_readyness(std::set<sock_handle_t>& rx, std::set<sock_handle_t>& tx)
{
for(auto i : links) {
//Always ready for RX.
rx.insert(i->handle());
if(i->can_tx()) tx.insert(i->handle());
}
}
//Given sockets with activity, do RX/TX cycle.
void do_rx_tx(std::set<sock_handle_t>& rx, std::set<sock_handle_t>& tx)
{
//Read/write all available sockets.
for(auto i : links) {
if(rx.count(i->handle()))
i->handle_rx();
if(tx.count(i->handle()))
i->handle_tx();
}
//Check for dead connections and close those.
for(auto i = links.begin(); i != links.end();) {
if((*i)->dead()) {
delete *i;
i = links.erase(i);
} else
i++;
}
redistribute_messages();
}
//Receive from all connections, broadcast the message.
void redistribute_messages()
{
//Receive on all connections, send on all other connections.
for(auto i : links) {
//Loop over all received messages.
while(i->recv_ready()) {
std::string msg = i->recv();
if(!msg.length()) continue; //Skip empty messages.
//Send to all others.
for(auto j : links) {
if(i == j) continue; //Don't send back.
j->send(msg);
}
}
}
}
//Accept a new connection.
void accept()
{
sock_handle_t h = low_accept();
if(h == invalid_socket)
return;
links.push_back(new k_link(h));
}
//Main poll loop.
void loop()
{
while(true) {
fd_set rset;
fd_set wset;
sock_handle_t bound = 0;
FD_ZERO(&rset);
FD_ZERO(&wset);
std::set<sock_handle_t> rx;
std::set<sock_handle_t> tx;
rx.insert(server_handle);
poll_readyness(rx, tx);
for(auto i : rx) FD_SET(i, &rset);
for(auto i : tx) FD_SET(i, &wset);
if(!rx.empty()) bound = max(bound, *rx.rbegin() + 1);
if(!tx.empty()) bound = max(bound, *tx.rbegin() + 1);
int r = select(bound, &rset, &wset, NULL, NULL);
for(auto i = rx.begin(); i != rx.end(); i++) {
if(FD_ISSET(*i, &rset))
i = rx.erase(i);
else
i++;
}
for(auto i = tx.begin(); i != tx.end(); i++) {
if(FD_ISSET(*i, &wset))
i = tx.erase(i);
else
i++;
}
do_rx_tx(rx, tx);
if(FD_ISSET(server_handle, &rset))
accept();
}
}
//Thread function for the server.
static int thread_main(void* x)
{
k_server* _x = reinterpret_cast<k_server*>(x);
_x->loop();
return 0;
}
private:
sock_handle_t low_accept();
std::list<k_link*> links;
sock_handle_t server_handle;
};
struct s_triple
{
s_triple();
s_triple(const std::string& r);
std::set<std::string> mods;
std::string key;
int32_t value;
bool ok;
operator std::string();
};
s_triple::s_triple() {}
s_triple::s_triple(const std::string& r)
{
ok = false;
size_t count;
size_t length;
char tmp;
std::vector<char> tmp3;
//size:{length:string}...length:string value
std::istringstream x(r);
x >> count;
if(!x) return; //Bad message.
x >> tmp;
for(size_t i = 0; i < count; i++) {
x >> length;
x >> tmp;
if(length > r.length())
return; //Bad message.
tmp3.resize(length);
x.read(&tmp3[0], length);
if(!x) return; //Bad message
std::string tmp2(tmp3.begin(), tmp3.end());
mods.insert(tmp2);
}
x >> length;
x >> tmp;
if(length > r.length())
return; //Bad message.
tmp3.resize(length);
x.read(&tmp3[0], length);
key = std::string(tmp3.begin(), tmp3.end());
if(!x) return; //Bad message
x >> value;
if(x) ok = true;
}
s_triple::operator std::string()
{
std::ostringstream x;
x << mods.size() << ":";
for(auto& i : mods)
x << i.length() << ":" << i;
x << key.length() << ":" << key;
x << value;
return x.str();
}
void event_handler(keyboard::modifier_set& mods, keyboard::key& key, keyboard::event& event)
{
if(!client_link)
return;
std::set<keyboard::modifier*> tmp = mods.get_set();
std::string _key = key.get_name();
int32_t value = event.get_raw();
std::set<std::string> _mods;
for(auto i : tmp)
_mods.insert(i->get_name());
s_triple s;
s.mods = _mods;
s.key = _key;
s.value = value;
std::string rep = s;
client_link->send(rep);
}
//Inject specified rep.
void inject_event(const std::string& rep)
{
s_triple s(rep);
if(!s.ok) {
messages << "Warning: inject_event: Skipping malformed message." << std::endl;
return;
}
keyboard::modifier_set _mods;
for(auto& i : s.mods) {
keyboard::modifier* m = lsnes_kbd.try_lookup_modifier(i);
if(m)
_mods.add(*m);
else
messages << "Warning: inject_event: Ignoring unknown modifier '" << i << "'"
<< std::endl;
}
keyboard::key* _key = lsnes_kbd.try_lookup_key(s.key);
if(!_key) {
messages << "Warning: inject_event: Skipping unknown key '" << s.key << "'"
<< std::endl;
return;
}
platform::queue(keypress(_mods, *_key, s.value));
}
int slave_thread(void* arg)
{
//TODO.
}
}
void keybroadcast_notify_foreground(bool fg)
{
if(client_link)
lsnes_gamepads.master_enable(!fg);
}
void keybroadcast_set_master(uint16_t port)
{
//TODO.
}
void keybroadcast_set_slave(uint16_t port)
{
//TODO.
}

View file

@ -135,6 +135,34 @@ namespace
std::vector<char> stream;
};
class keyboard_monitor : public keyboard::event_listener
{
public:
keyboard_monitor() throw()
{
active = false;
}
~keyboard_monitor() throw() {}
void on_key_event(keyboard::modifier_set& mods, keyboard::key& key, keyboard::event& event)
{
if(fn) fn(mods, key, event);
}
bool active;
std::function<void(keyboard::modifier_set& mods, keyboard::key& key, keyboard::event& event)> fn;
} keyboard_monitor_cb;
void monitor_all_keys()
{
for(auto i : lsnes_kbd.all_keys())
i->add_listener(keyboard_monitor_cb, true);
}
void unmonitor_all_keys()
{
for(auto i : lsnes_kbd.all_keys())
i->remove_listener(keyboard_monitor_cb);
}
class msgcallback : public messagebuffer::update_handler
{
public:
@ -503,6 +531,24 @@ void platform::queue(void (*f)(void* arg), void* arg, bool sync) throw(std::bad_
}
}
void platform::set_mirror_fn(std::function<void(keyboard::modifier_set& mods, keyboard::key& key,
keyboard::event& event)> fn)
{
if(fn) {
keyboard_monitor_cb.fn = fn;
if(!keyboard_monitor_cb.active)
for(auto i : lsnes_kbd.all_keys())
i->add_listener(keyboard_monitor_cb, true);
keyboard_monitor_cb.active = true;
} else {
if(keyboard_monitor_cb.active)
for(auto i : lsnes_kbd.all_keys())
i->remove_listener(keyboard_monitor_cb);
keyboard_monitor_cb.active = false;
keyboard_monitor_cb.fn = fn;
}
}
void platform::run_queues() throw()
{
internal_run_queues(false);

View file

@ -226,6 +226,10 @@ unsigned pad::add_hat(uint64_t idx, uint64_t idy, int64_t mindev, const std::str
void pad::report_axis(uint64_t id, int64_t val)
{
mutex.lock();
if(!enabled) {
mutex.unlock();
return;
}
if(_axes.count(id)) {
axis_info& i = _axes[id];
int16_t val2 = map_value(val, i.minus, i.zero, i.plus, i.neutral, i.pressure);
@ -258,6 +262,10 @@ void pad::report_axis(uint64_t id, int64_t val)
void pad::report_button(uint64_t id, bool val)
{
mutex.lock();
if(!enabled) {
mutex.unlock();
return;
}
if(!_buttons.count(id)) {
mutex.unlock();
return;
@ -275,6 +283,10 @@ void pad::report_button(uint64_t id, bool val)
void pad::report_hat(uint64_t id, int angle)
{
mutex.lock();
if(!enabled) {
mutex.unlock();
return;
}
unsigned h = angle_to_bitmask(angle);
if(!_hats.count(id)) {
mutex.unlock();
@ -623,8 +635,15 @@ std::string pad::get_summary()
return x.str();
}
void pad::master_enable(bool state)
{
umutex_class H(mutex);
enabled = state;
}
set::set()
{
enabled = true;
}
set::~set()
@ -711,6 +730,7 @@ unsigned set::add(const std::string& name)
gp->set_axismode_cb(amode_fn);
gp->set_newitem_cb(newitem_fn);
_gamepads.push_back(gp);
gp->master_enable(enabled);
return _gamepads.size() - 1;
} catch(...) {
delete gp;
@ -765,4 +785,13 @@ std::string set::get_summary()
x << i->get_summary();
return x.str();
}
void set::master_enable(bool state)
{
umutex_class h(mutex);
enabled = state;
for(auto i : _gamepads)
i->master_enable(state);
}
}

View file

@ -259,24 +259,24 @@ key::~key() throw()
register_queue<keyboard, key>::do_unregister(kbd, name);
}
event_key::event_key(uint32_t chngmask)
: event(chngmask, keytype::KBD_KEYTYPE_KEY)
event_key::event_key(uint32_t chngmask, int32_t _raw)
: event(chngmask, keytype::KBD_KEYTYPE_KEY, _raw)
{
}
event_axis::event_axis(int32_t _state, uint32_t chngmask)
: event(chngmask, keytype::KBD_KEYTYPE_AXIS)
event_axis::event_axis(int32_t _state, uint32_t chngmask, int32_t _raw)
: event(chngmask, keytype::KBD_KEYTYPE_AXIS, _raw)
{
state = _state;
}
event_hat::event_hat(uint32_t chngmask)
: event(chngmask, keytype::KBD_KEYTYPE_HAT)
event_hat::event_hat(uint32_t chngmask, int32_t _raw)
: event(chngmask, keytype::KBD_KEYTYPE_HAT, _raw)
{
}
event_mouse::event_mouse(int32_t _state, const mouse_calibration& _cal)
: event(0, keytype::KBD_KEYTYPE_MOUSE)
event_mouse::event_mouse(int32_t _state, const mouse_calibration& _cal, int32_t _raw)
: event(0, keytype::KBD_KEYTYPE_MOUSE, _raw)
{
state = _state;
cal = _cal;
@ -397,7 +397,7 @@ void key_key::set_state(modifier_set mods, int32_t _state) throw()
}
mutex.unlock();
if(edge) {
event_key e(change);
event_key e(change, _state);
call_listeners(mods, e);
}
}
@ -441,7 +441,7 @@ void key_hat::set_state(modifier_set mods, int32_t _state) throw()
}
mutex.unlock();
if(edge) {
event_hat e(change);
event_hat e(change, _state);
call_listeners(mods, e);
}
}
@ -534,7 +534,7 @@ void key_axis::set_state(modifier_set mods, int32_t _rawstate) throw()
}
mutex.unlock();
if(edge) {
event_axis e(state, change);
event_axis e(state, change, _rawstate);
call_listeners(mods, e);
}
}
@ -589,7 +589,7 @@ void key_mouse::set_state(modifier_set mods, int32_t _rawstate) throw()
}
mutex.unlock();
if(edge) {
event_mouse e(state, _cal);
event_mouse e(state, _cal, _rawstate);
call_listeners(mods, e);
}
}
@ -599,8 +599,9 @@ void key_mouse::set_calibration(mouse_calibration _cal) throw()
mutex.lock();
cal = _cal;
int32_t state = cal.get_calibrated_value(rawstate);
int32_t lraw = rawstate;
mutex.unlock();
event_mouse e(state, _cal);
event_mouse e(state, _cal, lraw);
modifier_set mods;
call_listeners(mods, e);
}

View file

@ -21,6 +21,7 @@
#include "core/dispatch.hpp"
#include "core/framebuffer.hpp"
#include "core/framerate.hpp"
#include "core/keybroadcast.hpp"
#include "core/keymapper.hpp"
#include "interface/romtype.hpp"
#include "core/loadlib.hpp"
@ -181,7 +182,7 @@ namespace
{
was_focused = (wxWindow::FindFocus() != NULL);
was_enabled = platform::is_sound_enabled();
Start(500);
Start(100);
}
void Notify()
{
@ -196,6 +197,7 @@ namespace
if(!background_audio)
platform::sound_enable(false);
}
keybroadcast_notify_foreground(is_focused);
was_focused = is_focused;
}
private: