Reimplemented support for naming devices
This commit is contained in:
parent
beed2d0861
commit
929bd68be8
14 changed files with 226 additions and 41 deletions
17
TODO
17
TODO
|
@ -42,6 +42,21 @@ $ dput my-ppa ../xboxdrv_0.7.1-1~lucid1_source.changes
|
|||
Stuff to do before 0.7.1 release:
|
||||
=================================
|
||||
|
||||
* implement set_dpad_as_button(), set_dpad_only()
|
||||
|
||||
* --name DEVNAME must work with multple controller slots and multiple
|
||||
devices, also allow a way to rename mouse and keyboard emulation devices
|
||||
|
||||
turn --name into --ui-name SLOT=foo
|
||||
|
||||
--ui-name 1=MouseEmulation:0500:0500,2=Keyboard,mouse=Mouse
|
||||
--ui-name 1=MouseEmulation,2=Keyboard,mouse=Mouse
|
||||
--ui-vendor =
|
||||
--ui-product
|
||||
--ui-bcdversion
|
||||
|
||||
* document match rules
|
||||
|
||||
* improve output in daemon mode, when --quiet is not given print the number of allocated controller slots
|
||||
|
||||
* [ERROR] XboxdrvDaemon::process_match(): no free controller slot found, controller will be ignored
|
||||
|
@ -120,8 +135,6 @@ Stuff to do before 0.7.2 release:
|
|||
|
||||
* extend usbid match rule to bcd device
|
||||
|
||||
* --name DEVNAME must work with multple controller slots and multiple devices
|
||||
|
||||
* cleanup device_id, don't manually do (slot<<16) | devid (only an
|
||||
issue in force feedback code)
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "ini_parser.hpp"
|
||||
#include "ini_schema_builder.hpp"
|
||||
#include "options.hpp"
|
||||
#include "ui_event.hpp"
|
||||
|
||||
#include "axisfilter/relative_axis_filter.hpp"
|
||||
#include "axisfilter/calibration_axis_filter.hpp"
|
||||
|
@ -67,6 +68,7 @@ enum {
|
|||
OPTION_BUTTONMAP,
|
||||
OPTION_AXISMAP,
|
||||
OPTION_NAME,
|
||||
OPTION_DEVICE_NAME,
|
||||
OPTION_NEXT_CONFIG,
|
||||
OPTION_NEXT_CONTROLLER,
|
||||
OPTION_CONFIG_SLOT,
|
||||
|
@ -271,7 +273,8 @@ CommandLineParser::init_argp()
|
|||
.add_text("Uinput Configuration Options: ")
|
||||
.add_option(OPTION_NO_UINPUT, 0, "no-uinput", "", "do not try to start uinput event dispatching")
|
||||
.add_option(OPTION_NO_EXTRA_DEVICES, 0, "no-extra-devices", "", "Do not create separate virtual keyboard and mouse devices, just use a single virtual device")
|
||||
.add_option(OPTION_NAME, 0, "name", "DEVNAME", "Changes the descriptive name the device will have")
|
||||
.add_option(OPTION_NAME, 0, "name", "NAME", "Changes the name prefix used for devices in the current slot")
|
||||
.add_option(OPTION_DEVICE_NAME, 0, "device-name", "DEVID=NAME", "Changes the descriptive name the given device")
|
||||
.add_option(OPTION_UI_CLEAR, 0, "ui-clear", "", "Removes all existing uinput bindings")
|
||||
.add_option(OPTION_UI_BUTTONMAP, 0, "ui-buttonmap", "MAP", "Changes the uinput events send when hitting a button (example: X=BTN_Y,A=KEY_A)")
|
||||
.add_option(OPTION_UI_AXISMAP, 0, "ui-axismap", "MAP", "Changes the uinput events send when moving a axis (example: X1=ABS_X2)")
|
||||
|
@ -609,8 +612,12 @@ CommandLineParser::parse_args(int argc, char** argv, Options* options)
|
|||
process_name_value_string(opt.argument, boost::bind(&CommandLineParser::set_axismap, this, _1, _2));
|
||||
break;
|
||||
|
||||
case OPTION_DEVICE_NAME:
|
||||
process_name_value_string(opt.argument, boost::bind(&CommandLineParser::set_device_name, this, _1, _2));
|
||||
break;
|
||||
|
||||
case OPTION_NAME:
|
||||
opts.get_controller_options().uinput.device_name = opt.argument;
|
||||
opts.set_device_name(opt.argument);
|
||||
break;
|
||||
|
||||
case OPTION_NEXT_CONFIG:
|
||||
|
@ -925,6 +932,36 @@ CommandLineParser::set_modifier(const std::string& name, const std::string& valu
|
|||
m_options->get_controller_options().modifier.push_back(ModifierPtr(Modifier::from_string(name, value)));
|
||||
}
|
||||
|
||||
void
|
||||
CommandLineParser::set_device_name(const std::string& name, const std::string& value)
|
||||
{
|
||||
// FIXME: insert magic to resolve symbolic names
|
||||
std::string::size_type p = name.find('.');
|
||||
if (p == std::string::npos)
|
||||
{
|
||||
uint16_t device_id = str2deviceid(name.substr());
|
||||
uint16_t slot_id = m_options->controller_slot;
|
||||
|
||||
uint32_t devid = UInput::create_device_id(slot_id, device_id);
|
||||
|
||||
m_options->uinput_device_names[devid] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint16_t device_id = str2deviceid(name.substr(0, p));
|
||||
uint16_t slot_id = str2slotid(name.substr(p+1));
|
||||
|
||||
if (slot_id == DEVICEID_AUTO)
|
||||
{
|
||||
slot_id = m_options->controller_slot;
|
||||
}
|
||||
|
||||
uint32_t devid = UInput::create_device_id(slot_id, device_id);
|
||||
|
||||
m_options->uinput_device_names[devid] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CommandLineParser::set_ui_buttonmap(const std::string& name, const std::string& value)
|
||||
{
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
void create_ini_schema(Options* opts);
|
||||
|
||||
private:
|
||||
void set_device_name(const std::string& name, const std::string& value);
|
||||
|
||||
void set_ui_buttonmap(const std::string& name, const std::string& value);
|
||||
void set_ui_axismap(const std::string& name, const std::string& value);
|
||||
void set_modifier(const std::string& name, const std::string& value);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "helper.hpp"
|
||||
#include "raise_exception.hpp"
|
||||
#include "uinput.hpp"
|
||||
|
||||
Options* g_options;
|
||||
|
||||
|
@ -68,7 +69,8 @@ Options::Options() :
|
|||
config_toggle_button(XBOX_BTN_UNKNOWN),
|
||||
controller_slot(0),
|
||||
config_slot(0),
|
||||
extra_devices(true)
|
||||
extra_devices(true),
|
||||
uinput_device_names()
|
||||
{
|
||||
// create the entry if not already available
|
||||
controller_slots[controller_slot].get_options(config_slot);
|
||||
|
@ -162,7 +164,8 @@ Options::set_debug()
|
|||
void
|
||||
Options::set_device_name(const std::string& name)
|
||||
{
|
||||
//get_controller().uinput.mouse();
|
||||
uint32_t device_id = UInput::create_device_id(controller_slot, DEVICEID_AUTO);
|
||||
uinput_device_names[device_id] = name;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -192,11 +195,13 @@ Options::set_trigger_as_zaxis()
|
|||
void
|
||||
Options::set_dpad_as_button()
|
||||
{
|
||||
// FIXME: implement me
|
||||
}
|
||||
|
||||
void
|
||||
Options::set_dpad_only()
|
||||
{
|
||||
// FIXME: implement me
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -116,6 +116,8 @@ public:
|
|||
|
||||
bool extra_devices;
|
||||
|
||||
std::map<uint32_t, std::string> uinput_device_names;
|
||||
|
||||
public:
|
||||
Options();
|
||||
|
||||
|
|
|
@ -143,8 +143,6 @@ UIEvent::get_device_id() const
|
|||
return UInput::create_device_id(m_slot_id, m_device_id);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int str2deviceid(const std::string& device)
|
||||
{
|
||||
if (device == "auto" || device.empty())
|
||||
|
@ -181,8 +179,6 @@ int str2slotid(const std::string& slot)
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void split_event_name(const std::string& str, std::string* event_str, int* slot_id, int* device_id)
|
||||
{
|
||||
std::string::size_type p = str.find('@');
|
||||
|
|
|
@ -68,6 +68,9 @@ private:
|
|||
*/
|
||||
void split_event_name(const std::string& str, std::string* event_str, int* slot_id, int* device_id);
|
||||
|
||||
int str2deviceid(const std::string& device);
|
||||
int str2slotid(const std::string& slot);
|
||||
|
||||
#endif
|
||||
|
||||
/* EOF */
|
||||
|
|
132
src/uinput.cpp
132
src/uinput.cpp
|
@ -21,12 +21,98 @@
|
|||
#include "log.hpp"
|
||||
|
||||
UInput::UInput() :
|
||||
uinput_devs(),
|
||||
rel_repeat_lst(),
|
||||
m_uinput_devs(),
|
||||
m_device_names(),
|
||||
m_rel_repeat_lst(),
|
||||
m_mutex()
|
||||
{
|
||||
}
|
||||
|
||||
std::string
|
||||
UInput::get_device_name(uint32_t device_id) const
|
||||
{
|
||||
uint16_t slot_id = get_slot_id(device_id);
|
||||
uint16_t type_id = get_type_id(device_id);
|
||||
|
||||
DeviceNames::const_iterator it = m_device_names.find(device_id);
|
||||
if (it != m_device_names.end())
|
||||
{
|
||||
// found an exact match, return it
|
||||
return it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
it = m_device_names.find(create_device_id(slot_id, DEVICEID_AUTO));
|
||||
if (it != m_device_names.end())
|
||||
{
|
||||
// found a match for the slot, build a name and return it
|
||||
std::ostringstream str;
|
||||
str << it->second;
|
||||
switch(type_id)
|
||||
{
|
||||
case DEVICEID_JOYSTICK:
|
||||
break;
|
||||
|
||||
case DEVICEID_MOUSE:
|
||||
str << " - Mouse Emulation";
|
||||
break;
|
||||
|
||||
case DEVICEID_KEYBOARD:
|
||||
str << " - Keyboard Emulation";
|
||||
break;
|
||||
|
||||
default:
|
||||
str << " - " << device_id+1;
|
||||
break;
|
||||
}
|
||||
return str.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
it = m_device_names.find(create_device_id(SLOTID_AUTO, type_id));
|
||||
|
||||
if (it != m_device_names.end())
|
||||
{
|
||||
// found match for the type, build name and return it
|
||||
std::ostringstream str;
|
||||
str << it->second;
|
||||
if (slot_id > 0)
|
||||
{
|
||||
str << " #" << slot_id;
|
||||
}
|
||||
return str.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream str;
|
||||
str << "Xbox Gamepad (userspace driver)";
|
||||
switch(type_id)
|
||||
{
|
||||
case DEVICEID_JOYSTICK:
|
||||
break;
|
||||
|
||||
case DEVICEID_MOUSE:
|
||||
str << " - Mouse Emulation";
|
||||
break;
|
||||
|
||||
case DEVICEID_KEYBOARD:
|
||||
str << " - Keyboard Emulation";
|
||||
break;
|
||||
|
||||
default:
|
||||
str << " - " << device_id+1;
|
||||
break;
|
||||
}
|
||||
if (slot_id > 0)
|
||||
{
|
||||
str << " #" << slot_id;
|
||||
}
|
||||
return str.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinuxUinput*
|
||||
UInput::create_uinput_device(uint32_t device_id)
|
||||
{
|
||||
|
@ -34,8 +120,8 @@ UInput::create_uinput_device(uint32_t device_id)
|
|||
// have called resolve_device_id()
|
||||
assert(device_id != DEVICEID_AUTO);
|
||||
|
||||
UInputDevs::iterator it = uinput_devs.find(device_id);
|
||||
if (it != uinput_devs.end())
|
||||
UInputDevs::iterator it = m_uinput_devs.find(device_id);
|
||||
if (it != m_uinput_devs.end())
|
||||
{
|
||||
// device already exist, so return it
|
||||
return it->second.get();
|
||||
|
@ -44,8 +130,6 @@ UInput::create_uinput_device(uint32_t device_id)
|
|||
{
|
||||
log_debug("create device: " << device_id);
|
||||
LinuxUinput::DeviceType device_type = LinuxUinput::kGenericDevice;
|
||||
std::ostringstream dev_name;
|
||||
dev_name << "Xbox Gamepad (userspace driver)";
|
||||
|
||||
switch (device_id)
|
||||
{
|
||||
|
@ -55,23 +139,21 @@ UInput::create_uinput_device(uint32_t device_id)
|
|||
|
||||
case DEVICEID_MOUSE:
|
||||
device_type = LinuxUinput::kMouseDevice;
|
||||
dev_name << " - Mouse Emulation";
|
||||
break;
|
||||
|
||||
case DEVICEID_KEYBOARD:
|
||||
device_type = LinuxUinput::kGenericDevice;
|
||||
dev_name << " - Keyboard Emulation";
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_name << " - " << device_id+1;
|
||||
break;
|
||||
}
|
||||
|
||||
boost::shared_ptr<LinuxUinput> dev(new LinuxUinput(device_type, dev_name.str(), 0x0000, 0x0000));
|
||||
uinput_devs.insert(std::pair<int, boost::shared_ptr<LinuxUinput> >(device_id, dev));
|
||||
std::string dev_name = get_device_name(device_id);
|
||||
boost::shared_ptr<LinuxUinput> dev(new LinuxUinput(device_type, dev_name, 0x0000, 0x0000));
|
||||
m_uinput_devs.insert(std::pair<int, boost::shared_ptr<LinuxUinput> >(device_id, dev));
|
||||
|
||||
log_debug("created uinput device: " << device_id << " - '" << dev_name.str() << "'");
|
||||
log_debug("created uinput device: " << device_id << " - '" << dev_name << "'");
|
||||
|
||||
return dev.get();
|
||||
}
|
||||
|
@ -112,7 +194,7 @@ UInput::add_ff(uint32_t device_id, uint16_t code)
|
|||
void
|
||||
UInput::finish()
|
||||
{
|
||||
for(UInputDevs::iterator i = uinput_devs.begin(); i != uinput_devs.end(); ++i)
|
||||
for(UInputDevs::iterator i = m_uinput_devs.begin(); i != m_uinput_devs.end(); ++i)
|
||||
{
|
||||
i->second->finish();
|
||||
}
|
||||
|
@ -142,7 +224,7 @@ UInput::send_key(uint32_t device_id, int ev_code, bool value)
|
|||
void
|
||||
UInput::update(int msec_delta)
|
||||
{
|
||||
for(std::map<UIEvent, RelRepeat>::iterator i = rel_repeat_lst.begin(); i != rel_repeat_lst.end(); ++i)
|
||||
for(std::map<UIEvent, RelRepeat>::iterator i = m_rel_repeat_lst.begin(); i != m_rel_repeat_lst.end(); ++i)
|
||||
{
|
||||
i->second.time_count += msec_delta;
|
||||
|
||||
|
@ -153,7 +235,7 @@ UInput::update(int msec_delta)
|
|||
}
|
||||
}
|
||||
|
||||
for(UInputDevs::iterator i = uinput_devs.begin(); i != uinput_devs.end(); ++i)
|
||||
for(UInputDevs::iterator i = m_uinput_devs.begin(); i != m_uinput_devs.end(); ++i)
|
||||
{
|
||||
i->second->update(msec_delta);
|
||||
}
|
||||
|
@ -162,7 +244,7 @@ UInput::update(int msec_delta)
|
|||
void
|
||||
UInput::sync()
|
||||
{
|
||||
for(UInputDevs::iterator i = uinput_devs.begin(); i != uinput_devs.end(); ++i)
|
||||
for(UInputDevs::iterator i = m_uinput_devs.begin(); i != m_uinput_devs.end(); ++i)
|
||||
{
|
||||
i->second->sync();
|
||||
}
|
||||
|
@ -173,21 +255,21 @@ UInput::send_rel_repetitive(const UIEvent& code, int value, int repeat_interval)
|
|||
{
|
||||
if (repeat_interval < 0)
|
||||
{ // remove rel_repeats from list
|
||||
rel_repeat_lst.erase(code);
|
||||
m_rel_repeat_lst.erase(code);
|
||||
// no need to send a event for rel, as it defaults to 0 anyway
|
||||
}
|
||||
else
|
||||
{ // add rel_repeats to list
|
||||
std::map<UIEvent, RelRepeat>::iterator it = rel_repeat_lst.find(code);
|
||||
std::map<UIEvent, RelRepeat>::iterator it = m_rel_repeat_lst.find(code);
|
||||
|
||||
if (it == rel_repeat_lst.end())
|
||||
if (it == m_rel_repeat_lst.end())
|
||||
{
|
||||
RelRepeat rel_rep;
|
||||
rel_rep.code = code;
|
||||
rel_rep.value = value;
|
||||
rel_rep.time_count = 0;
|
||||
rel_rep.repeat_interval = repeat_interval;
|
||||
rel_repeat_lst.insert(std::pair<UIEvent, RelRepeat>(code, rel_rep));
|
||||
m_rel_repeat_lst.insert(std::pair<UIEvent, RelRepeat>(code, rel_rep));
|
||||
|
||||
// Send the event once
|
||||
get_uinput(code.get_device_id())->send(EV_REL, code.code, value);
|
||||
|
@ -205,8 +287,8 @@ UInput::send_rel_repetitive(const UIEvent& code, int value, int repeat_interval)
|
|||
LinuxUinput*
|
||||
UInput::get_uinput(uint32_t device_id) const
|
||||
{
|
||||
UInputDevs::const_iterator it = uinput_devs.find(device_id);
|
||||
if (it != uinput_devs.end())
|
||||
UInputDevs::const_iterator it = m_uinput_devs.find(device_id);
|
||||
if (it != m_uinput_devs.end())
|
||||
{
|
||||
return it->second.get();
|
||||
}
|
||||
|
@ -219,6 +301,12 @@ UInput::get_uinput(uint32_t device_id) const
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
UInput::set_device_names(const std::map<uint32_t, std::string>& device_names)
|
||||
{
|
||||
m_device_names = device_names;
|
||||
}
|
||||
|
||||
void
|
||||
UInput::set_ff_callback(int device_id, const boost::function<void (uint8_t, uint8_t)>& callback)
|
||||
{
|
||||
|
|
|
@ -31,14 +31,27 @@ struct Xbox360GuitarMsg;
|
|||
class UInput
|
||||
{
|
||||
public:
|
||||
static inline uint32_t create_device_id(uint16_t slot_id, uint16_t device_id)
|
||||
static inline uint32_t create_device_id(uint16_t slot_id, uint16_t type_id)
|
||||
{
|
||||
return (slot_id << 16) | device_id;
|
||||
return (slot_id << 16) | type_id;
|
||||
}
|
||||
|
||||
static inline uint16_t get_type_id(uint32_t device_id)
|
||||
{
|
||||
return device_id & 0xffff;
|
||||
}
|
||||
|
||||
static inline uint16_t get_slot_id(uint32_t device_id)
|
||||
{
|
||||
return ((device_id) >> 16) & 0xffff;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::map<uint32_t, boost::shared_ptr<LinuxUinput> > UInputDevs;
|
||||
UInputDevs uinput_devs;
|
||||
UInputDevs m_uinput_devs;
|
||||
|
||||
typedef std::map<uint32_t, std::string> DeviceNames;
|
||||
DeviceNames m_device_names;
|
||||
|
||||
struct RelRepeat
|
||||
{
|
||||
|
@ -48,7 +61,7 @@ private:
|
|||
int repeat_interval;
|
||||
};
|
||||
|
||||
std::map<UIEvent, RelRepeat> rel_repeat_lst;
|
||||
std::map<UIEvent, RelRepeat> m_rel_repeat_lst;
|
||||
|
||||
boost::mutex m_mutex;
|
||||
|
||||
|
@ -58,6 +71,7 @@ public:
|
|||
|
||||
void update(int msec_delta);
|
||||
|
||||
void set_device_names(const std::map<uint32_t, std::string>& device_names);
|
||||
void set_ff_callback(int device_id, const boost::function<void (uint8_t, uint8_t)>& callback);
|
||||
|
||||
/** Device construction functions
|
||||
|
@ -93,6 +107,8 @@ private:
|
|||
|
||||
/** must only be called with a valid device_id */
|
||||
LinuxUinput* get_uinput(uint32_t device_id) const;
|
||||
|
||||
std::string get_device_name(uint32_t device_id) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <linux/input.h>
|
||||
|
||||
UInputOptions::UInputOptions() :
|
||||
device_name("Xbox Gamepad (userspace driver)"),
|
||||
m_btn_map(),
|
||||
m_axis_map()
|
||||
{
|
||||
|
@ -56,7 +55,8 @@ UInputOptions::get_axis_map() const
|
|||
void
|
||||
UInputOptions::mimic_xpad()
|
||||
{
|
||||
device_name = "Microsoft X-Box 360 pad";
|
||||
// FIXME: need to set this somewhere:
|
||||
// device_name = "Microsoft X-Box 360 pad";
|
||||
|
||||
get_btn_map().bind(XBOX_BTN_START, ButtonEvent::create_key(BTN_START));
|
||||
get_btn_map().bind(XBOX_BTN_GUIDE, ButtonEvent::create_key(BTN_MODE));
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
|
||||
class UInputOptions
|
||||
{
|
||||
public:
|
||||
std::string device_name;
|
||||
|
||||
private:
|
||||
ButtonMap m_btn_map;
|
||||
AxisMap m_axis_map;
|
||||
|
|
|
@ -443,6 +443,7 @@ Xboxdrv::run_main(const Options& opts)
|
|||
if (!opts.quiet)
|
||||
std::cout << "Starting with uinput" << std::endl;
|
||||
uinput = std::auto_ptr<UInput>(new UInput());
|
||||
uinput->set_device_names(opts.uinput_device_names);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -203,6 +203,7 @@ XboxdrvDaemon::init_uinput(const Options& opts)
|
|||
log_info("starting with UInput");
|
||||
|
||||
m_uinput.reset(new UInput());
|
||||
m_uinput->set_device_names(opts.uinput_device_names);
|
||||
|
||||
// create controller slots
|
||||
int slot_count = 0;
|
||||
|
|
24
test/xboxdrv-match-rules.sh
Executable file
24
test/xboxdrv-match-rules.sh
Executable file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Use flightstick config for regular Xbox360 controller and fighting game config for arcade stick"
|
||||
echo
|
||||
|
||||
./xboxdrv --daemon \
|
||||
`# regular Xbox360 controller gets flightstick threatment` \
|
||||
--name "Fightstick" \
|
||||
--match-group usbserial=13FEF2D \
|
||||
--trigger-as-zaxis --square-axis \
|
||||
--relative-axis y2=64000 \
|
||||
--axismap -y2=x2,x2=y2 \
|
||||
--next-controller \
|
||||
`# arcade stick gets fighting config` \
|
||||
--name "Xbox360 Controller" \
|
||||
--match-group usbserial=89E88EEF \
|
||||
--dpad-only \
|
||||
--trigger-as-button \
|
||||
--buttonmap lb=1,x=2,y=3,lt=4,a=5,b=6 \
|
||||
--buttonmap rb=1,rb=2,rb=3 \
|
||||
--buttonmap rt=4,rt=5,rt=6 \
|
||||
"$@"
|
||||
|
||||
# EOF #
|
Loading…
Add table
Reference in a new issue