blob: e7ba5e6aebd4ff1899012af3a4c74bca9709c7de [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/input.h>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <X11/extensions/XI2.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/XIproto.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x) ((x)%BITS_PER_LONG)
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array)((array[LONG(bit)] >> OFF(bit)) & 1)
const char kDevInputEvent[] = "/dev/input";
const char kEventDevName[] = "event";
const char kTapPausedName[] = "Tap Paused";
using std::string;
namespace lid_touchpad_helper {
void PerrorAbort(const char* err) __attribute__((noreturn));
void PerrorAbort(const char* err) {
perror(err);
exit(1);
}
class XNotifier {
public:
XNotifier(Display* display, int tp_id) : display_(display), dev_(NULL) {
if (!display_) {
fprintf(stderr, "Unable to connect to X server.\n");
exit(1);
}
dev_ = XOpenDevice(display_, tp_id);
prop_tap_paused_ = XInternAtom(display_, kTapPausedName, True);
}
~XNotifier() {
if (dev_) {
XCloseDevice(display_, dev_);
dev_ = NULL;
}
}
bool NotifyLidEvent(int lid_close) {
if (!dev_)
return false;
unsigned char pause_tap = lid_close ? 1 : 0;
XChangeDeviceProperty(display_, dev_, prop_tap_paused_, XA_INTEGER, 8,
PropModeReplace, &pause_tap, 1);
XSync(display_, False);
return true;
}
private:
Display *display_;
XDevice* dev_;
Atom prop_tap_paused_;
};
bool SupportLidEvent(int fd) {
unsigned long events[EV_MAX];
memset(events, 0, sizeof(events));
if (ioctl(fd, EVIOCGBIT(0, EV_MAX), events) < 0) {
fprintf(stderr, "error in ioctl on fd %d for events list\n", fd);
return false;
}
if (test_bit(EV_SW, events)) {
unsigned long switch_events[NBITS(SW_LID + 1)];
memset(switch_events, 0, sizeof(switch_events));
if (ioctl(fd, EVIOCGBIT(EV_SW, SW_LID + 1), switch_events) < 0) {
fprintf(stderr, "Error in ioctl on fd %d for switch_events", fd);
return false;
}
if (test_bit(SW_LID, switch_events))
return true;
}
return false;
}
int IsEventDevice(const struct dirent *dir) {
return strncmp(kEventDevName, dir->d_name, sizeof(kEventDevName) - 1) == 0;
}
int GetLidFd() {
struct dirent **namelist;
int ret = -1;
int ndev = scandir(kDevInputEvent, &namelist, IsEventDevice, alphasort);
if (ndev <= 0)
return ret;
for (int i = 0; i < ndev; i++) {
char fname[64];
snprintf(fname, sizeof(fname),
"%s/%s", kDevInputEvent, namelist[i]->d_name);
int fd = open(fname, O_RDONLY);
if (fd < 0)
continue;
if (SupportLidEvent(fd)) {
ret = fd;
break;
}
close(fd);
}
for (int i = 0; i < ndev; i++)
free(namelist[i]);
free(namelist);
return ret;
}
bool IsCmtTouchpad(Display* display, int device_id) {
Atom prop = XInternAtom(display, kTapPausedName, True);
// Fail quick if server does not have Atom
if (prop == None)
return false;
Atom actual_type;
int actual_format;
unsigned long num_items, bytes_after;
unsigned char* data;
if (XIGetProperty(display, device_id, prop, 0, 1000, False, AnyPropertyType,
&actual_type, &actual_format, &num_items, &bytes_after,
&data) != Success) {
// XIGetProperty can generate BadAtom, BadValue, and BadWindow errors
fprintf(stderr, "Mysterious X server error\n");
return false;
}
if (actual_type == None)
return false;
XFree(data);
return (actual_type == XA_INTEGER);
}
int GetTouchpadXId(Display* display) {
int num_devices = 0;
XIDeviceInfo* info = XIQueryDevice(display, XIAllDevices, &num_devices);
if (!info)
PerrorAbort("XIQueryDevice");
int ret = -1;
for (int i = 0; i < num_devices; i++) {
XIDeviceInfo* device = &info[i];
if (device->use != XISlavePointer)
continue;
if (!IsCmtTouchpad(display, device->deviceid))
continue;
ret = device->deviceid;
break;
}
XIFreeDeviceInfo(info);
return ret;
}
bool HandleInput(int fd, XNotifier* notifier) {
const size_t kNumEvents = 16; // Max events to read per read call.
struct input_event events[kNumEvents];
const size_t kEventSize = sizeof(events[0]);
while (true) {
ssize_t readlen = read(fd, events, sizeof(events));
if (readlen < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return true;
if (errno == ENODEV)
return false; // Device is gone. Try to reopen
PerrorAbort("read");
}
if (readlen == 0) {
// EOF. Reopen device.
return false;
}
if (readlen % kEventSize) {
fprintf(stderr, "Read %zd bytes, not a multiple of %zu.\n",
readlen, kEventSize);
exit(1);
}
for (size_t i = 0, cnt = readlen / kEventSize; i < cnt; i++) {
const struct input_event& event = events[i];
if (event.type == EV_SW && event.code == SW_LID) {
if (!notifier->NotifyLidEvent(event.value))
return false;
}
}
}
return true;
}
class MainLoop {
public:
MainLoop(Display* display)
: display_(display),
tp_id_(-1),
lid_fd_(-1),
x_fd_(ConnectionNumber(display_)),
xiopcode_(GetXInputOpCode()) {}
void Run() __attribute__ ((noreturn)) {
XIEventMask evmask;
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};
XISetMask(mask, XI_HierarchyChanged);
evmask.deviceid = XIAllDevices;
evmask.mask_len = sizeof(mask);
evmask.mask = mask;
XISelectEvents(display_, DefaultRootWindow(display_), &evmask, 1);
while (true) {
if (DevicesNeedRefresh()) {
sleep(1);
RefreshDevices();
continue;
}
int max_fd_plus_1 = std::max(x_fd_, lid_fd_) + 1;
fd_set theset;
FD_ZERO(&theset);
FD_SET(x_fd_, &theset);
FD_SET(lid_fd_, &theset);
int rc = select(max_fd_plus_1, &theset, NULL, NULL, NULL);
if (errno == EBADF) {
close(lid_fd_);
lid_fd_ = -1;
continue;
}
if (rc < 0)
PerrorAbort("select");
if (FD_ISSET(x_fd_, &theset))
ServiceXFd();
else if (FD_ISSET(lid_fd_, &theset))
ServiceLidFd();
}
}
void ServiceLidFd() {
if (!HandleInput(lid_fd_, notifier_.get())) {
close(lid_fd_);
lid_fd_ = -1;
}
}
void ServiceXFd() {
while (XPending(display_)) {
XEvent ev;
XNextEvent(display_, &ev);
if (ev.xcookie.type == GenericEvent &&
ev.xcookie.extension == xiopcode_ &&
XGetEventData(display_, &ev.xcookie)) {
if (ev.xcookie.evtype == XI_HierarchyChanged) {
// Rescan for touchpad
tp_id_ = -1;
}
XFreeEventData(display_, &ev.xcookie);
}
}
}
void RefreshDevices() {
if (lid_fd_ < 0)
lid_fd_ = GetLidFd();
if (tp_id_ < 0) {
tp_id_ = GetTouchpadXId(display_);
if (tp_id_ >= 0)
notifier_.reset(new XNotifier(display_, tp_id_));
}
}
bool DevicesNeedRefresh() {
return tp_id_ < 0 || lid_fd_ < 0;
}
// Lifted from http://codereview.chromium.org/6975057/patch/16001/17005
int GetXInputOpCode() {
static const char kExtensionName[] = "XInputExtension";
int xi_opcode = -1;
int event;
int error;
if (!XQueryExtension(display_, kExtensionName, &xi_opcode, &event,
&error)) {
printf("X Input extension not available: error=%d\n", error);
return -1;
}
return xi_opcode;
}
private:
Display* display_;
std::unique_ptr<XNotifier> notifier_;
int tp_id_;
int lid_fd_;
int x_fd_;
int xiopcode_;
};
}; // namespace lid_touchpad_helper
int main(int argc, char** argv) {
using namespace lid_touchpad_helper;
bool FLAGS_foreground = false;
string FLAGS_display;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"display", required_argument, 0, 0},
{"foreground", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{0, 0, 0, 0}
};
if (getopt_long_only(argc, argv, "", long_options, &option_index) == 0) {
switch (option_index) {
case 0:
FLAGS_display.assign(optarg);
break;
case 1:
FLAGS_foreground = true;
break;
case 2:
printf("Chromium OS Lid Touchpad Helper\n\n");
printf("\t--display\tX display. Default: use env var DISPLAY\n");
printf("\t--foreground\tDon't daemon()ize; run in foreground.\n");
printf("\t--help\t\tDisplay this help message.\n");
exit(0);
default:
break;
}
} else {
break;
}
}
if (!FLAGS_foreground && daemon(0, 0) < 0)
PerrorAbort("daemon");
Display* dpy = XOpenDisplay(
FLAGS_display.empty() ? NULL : FLAGS_display.c_str());
if (!dpy) {
printf("XOpenDisplay failed\n");
exit(1);
}
// Check for Xinput 2
int opcode, event, err;
if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &err)) {
fprintf(stderr,
"Failed to get XInputExtension.\n");
return -1;
}
int major = 2, minor = 0;
if (XIQueryVersion(dpy, &major, &minor) == BadRequest) {
fprintf(stderr,
"Server does not have XInput2.\n");
return -1;
}
MainLoop looper(dpy);
looper.Run();
return 0;
}