1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2016, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "LInputDevice.h"
8 
9 //Qt Library includes
10 #include <QString>
11 #include <QX11Info>
12 #include <QDebug>
13 
14 //XCB Library includes
15 #include <xcb/xcb.h>
16 #include <xcb/xcb_atom.h>
17 #include <xcb/xinput.h>
18 #include <xcb/xproto.h>
19 
20 #include <LUtils.h>
21 
22 //===================
23 //    LInputDevice Class
24 //===================
25 // === PUBLIC ===
LInputDevice(unsigned int id,unsigned int type)26 LInputDevice::LInputDevice(unsigned int id, unsigned int type){
27   devID = id;
28   devType = type;
29   //ATOM_FLOAT = 0; //init this when needed later
30   //devName = name;
31   getProperties(); //need to populate the name/atom correlations for properties
32   readProperties(); //populate the hash with the current values of the properties
33 }
34 
~LInputDevice()35 LInputDevice::~LInputDevice(){
36 
37 }
38 
devNumber()39 unsigned int LInputDevice::devNumber(){
40   return devID;
41 }
42 
isPointer()43 bool LInputDevice::isPointer(){
44   return (devType==XCB_INPUT_DEVICE_USE_IS_X_POINTER \
45 	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_POINTER);
46 }
47 
isKeyboard()48 bool LInputDevice::isKeyboard(){
49   return (devType==XCB_INPUT_DEVICE_USE_IS_X_KEYBOARD \
50 	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_KEYBOARD);
51 }
52 
isExtension()53 bool LInputDevice::isExtension(){
54   return (devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_DEVICE \
55 	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_KEYBOARD \
56 	|| devType==XCB_INPUT_DEVICE_USE_IS_X_EXTENSION_POINTER);
57 }
58 
59 // Property Management
listProperties()60 QList<int> LInputDevice::listProperties(){
61   return devProps.keys();
62 }
63 
propertyName(int prop)64 QString LInputDevice::propertyName(int prop){
65   if(devProps.contains(prop)){ return devProps[prop].name; }
66   else{ return ""; }
67 }
68 
getPropertyValue(int prop)69 QVariant LInputDevice::getPropertyValue(int prop){
70   if(devProps.contains(prop)){ return devProps[prop].value; }
71   else{ return QVariant(); }
72 }
73 
setPropertyValue(int prop,QVariant value)74 bool LInputDevice::setPropertyValue(int prop, QVariant value){
75   if(!devProps.contains(prop)){ return false; }
76   //Need the float atom for some properties - make sure we have that first
77   /*if(ATOM_FLOAT==0){
78     xcb_intern_atom_reply_t *ar = xcb_intern_atom_reply(QX11Info::connection(), \
79 			xcb_intern_atom(QX11Info::connection(), 0, 1, "FLOAT"), NULL);
80     if(ar!=0){
81       ATOM_FLOAT = ar->atom;
82       free(ar);
83     }
84   }*/
85   //Now setup the argument
86   bool ok = false;
87   QStringList args;
88    args << "--set-prop";
89    args << QString::number(devID);
90    args << QString::number(prop); //prop ID
91    args << variantToString(value);
92   ok = (0 == LUtils::runCmd("xinput", args) );
93   if(ok){
94     //Need to update the value in the hash as well
95     propData dat = devProps[prop];
96       dat.value = value;
97     devProps.insert(prop, dat);
98   }
99   return ok;
100 }
101 
102 // === PRIVATE ===
getProperties()103 void LInputDevice::getProperties(){
104   devProps.clear();
105   xcb_input_list_device_properties_cookie_t cookie = xcb_input_list_device_properties_unchecked(QX11Info::connection(), devID);
106   xcb_input_list_device_properties_reply_t *reply = xcb_input_list_device_properties_reply(QX11Info::connection(), cookie, NULL);
107   //Get the atoms
108   xcb_atom_t *atoms = xcb_input_list_device_properties_atoms(reply);
109   //qDebug() << "Property Response Type:" << reply->response_type; //Always seems to be "1"
110   QList<xcb_get_atom_name_cookie_t> cookies;
111   for(int i=0; i<reply->num_atoms; i++){  cookies <<  xcb_get_atom_name(QX11Info::connection(), atoms[i]);  }
112   for(int i=0; i<reply->num_atoms; i++){
113     xcb_get_atom_name_reply_t *nr = xcb_get_atom_name_reply(QX11Info::connection(), cookies[i], NULL);
114     propData DATA;
115       DATA.name = QString::fromUtf8( xcb_get_atom_name_name(nr), xcb_get_atom_name_name_length(nr) );
116       DATA.atom = atoms[i];
117       DATA.id = (int)(atoms[i]);
118     devProps.insert(DATA.id,DATA);
119     ::free(nr);
120   }
121   //Done with data structure
122   ::free(reply);
123 }
124 
readProperties()125 void LInputDevice::readProperties(){
126   QList<int> props = devProps.keys();
127   //XINPUT UTILITY USAGE (alternative to XCB which actually works right now)
128   QStringList info = LUtils::getCmdOutput("xinput list-props "+QString::number(devID));
129   for(int i=0; i<props.length(); i++){
130     propData PROP = devProps[props[i]];
131     QStringList filter = info.filter(" ("+QString::number(PROP.id)+"):");
132     if(filter.length()==1){
133       QString val = filter.first().section("):",1,-1).simplified();
134       //Now figure out what type of value this is and save it into the QVariant
135       QVariant variant;
136       if(val.split(", ").length()>1){
137         //some kind of array
138         QList<QVariant> list;
139         QStringList valList = val.split(", ");
140         for(int j=0; j<valList.length(); j++){ list << valueToVariant(valList[j]); }
141         variant = QVariant(list);
142       }else{
143 	variant = valueToVariant(val);
144       }
145       PROP.value = variant;
146     }
147     devProps.insert(props[i], PROP);
148   }
149 
150 //XCB Code (non-functional - issue with library itself? 12/6/16 - Ken Moore)
151   /*QVariant result;
152   if(!devProps.contains(prop)){qDebug() << "Invalid Property"; return result; }
153   //Now generate the property request
154   xcb_input_get_device_property_cookie_t cookie = xcb_input_get_device_property_unchecked( QX11Info::connection(), devProps.value(prop).atom, \
155 		XCB_ATOM_ATOM, 0, 1000, devID, 0);
156   xcb_input_get_device_property_reply_t *reply = xcb_input_get_device_property_reply(QX11Info::connection(), cookie, NULL);
157   if(reply==0){ qDebug() << "Could not get reply!"; return result; }
158   //Now read off the value of the property
159   if(ATOM_FLOAT==0){
160     xcb_intern_atom_reply_t *ar = xcb_intern_atom_reply(QX11Info::connection(), \
161 			xcb_intern_atom(QX11Info::connection(), 0, 1, "FLOAT"), NULL);
162     if(ar!=0){
163       ATOM_FLOAT = ar->atom;
164       free(ar);
165     }
166   }
167   //Turn the reply into the proper items array (depends on format of the return data)
168   xcb_input_get_device_property_items_t items;
169   qDebug() <<QByteArray::fromRawData( (char*)(xcb_input_get_device_property_items(reply) ) , reply->num_items);
170   void *buffer = xcb_input_get_device_property_items(reply);
171   xcb_input_get_device_property_items_serialize( &buffer, reply->num_items, reply->format, &items);
172 
173   //if(reply->num_items > 0){
174   //qDebug() << "Format:" << reply->format << "Length:" << length;
175   //qDebug() << "Response Type:" << reply->response_type << "Pads:" << reply->pad0 << reply->pad1;
176     switch(reply->type){
177 	case XCB_ATOM_INTEGER:
178 	  //qDebug() << "Got Integer";
179 
180 	  break;
181 	case XCB_ATOM_CARDINAL:
182 	  //qDebug() << "Got Cardinal";
183 
184 	  break;
185 	case XCB_ATOM_STRING:
186 	  qDebug() << "Got String:";
187 	  if(reply->format==8){
188 	    result.setValue( QByteArray::fromRawData( (char*) xcb_input_get_device_property_items_data_8(&items), sizeof(xcb_input_get_device_property_items_data_8(&items))/sizeof(char)) );
189 	  }
190 	  break;
191 	case XCB_ATOM_ATOM:
192 	  //qDebug() << "Got Atom";
193 
194 	  break;
195 	default:
196 	    qDebug() << "Other Type:" << reply->type;
197     }
198   //}
199   free(reply); //done with this structure
200   return result;*/
201 }
202 
valueToVariant(QString value)203 QVariant LInputDevice::valueToVariant(QString value){
204   //Read through the string and see what type of value it is
205   if(value.count("\"")==2){
206     //String value or atom
207     if(value.endsWith(")")){
208       //ATOM (name string +(atomID))
209       return QVariant(value); //don't strip off the atom number -- keep that within the parenthesis
210     }else{
211       //String
212       value = value.section("\"",1,-2); //everything between the quotes
213       return QVariant(value);
214     }
215   }else if(value.contains(".")){
216     //float/double number
217     return QVariant( value.toDouble() );
218   }else{
219     //integer or boolian (no way to tell right now - assume all int)
220     bool ok = false;
221     int intval = value.toInt(&ok);
222     if(ok){ return QVariant(intval); }
223   }
224   return QVariant();
225 }
226 
variantToString(QVariant value)227 QString LInputDevice::variantToString(QVariant value){
228   if( value.canConvert< QList<QVariant> >() ){
229     //List of variants
230     QStringList out;
231     QList<QVariant> list = value.toList();
232     for(int i=0; i<list.length(); i++){ out << variantToString(list[i]); }
233     return out.join(", ");
234   }else{
235     //Single value
236     if(value.canConvert<double>() ){
237       return QString::number(value.toDouble());
238     }else if(value.canConvert<int>() ){
239      return QString::number(value.toInt());
240     }else if( value.canConvert<QString>() ){
241       //See if this is an atom first
242       QString val = value.toString();
243       if(val.contains("(")){ val = val.section("(",1,-1).section(")",0,0); }
244       return val;
245     }
246   }
247   return ""; //nothing to return
248 }
249 
250 //======================
251 //  LInput Static Functions
252 //======================
listDevices()253 QList<LInputDevice*> LInput::listDevices(){
254   QList<LInputDevice*> devices;
255   xcb_input_list_input_devices_cookie_t cookie = xcb_input_list_input_devices_unchecked(QX11Info::connection());
256   xcb_input_list_input_devices_reply_t *reply = xcb_input_list_input_devices_reply(QX11Info::connection(), cookie, NULL);
257   if(reply==0){ return devices; } //error - nothing returned
258   //Use the iterator for going through the reply
259   //qDebug() << "Create iterator";
260   xcb_input_device_info_iterator_t iter = xcb_input_list_input_devices_devices_iterator(reply);
261   //xcb_str_iterator_t nameiter = xcb_input_list_input_devices_names_iterator(reply);
262 
263   //Now step through the reply
264   while(iter.data != 0 ){
265     devices << new LInputDevice(iter.data->device_id, iter.data->device_use);
266     //qDebug() << "Found Input Device:" << iter.data->device_id;
267     //qDebug() << "  - num_class_info:" << iter.data->num_class_info;
268     if(iter.rem>0){ xcb_input_device_info_next(&iter); }
269     else{ break; }
270   }
271   //Free the reply (done with it)
272   ::free(reply);
273   //return the information
274   return devices;
275 }
276