Files
libg15/libg15.c
T

650 lines
21 KiB
C
Raw Normal View History

2019-04-29 15:39:27 +02:00
/*
This file is part of g15tools.
g15tools is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
g15tools is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with g15lcd; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <pthread.h>
#include "libg15.h"
#include <stdio.h>
#include <stdarg.h>
#include <usb.h>
#include <string.h>
#include <errno.h>
static usb_dev_handle *keyboard_device = 0;
static int libg15_debugging_enabled = 0;
static int enospc_slowdown = 0;
static int found_devicetype = -1;
static int shared_device = 0;
static int g15_keys_endpoint = 0;
static int g15_lcd_endpoint = 0;
static pthread_mutex_t libusb_mutex;
/* to add a new device, simply create a new DEVICE() in this list */
/* Fields are: "Name",VendorID,ProductID,Capabilities */
const libg15_devices_t g15_devices[] = {
DEVICE("Logitech G15",0x46d,0xc222,G15_LCD|G15_KEYS),
DEVICE("Logitech G11",0x46d,0xc225,G15_KEYS),
DEVICE("Logitech Z-10",0x46d,0x0a07,G15_LCD|G15_KEYS|G15_DEVICE_IS_SHARED),
DEVICE(NULL,0,0,0)
};
/* return device capabilities */
int g15DeviceCapabilities() {
if(found_devicetype>-1)
return g15_devices[found_devicetype].caps;
else
return -1;
}
/* enable or disable debugging */
void libg15Debug(int option) {
libg15_debugging_enabled = option;
usb_set_debug(option);
}
/* debugging wrapper */
static int g15_log (FILE *fd, unsigned int level, const char *fmt, ...) {
if (libg15_debugging_enabled && libg15_debugging_enabled>=level) {
fprintf(fd,"libg15: ");
va_list argp;
va_start (argp, fmt);
vfprintf(fd,fmt,argp);
va_end (argp);
}
return 0;
}
/* return number of connected and supported devices */
int g15NumberOfConnectedDevices() {
struct usb_bus *bus = 0;
struct usb_device *dev = 0;
int i=0;
unsigned int found = 0;
for (i=0; g15_devices[i].name !=NULL;i++)
for (bus = usb_busses; bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if ((dev->descriptor.idVendor == g15_devices[i].vendorid && dev->descriptor.idProduct == g15_devices[i].productid))
found++;
}
}
g15_log(stderr,G15_LOG_INFO,"Found %i supported devices\n",found);
return found;
}
static int initLibUsb()
{
usb_init();
/**
* usb_find_busses and usb_find_devices both report the number of devices
* / busses added / removed since the last call. since this is the first
* call we have to return values != 0 or else we didnt find anything */
if (!usb_find_busses())
return G15_ERROR_OPENING_USB_DEVICE;
if (!usb_find_devices())
return G15_ERROR_OPENING_USB_DEVICE;
return G15_NO_ERROR;
}
static usb_dev_handle * findAndOpenDevice(libg15_devices_t handled_device, int device_index)
{
struct usb_bus *bus = 0;
struct usb_device *dev = 0;
int retries=0;
int j,i,k,l;
int interface=0;
for (bus = usb_busses; bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if ((dev->descriptor.idVendor == handled_device.vendorid && dev->descriptor.idProduct == handled_device.productid))
{
int ret=0;
char name_buffer[65535];
name_buffer[0] = 0;
usb_dev_handle *devh = 0;
found_devicetype = device_index;
g15_log(stderr,G15_LOG_INFO,"Found %s, trying to open it\n",handled_device.name);
#if 0
devh = usb_open(dev);
usb_reset(devh);
usleep(50*1000);
usb_close(devh);
#endif
devh = usb_open(dev);
if (!devh)
{
g15_log(stderr,G15_LOG_INFO, "Error, could not open the keyboard\n");
g15_log(stderr,G15_LOG_INFO, "Perhaps you dont have enough permissions to access it\n");
return 0;
}
usleep(50*1000);
g15_log(stderr, G15_LOG_INFO, "Device has %i possible configurations\n",dev->descriptor.bNumConfigurations);
/* if device is shared with another driver, such as the Z-10 speakers sharing with alsa, we have to disable some calls */
if(g15DeviceCapabilities() & G15_DEVICE_IS_SHARED)
shared_device = 1;
for (j = 0; j<dev->descriptor.bNumConfigurations;j++){
struct usb_config_descriptor *cfg = &dev->config[j];
for (i=0;i<cfg->bNumInterfaces; i++){
struct usb_interface *ifp = &cfg->interface[i];
/* if endpoints are already known, finish up */
if(g15_keys_endpoint && g15_lcd_endpoint)
break;
g15_log(stderr, G15_LOG_INFO, "Device has %i Alternate Settings\n", ifp->num_altsetting);
for(k=0;k<ifp->num_altsetting;k++){
struct usb_interface_descriptor *as = &ifp->altsetting[k];
/* verify that the interface is for a HID device */
if(as->bInterfaceClass==USB_CLASS_HID){
g15_log(stderr, G15_LOG_INFO, "Interface %i has %i Endpoints\n", i, as->bNumEndpoints);
usleep(50*1000);
/* libusb functions ending in _np are not portable between OS's
* Non-linux users will need some way to detach the HID driver from
* the G15 until we work out how to do this for other OS's automatically.
* For the moment, we just skip this code..
*/
#ifdef LIBUSB_HAS_GET_DRIVER_NP
ret = usb_get_driver_np(devh, i, name_buffer, 65535);
/* some kernel versions say that a driver is attached even though there is none
in this case the name buffer has not been changed
thanks to RobEngle for pointing this out */
if (!ret && name_buffer[0])
{
g15_log(stderr,G15_LOG_INFO,"Trying to detach driver currently attached: \"%s\"\n",name_buffer);
ret = usb_detach_kernel_driver_np(devh, i);
if (!ret)
{
g15_log(stderr,G15_LOG_INFO,"Success, detached the driver\n");
}
else
{
g15_log(stderr,G15_LOG_INFO,"Sorry, I could not detached the driver, giving up\n");
return 0;
}
}
#endif
/* don't set configuration if device is shared */
if(0 == shared_device) {
ret = usb_set_configuration(devh, 1);
if (ret)
{
g15_log(stderr,G15_LOG_INFO,"Error setting the configuration, this is fatal\n");
return 0;
}
}
usleep(50*1000);
while((ret = usb_claim_interface(devh,i)) && retries <10) {
usleep(50*1000);
retries++;
g15_log(stderr,G15_LOG_INFO,"Trying to claim interface\n");
}
if (ret)
{
g15_log(stderr,G15_LOG_INFO,"Error claiming interface, good day cruel world\n");
return 0;
}
for (l=0; l< as->bNumEndpoints;l++){
struct usb_endpoint_descriptor *ep=&as->endpoint[l];
g15_log(stderr, G15_LOG_INFO, "Found %s endpoint %i with address 0x%X maxtransfersize=%i \n",
0x80&ep->bEndpointAddress?"\"Extra Keys\"":"\"LCD\"",
ep->bEndpointAddress&0x0f,ep->bEndpointAddress, ep->wMaxPacketSize
);
if(0x80 & ep->bEndpointAddress) {
g15_keys_endpoint = ep->bEndpointAddress;
} else {
g15_lcd_endpoint = ep->bEndpointAddress;
}
#if 0
usb_resetep(devh,ep->bEndpointAddress);
#endif
}
if (ret)
{
g15_log(stderr, G15_LOG_INFO, "Error setting Alternate Interface\n");
}
}
}
}
}
g15_log(stderr,G15_LOG_INFO,"Done opening the keyboard\n");
usleep(500*1000); // sleep a bit for good measure
return devh;
}
}
}
return 0;
}
static usb_dev_handle * findAndOpenG15() {
int i;
for (i=0; g15_devices[i].name !=NULL ;i++){
g15_log(stderr,G15_LOG_INFO,"Trying to find %s\n",g15_devices[i].name);
if(keyboard_device = findAndOpenDevice(g15_devices[i],i)){
break;
}
else
g15_log(stderr,G15_LOG_INFO,"%s not found\n",g15_devices[i].name);
}
return keyboard_device;
}
int re_initLibG15()
{
usb_init();
/**
* usb_find_busses and usb_find_devices both report the number of devices
* / busses added / removed since the last call. since this is the first
* call we have to return values != 0 or else we didnt find anything */
if (!usb_find_devices())
return G15_ERROR_OPENING_USB_DEVICE;
keyboard_device = findAndOpenG15();
if (!keyboard_device)
return G15_ERROR_OPENING_USB_DEVICE;
return G15_NO_ERROR;
}
int initLibG15()
{
int retval = G15_NO_ERROR;
retval = initLibUsb();
if (retval)
return retval;
g15NumberOfConnectedDevices();
keyboard_device = findAndOpenG15();
if (!keyboard_device)
return G15_ERROR_OPENING_USB_DEVICE;
pthread_mutex_init(&libusb_mutex, NULL);
return retval;
}
/* reset the keyboard, returning it to a known state */
int exitLibG15()
{
int retval = G15_NO_ERROR;
if (keyboard_device){
retval = usb_release_interface (keyboard_device, 0);
usleep(50*1000);
#if 0
retval = usb_reset(keyboard_device);
usleep(50*1000);
#endif
usb_close(keyboard_device);
keyboard_device=0;
pthread_mutex_destroy(&libusb_mutex);
return retval;
}
return -1;
}
static void dumpPixmapIntoLCDFormat(unsigned char *lcd_buffer, unsigned char const *data)
{
unsigned int offset_from_start = G15_LCD_OFFSET;
unsigned int curr_row = 0;
unsigned int curr_col = 0;
for (curr_row=0;curr_row<G15_LCD_HEIGHT;++curr_row)
{
for (curr_col=0;curr_col<G15_LCD_WIDTH;++curr_col)
{
unsigned int pixel_offset = curr_row*G15_LCD_WIDTH + curr_col;
unsigned int byte_offset = pixel_offset / 8;
unsigned int bit_offset = pixel_offset % 8;
unsigned int val = data[byte_offset] & 1<<(7-bit_offset);
unsigned int row = curr_row / 8;
unsigned int offset = G15_LCD_WIDTH*row + curr_col;
unsigned int bit = curr_row % 8;
/*
if (val)
printf("Setting pixel at row %d col %d to %d offset %d bit %d\n",curr_row,curr_col, val, offset, bit);
*/
if (val)
lcd_buffer[offset_from_start + offset] = lcd_buffer[offset_from_start + offset] | 1 << bit;
else
lcd_buffer[offset_from_start + offset] = lcd_buffer[offset_from_start + offset] & ~(1 << bit);
}
}
}
int handle_usb_errors(const char *prefix, int ret) {
switch (ret){
case -ETIMEDOUT:
return G15_ERROR_READING_USB_DEVICE; /* backward-compatibility */
break;
case -ENOSPC: /* the we dont have enough bandwidth, apparently.. something has to give here.. */
g15_log(stderr,G15_LOG_INFO,"usb error: ENOSPC.. reducing speed\n");
enospc_slowdown = 1;
break;
case -ENODEV: /* the device went away - we probably should attempt to reattach */
case -ENXIO: /* host controller bug */
case -EINVAL: /* invalid request */
case -EAGAIN: /* try again */
case -EFBIG: /* too many frames to handle */
case -EMSGSIZE: /* msgsize is invalid */
g15_log(stderr,G15_LOG_INFO,"usb error: %s (%i)\n",prefix,ret);
break;
case -EPIPE: /* endpoint is stalled */
g15_log(stderr,G15_LOG_INFO,"usb error: %s EPIPE! clearing...\n",prefix);
pthread_mutex_lock(&libusb_mutex);
usb_clear_halt(keyboard_device, 0x81);
pthread_mutex_unlock(&libusb_mutex);
break;
default: /* timed out */
g15_log(stderr,G15_LOG_INFO,"Unknown usb error: %s !! (err is %i)\n",prefix,ret);
}
return ret;
}
int writePixmapToLCD(unsigned char const *data)
{
int ret = 0;
int transfercount=0;
unsigned char lcd_buffer[G15_BUFFER_LEN];
memset(lcd_buffer,0,G15_BUFFER_LEN);
dumpPixmapIntoLCDFormat(lcd_buffer, data);
if(!(g15_devices[found_devicetype].caps & G15_LCD))
return 0;
/* the keyboard needs this magic byte */
lcd_buffer[0] = 0x03;
/* in an attempt to reduce peak bus utilisation, we break the transfer into 32 byte chunks and sleep a bit in between.
It shouldnt make much difference, but then again, the g15 shouldnt be flooding the bus enough to cause ENOSPC, yet
apparently does on some machines...
I'm not sure how successful this will be in combatting ENOSPC, but we'll give it try in the real-world. */
if(enospc_slowdown != 0){
pthread_mutex_lock(&libusb_mutex);
for(transfercount = 0;transfercount<=31;transfercount++){
ret = usb_interrupt_write(keyboard_device, g15_lcd_endpoint, (char*)lcd_buffer+(32*transfercount), 32, 1000);
if (ret != 32)
{
handle_usb_errors ("LCDPixmap Slow Write",ret);
return G15_ERROR_WRITING_PIXMAP;
}
usleep(100);
}
pthread_mutex_unlock(&libusb_mutex);
}else{
/* transfer entire buffer in one hit */
pthread_mutex_lock(&libusb_mutex);
ret = usb_interrupt_write(keyboard_device, g15_lcd_endpoint, (char*)lcd_buffer, G15_BUFFER_LEN, 1000);
pthread_mutex_unlock(&libusb_mutex);
if (ret != G15_BUFFER_LEN)
{
handle_usb_errors ("LCDPixmap Write",ret);
return G15_ERROR_WRITING_PIXMAP;
}
usleep(100);
}
return 0;
}
int setLCDContrast(unsigned int level)
{
int retval = 0;
unsigned char usb_data[] = { 2, 32, 129, 0 };
if(shared_device>0)
return G15_ERROR_UNSUPPORTED;
switch(level)
{
case 1:
usb_data[3] = 22;
break;
case 2:
usb_data[3] = 26;
break;
default:
usb_data[3] = 18;
}
pthread_mutex_lock(&libusb_mutex);
retval = usb_control_msg(keyboard_device, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x302, 0, (char*)usb_data, 4, 10000);
pthread_mutex_unlock(&libusb_mutex);
return retval;
}
int setLEDs(unsigned int leds)
{
int retval = 0;
unsigned char m_led_buf[4] = { 2, 4, 0, 0 };
m_led_buf[2] = ~(unsigned char)leds;
if(shared_device>0)
return G15_ERROR_UNSUPPORTED;
pthread_mutex_lock(&libusb_mutex);
retval = usb_control_msg(keyboard_device, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x302, 0, (char*)m_led_buf, 4, 10000);
pthread_mutex_unlock(&libusb_mutex);
return retval;
}
int setLCDBrightness(unsigned int level)
{
int retval = 0;
unsigned char usb_data[] = { 2, 2, 0, 0 };
if(shared_device>0)
return G15_ERROR_UNSUPPORTED;
switch(level)
{
case 1 :
usb_data[2] = 0x10;
break;
case 2 :
usb_data[2] = 0x20;
break;
default:
usb_data[2] = 0x00;
}
pthread_mutex_lock(&libusb_mutex);
retval = usb_control_msg(keyboard_device, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x302, 0, (char*)usb_data, 4, 10000);
pthread_mutex_unlock(&libusb_mutex);
return retval;
}
/* set the keyboard backlight. doesnt affect lcd backlight. 0==off,1==medium,2==high */
int setKBBrightness(unsigned int level)
{
int retval = 0;
unsigned char usb_data[] = { 2, 1, 0, 0 };
if(shared_device>0)
return G15_ERROR_UNSUPPORTED;
switch(level)
{
case 1 :
usb_data[2] = 0x1;
break;
case 2 :
usb_data[2] = 0x2;
break;
default:
usb_data[2] = 0x0;
}
pthread_mutex_lock(&libusb_mutex);
retval = usb_control_msg(keyboard_device, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x302, 0, (char*)usb_data, 4, 10000);
pthread_mutex_unlock(&libusb_mutex);
return retval;
}
static unsigned char g15KeyToLogitechKeyCode(int key)
{
// first 12 G keys produce F1 - F12, thats 0x3a + key
if (key < 12)
{
return 0x3a + key;
}
// the other keys produce Key '1' (above letters) + key, thats 0x1e + key
else
{
return 0x1e + key - 12; // sigh, half an hour to find -12 ....
}
}
static void processKeyEvent(unsigned int *pressed_keys, unsigned char *buffer)
{
int i;
*pressed_keys = 0;
g15_log(stderr,G15_LOG_WARN,"Keyboard: %x, %x, %x, %x, %x, %x, %x, %x, %x\n",buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5],buffer[6],buffer[7],buffer[8]);
if (buffer[0] == 0x02)
{
if (buffer[1]&0x01)
*pressed_keys |= G15_KEY_G1;
if (buffer[2]&0x02)
*pressed_keys |= G15_KEY_G2;
if (buffer[3]&0x04)
*pressed_keys |= G15_KEY_G3;
if (buffer[4]&0x08)
*pressed_keys |= G15_KEY_G4;
if (buffer[5]&0x10)
*pressed_keys |= G15_KEY_G5;
if (buffer[6]&0x20)
*pressed_keys |= G15_KEY_G6;
if (buffer[2]&0x01)
*pressed_keys |= G15_KEY_G7;
if (buffer[3]&0x02)
*pressed_keys |= G15_KEY_G8;
if (buffer[4]&0x04)
*pressed_keys |= G15_KEY_G9;
if (buffer[5]&0x08)
*pressed_keys |= G15_KEY_G10;
if (buffer[6]&0x10)
*pressed_keys |= G15_KEY_G11;
if (buffer[7]&0x20)
*pressed_keys |= G15_KEY_G12;
if (buffer[1]&0x04)
*pressed_keys |= G15_KEY_G13;
if (buffer[2]&0x08)
*pressed_keys |= G15_KEY_G14;
if (buffer[3]&0x10)
*pressed_keys |= G15_KEY_G15;
if (buffer[4]&0x20)
*pressed_keys |= G15_KEY_G16;
if (buffer[5]&0x40)
*pressed_keys |= G15_KEY_G17;
if (buffer[8]&0x40)
*pressed_keys |= G15_KEY_G18;
if (buffer[6]&0x01)
*pressed_keys |= G15_KEY_M1;
if (buffer[7]&0x02)
*pressed_keys |= G15_KEY_M2;
if (buffer[8]&0x04)
*pressed_keys |= G15_KEY_M3;
if (buffer[7]&0x40)
*pressed_keys |= G15_KEY_MR;
if (buffer[8]&0x80)
*pressed_keys |= G15_KEY_L1;
if (buffer[2]&0x80)
*pressed_keys |= G15_KEY_L2;
if (buffer[3]&0x80)
*pressed_keys |= G15_KEY_L3;
if (buffer[4]&0x80)
*pressed_keys |= G15_KEY_L4;
if (buffer[5]&0x80)
*pressed_keys |= G15_KEY_L5;
if (buffer[1]&0x80)
*pressed_keys |= G15_KEY_LIGHT;
}
}
int getPressedKeys(unsigned int *pressed_keys, unsigned int timeout)
{
unsigned char buffer[9];
int ret = 0;
pthread_mutex_lock(&libusb_mutex);
ret = usb_interrupt_read(keyboard_device, g15_keys_endpoint, (char*)buffer, 9, timeout);
pthread_mutex_unlock(&libusb_mutex);
if (ret == 9)
{
if (buffer[0] == 1)
return G15_ERROR_TRY_AGAIN;
processKeyEvent(pressed_keys, buffer);
return G15_NO_ERROR;
}
return handle_usb_errors("Keyboard Read", ret); /* allow the app to deal with errors */
}