-H flag added for headless mode. Requires profile name without the .lyt extension.
This commit is contained in:
@ -260,6 +260,7 @@ Layout files use standard X11 keycodes and are human-readable. You can edit them
|
||||
- `-t, --tray`: Use system tray icon (instead of default window mode)
|
||||
- `-T, --notray`: Use window mode (default behavior)
|
||||
- `-u, --update`: Update device list in running instance
|
||||
- `-H, --headless=LAYOUT`: Run in headless mode with no GUI, loading the specified layout
|
||||
|
||||
**Layout Name:** Load the specified layout on startup
|
||||
|
||||
|
@ -3,9 +3,27 @@
|
||||
#include <X11/extensions/XTest.h>
|
||||
#include "event.h"
|
||||
|
||||
//persistent X11 display connection to avoid opening/closing for each event
|
||||
static Display* g_display = nullptr;
|
||||
|
||||
static Display* getDisplay() {
|
||||
if (!g_display) {
|
||||
g_display = XOpenDisplay(nullptr);
|
||||
}
|
||||
return g_display;
|
||||
}
|
||||
|
||||
//cleanup function called at application exit
|
||||
void cleanupDisplay() {
|
||||
if (g_display) {
|
||||
XCloseDisplay(g_display);
|
||||
g_display = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//actually creates an XWindows event :)
|
||||
void sendevent(const FakeEvent &e) {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
Display* display = getDisplay();
|
||||
if (!display) return;
|
||||
|
||||
switch (e.type) {
|
||||
@ -48,5 +66,4 @@ void sendevent(const FakeEvent &e) {
|
||||
break;
|
||||
}
|
||||
XFlush(display);
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
@ -22,5 +22,6 @@ struct FakeEvent {
|
||||
};
|
||||
|
||||
void sendevent(const FakeEvent& e);
|
||||
void cleanupDisplay();
|
||||
|
||||
#endif
|
||||
|
@ -8,8 +8,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
JoyPad::JoyPad( int i, int dev, QObject *parent )
|
||||
@ -96,7 +96,7 @@ void JoyPad::open(int dev) {
|
||||
readNotifier = new QSocketNotifier(joydev, QSocketNotifier::Read, this);
|
||||
connect(readNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
|
||||
errorNotifier = new QSocketNotifier(joydev, QSocketNotifier::Exception, this);
|
||||
connect(errorNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
|
||||
connect(errorNotifier, SIGNAL(activated(int)), this, SLOT(errorRead()));
|
||||
debug_mesg("Done setting up joyDeviceListeners\n");
|
||||
debug_mesg("done resetting to dev\n");
|
||||
}
|
||||
@ -259,6 +259,18 @@ void JoyPad::handleJoyEvents() {
|
||||
if (len == sizeof(js_event)) {
|
||||
//pass that event on to the joypad!
|
||||
jsevent(msg);
|
||||
} else if (len < 0) {
|
||||
//handle read errors
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
//normal for non-blocking read when no data available
|
||||
return;
|
||||
}
|
||||
//device error - trigger error handling
|
||||
debug_mesg("Read error on joystick device fd %d: %s\n", joydev, strerror(errno));
|
||||
errorRead();
|
||||
} else if (len > 0) {
|
||||
//partial read - should not happen with joystick events
|
||||
debug_mesg("Warning: partial read (%zd bytes) from joystick device fd %d\n", len, joydev);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,14 @@
|
||||
|
||||
|
||||
//initialize things and set up an icon :)
|
||||
LayoutManager::LayoutManager( bool useTrayIcon, const QString &devdir, const QString &settingsDir )
|
||||
LayoutManager::LayoutManager( bool useTrayIcon, const QString &devdir, const QString &settingsDir, bool headless )
|
||||
: devdir(devdir), settingsDir(settingsDir),
|
||||
layoutGroup(new QActionGroup(this)),
|
||||
updateDevicesAction(new QAction(QIcon::fromTheme("view-refresh"),"Update &Joystick Devices",this)),
|
||||
updateLayoutsAction(new QAction(QIcon::fromTheme("view-refresh"),"Update &Layout List",this)),
|
||||
addNewConfiguration(new QAction(QIcon::fromTheme("list-add"),"Add new configuration",this)),
|
||||
quitAction(new QAction(QIcon::fromTheme("application-exit"),"&Quit",this)),
|
||||
le(0), isNotrayMode(!useTrayIcon) {
|
||||
layoutGroup(headless ? 0 : new QActionGroup(this)),
|
||||
updateDevicesAction(headless ? 0 : new QAction(QIcon::fromTheme("view-refresh"),"Update &Joystick Devices",this)),
|
||||
updateLayoutsAction(headless ? 0 : new QAction(QIcon::fromTheme("view-refresh"),"Update &Layout List",this)),
|
||||
addNewConfiguration(headless ? 0 : new QAction(QIcon::fromTheme("list-add"),"Add new configuration",this)),
|
||||
quitAction(headless ? 0 : new QAction(QIcon::fromTheme("application-exit"),"&Quit",this)),
|
||||
le(0), isNotrayMode(!useTrayIcon), isHeadless(headless) {
|
||||
|
||||
#ifdef WITH_LIBUDEV
|
||||
udevNotifier = 0;
|
||||
@ -31,6 +31,8 @@ LayoutManager::LayoutManager( bool useTrayIcon, const QString &devdir, const QSt
|
||||
}
|
||||
#endif
|
||||
|
||||
//skip GUI initialization in headless mode
|
||||
if (!headless) {
|
||||
//prepare the popup first.
|
||||
fillPopup();
|
||||
|
||||
@ -52,6 +54,7 @@ LayoutManager::LayoutManager( bool useTrayIcon, const QString &devdir, const QSt
|
||||
connect(updateDevicesAction, SIGNAL(triggered()), this, SLOT(updateJoyDevs()));
|
||||
connect(addNewConfiguration, SIGNAL(triggered()), this, SLOT(addNewConfig()));
|
||||
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
|
||||
}
|
||||
|
||||
//no layout loaded at start.
|
||||
setLayoutName(QString());
|
||||
@ -146,7 +149,9 @@ void LayoutManager::udevUpdate() {
|
||||
addJoyPad(index, path);
|
||||
}
|
||||
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
if (le) {
|
||||
le->updateJoypadWidgets();
|
||||
}
|
||||
@ -359,7 +364,9 @@ void LayoutManager::saveAs() {
|
||||
save(file);
|
||||
|
||||
//add the new name to our lists
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
if (le) {
|
||||
le->updateLayoutList();
|
||||
}
|
||||
@ -402,7 +409,9 @@ void LayoutManager::importLayout() {
|
||||
}
|
||||
QFile::copy(sourceFile, filename);
|
||||
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
if (le) {
|
||||
le->updateLayoutList();
|
||||
}
|
||||
@ -444,7 +453,9 @@ void LayoutManager::remove() {
|
||||
if (!QFile(filename).remove()) {
|
||||
errorBox(tr("Remove error"), tr("Could not remove file %1").arg(filename), le);
|
||||
}
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
|
||||
if (le) {
|
||||
le->updateLayoutList();
|
||||
@ -483,7 +494,9 @@ void LayoutManager::rename() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
if (le) {
|
||||
le->updateLayoutList();
|
||||
}
|
||||
@ -504,6 +517,8 @@ QStringList LayoutManager::getLayoutNames() const {
|
||||
}
|
||||
|
||||
void LayoutManager::setLayoutName(const QString& name) {
|
||||
//skip GUI operations in headless mode
|
||||
if (layoutGroup) {
|
||||
QList<QAction*> actions = layoutGroup->actions();
|
||||
for (int i = 0; i < actions.size(); ++ i) {
|
||||
QAction* action = actions[i];
|
||||
@ -512,6 +527,7 @@ void LayoutManager::setLayoutName(const QString& name) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentLayout = name;
|
||||
|
||||
if (le) {
|
||||
@ -672,7 +688,9 @@ void LayoutManager::updateJoyDevs() {
|
||||
#endif
|
||||
//when it's all done, rebuild the popup menu so it displays the correct
|
||||
//information.
|
||||
if (!isHeadless) {
|
||||
fillPopup();
|
||||
}
|
||||
if (le) {
|
||||
le->updateJoypadWidgets();
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class LayoutManager : public QObject {
|
||||
friend class LayoutEdit;
|
||||
Q_OBJECT
|
||||
public:
|
||||
LayoutManager(bool useTrayIcon, const QString &devdir, const QString &settingsDir);
|
||||
LayoutManager(bool useTrayIcon, const QString &devdir, const QString &settingsDir, bool headless = false);
|
||||
~LayoutManager();
|
||||
|
||||
//produces a list of the names of all the available layout.
|
||||
@ -111,6 +111,7 @@ class LayoutManager : public QObject {
|
||||
QHash<int, JoyPad*> available;
|
||||
QHash<int, JoyPad*> joypads;
|
||||
bool isNotrayMode;
|
||||
bool isHeadless;
|
||||
|
||||
#ifdef WITH_LIBUDEV
|
||||
bool initUDev();
|
||||
|
38
src/main.cpp
38
src/main.cpp
@ -73,6 +73,8 @@ int main( int argc, char **argv )
|
||||
//this execution wasn't made to update the joystick device list.
|
||||
bool update = false;
|
||||
bool forceTrayIcon = false;
|
||||
//headless mode - no GUI at all
|
||||
bool headless = false;
|
||||
|
||||
//parse command-line options
|
||||
struct option long_options[] = {
|
||||
@ -81,11 +83,12 @@ int main( int argc, char **argv )
|
||||
{"tray", no_argument, 0, 't'},
|
||||
{"notray", no_argument, 0, 'T'},
|
||||
{"update", no_argument, 0, 'u'},
|
||||
{"headless", required_argument, 0, 'H'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
int c = getopt_long(argc, argv, "hd:tTu", long_options, NULL);
|
||||
int c = getopt_long(argc, argv, "hd:tTuH:", long_options, NULL);
|
||||
|
||||
if (c == -1)
|
||||
break;
|
||||
@ -93,7 +96,7 @@ int main( int argc, char **argv )
|
||||
switch (c) {
|
||||
case 'h':
|
||||
printf("%s\n"
|
||||
"Usage: %s [--device=\"/device/path\"] [--tray|--notray] [\"layout name\"]\n"
|
||||
"Usage: %s [--device=\"/device/path\"] [--tray|--notray] [--headless=LAYOUT] [\"layout name\"]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -h, --help Print this help message.\n"
|
||||
@ -104,6 +107,8 @@ int main( int argc, char **argv )
|
||||
" -T, --notray Do not use a system tray icon (default).\n"
|
||||
" -u, --update Force a running instance of ThunderPad to update its\n"
|
||||
" list of devices and layouts.\n"
|
||||
" -H, --headless=LAYOUT Run in headless mode with no GUI, loading the\n"
|
||||
" specified layout for joystick-to-keyboard translation.\n"
|
||||
" \"layout name\" Load the given layout in an already running\n"
|
||||
" instance of ThunderPad, or start ThunderPad using the\n"
|
||||
" given layout.\n",
|
||||
@ -134,6 +139,11 @@ int main( int argc, char **argv )
|
||||
update = true;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
headless = true;
|
||||
layout = optarg;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
fprintf(stderr, "Illegal argument.\n"
|
||||
"See `%s --help` for more information\n", argc > 0 ? argv[0] : "thunderpad");
|
||||
@ -151,6 +161,14 @@ int main( int argc, char **argv )
|
||||
}
|
||||
}
|
||||
|
||||
//validate headless mode requirements
|
||||
if (headless && layout.isEmpty()) {
|
||||
fprintf(stderr, "Headless mode requires a layout to be specified.\n"
|
||||
"Use -H LAYOUT or --headless=LAYOUT\n"
|
||||
"See `%s --help` for more information\n", argc > 0 ? argv[0] : "thunderpad");
|
||||
return 1;
|
||||
}
|
||||
|
||||
//if the user specified a layout to use,
|
||||
if (!layout.isEmpty())
|
||||
{
|
||||
@ -169,7 +187,7 @@ int main( int argc, char **argv )
|
||||
|
||||
|
||||
//create a pid lock file.
|
||||
QFile pidFile( "/tmp/thunderpad.pid" );
|
||||
QFile pidFile( headless ? "/tmp/thunderpad-headless.pid" : "/tmp/thunderpad.pid" );
|
||||
//if that file already exists, then thunderpad is already running!
|
||||
if (pidFile.exists())
|
||||
{
|
||||
@ -185,10 +203,11 @@ int main( int argc, char **argv )
|
||||
//then prevent two instances from running at once.
|
||||
//however, if we are setting the layout or updating the device
|
||||
//list, this is not an error and we shouldn't make one!
|
||||
if (layout.isEmpty() && !update)
|
||||
//headless mode also allows multiple instances
|
||||
if (layout.isEmpty() && !update && !headless)
|
||||
errorBox("Instance Error",
|
||||
"There is already a running instance of ThunderPad; please close\nthe old instance before starting a new one.");
|
||||
else {
|
||||
else if (!headless) {
|
||||
//if one of these is the case, send the appropriate signal!
|
||||
if (update) {
|
||||
kill(pid,SIGUSR1);
|
||||
@ -197,8 +216,8 @@ int main( int argc, char **argv )
|
||||
kill(pid,SIGUSR2);
|
||||
}
|
||||
}
|
||||
//and quit. We don't need two instances.
|
||||
return 0;
|
||||
//and quit. We don't need two instances (unless headless).
|
||||
if (!headless) return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -223,7 +242,7 @@ int main( int argc, char **argv )
|
||||
}
|
||||
//create a new LayoutManager with a tray icon / floating icon, depending
|
||||
//on the user's request
|
||||
LayoutManager layoutManager(useTrayIcon,devdir,settingsDir);
|
||||
LayoutManager layoutManager(useTrayIcon,devdir,settingsDir,headless);
|
||||
layoutManagerPtr = &layoutManager;
|
||||
|
||||
//build the joystick device list for the first time,
|
||||
@ -246,6 +265,9 @@ int main( int argc, char **argv )
|
||||
//remove the lock file...
|
||||
pidFile.remove();
|
||||
|
||||
//cleanup X11 display connection
|
||||
cleanupDisplay();
|
||||
|
||||
//and terminate!
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user