udev-actudev-act is a small program you can run inside your X session to respond to usb udev events. I use it to run a script that configures my mouse buttons when I plug it in and also to start up the 3d printer control program when I plug in my 3d printer:
// The udev-act program to be run on a per user basis to execute
// commands when udev usb devices appear, disappear, etc.
//
#include <stdlib.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <unistd.h>
#include <libudev.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
using namespace std;
static const char * debug_file = NULL;
struct config_info {
config_info * next;
string action;
string usb_id;
string command;
};
struct config_info * config_list = NULL;
struct config_info ** config_end = &config_list;
// path under $HOME where the config file is located.
//
#define CONFIG_FILE_NAME "/.config/udev-act.conf"
static void
write_debug(string dbgstr) {
ofstream outfile;
outfile.open(debug_file, ios::app);
outfile << dbgstr;
outfile << endl;
}
// Read the config file and build config_list records
//
static void
read_config_file() {
char * homedir = getenv("HOME");
string cfile = homedir;
cfile.append(CONFIG_FILE_NAME);
ifstream s;
s.open(cfile);
if (s.is_open()) {
string line;
while (getline(s,line)) {
if (line[0] != '#') {
istringstream iss(line);
string act;
string id;
string prog;
if (iss >> act >> id >> prog) {
config_info * ci = new config_info;
ci->next = *config_end;
*config_end = ci;
config_end = &ci->next;
ci->action = act;
ci->usb_id = id;
ci->command = prog;
}
}
}
s.close();
} else {
cerr << "Missing config file.\n";
}
config_info * ci = config_list;
}
// Run a command using the double fork technique so I don't need to
// wait on kids.
//
static void
run_command(string command) {
pid_t kid = fork();
if (kid == 0) {
// I am the kid, fork again to do the exec then exit, let
// the system wait on the actual command, I won't have to.
pid_t grandkid = fork();
if (grandkid == 0) {
// Setup /dev/null as stdin, stdout, and stderr, close any
// other file descriptors that might be hanging around.
int null_out_fd = open("/dev/null", O_WRONLY);
int null_in_fd = open("/dev/null", O_RDONLY);
dup2(null_out_fd, 1 /* stdout */);
dup2(null_out_fd, 2 /* stderr */);
dup2(null_in_fd, 0 /* stdin */);
for (int i = 3; i < 60000; ++i) {
close(i);
}
// Now run the command
if (debug_file != NULL) {
string dbgstr;
dbgstr.append("In run_command with: ");
dbgstr.append(command);
write_debug(dbgstr);
}
execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL);
// And if that somehow didn't work, exit
_exit(2);
} else {
_exit(0);
}
} else {
// Wait on the kid to exit (which it should do almost
// immediately - as soon as it forks again).
int status;
waitpid(kid, &status, 0);
}
}
// Extract first line of file and append to str, return true if
// file existed and there was something appended, false otherwise
//
static bool
append_line(string file, string & str) {
bool rval = false;
ifstream s;
s.open(file);
if (s.is_open()) {
string line;
if (getline(s,line)) {
if (line.length() > 0) {
str.append(line);
rval = true;
}
}
s.close();
}
return rval;
}
// Monitor usb udev events and if they are associated with a path
// containing a vendor and product id, compare to the config records
// and run the commands given in the config. Loop forever doing this.
//
static void
monitor_events() {
struct udev *udev;
struct udev_device *dev;
struct udev_monitor *mon;
int fd;
udev = udev_new();
if (!udev) {
cerr << "Can't create udev\n";
return;
}
mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL);
udev_monitor_enable_receiving(mon);
fd = udev_monitor_get_fd(mon);
while (1) {
fd_set fds;
int ret;
FD_ZERO(&fds);
FD_SET(fd, &fds);
ret = select(fd+1, &fds, NULL, NULL, NULL);
if (ret > 0 && FD_ISSET(fd, &fds)) {
dev = udev_monitor_receive_device(mon);
if (dev) {
string dbgstr;
string curact = udev_device_get_action(dev);
string dirname = "/sys";
dirname.append(udev_device_get_devpath(dev));
udev_device_unref(dev);
string prodfile = dirname;
prodfile.append("/idProduct");
string vendfile = dirname;
vendfile.append("/idVendor");
if (debug_file != NULL) {
dbgstr.append("curact: ");
dbgstr.append(curact);
dbgstr.append(", dirname: ");
dbgstr.append(dirname);
}
string usbid;
if (append_line(vendfile, usbid)) {
if (debug_file != NULL) {
dbgstr.append(", vendor id: ");
dbgstr.append(usbid);
}
usbid.append(":");
if (append_line(prodfile, usbid)) {
if (debug_file != NULL) {
dbgstr.append(", product id: ");
dbgstr.append(usbid);
}
config_info * ci = config_list;
while (ci != NULL) {
if ((ci->action.compare(curact) == 0) &&
(ci->usb_id.compare(usbid) == 0)) {
if (debug_file != NULL) {
dbgstr.append(", run command: ");
dbgstr.append(ci->command);
}
run_command(ci->command);
}
ci = ci->next;
}
}
}
if (debug_file != NULL) {
write_debug(dbgstr);
}
}
}
}
udev_unref(udev);
}
int
main(int argc, char ** argv) {
if ((argc > 2) && (strcmp(argv[1], "-d") == 0)) {
debug_file = argv[2];
}
read_config_file();
monitor_events();
return 2;
}
Here's my config file as a sample: # Sample config file for udev-act user daemon # # When action shows up with the given usb device ID run command: # # Action ID command # bind 047d:8018 /home/tom/scripts/new-mouse-settings # bind 1949:0324 /home/tom/scripts/paperwhite-plugged bind 1949:0004 /home/tom/scripts/kindle-plugged |