OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ui/events/ozone/evdev/libgestures_glue/gesture_property_provider.h" | |
6 | |
7 #include <gestures/gestures.h> | |
8 #include <libevdev/libevdev.h> | |
9 | |
10 #include <fnmatch.h> | |
11 #include <string.h> | |
12 | |
13 #include <algorithm> | |
14 #include <iostream> | |
15 #include <set> | |
16 | |
17 #include "base/files/file_enumerator.h" | |
18 #include "base/files/file_util.h" | |
19 #include "base/logging.h" | |
20 #include "base/memory/singleton.h" | |
21 #include "base/strings/string_number_conversions.h" | |
22 #include "base/strings/string_split.h" | |
23 #include "base/strings/string_tokenizer.h" | |
24 #include "base/strings/string_util.h" | |
25 #include "base/strings/string_util.h" | |
26 #include "base/strings/stringize_macros.h" | |
27 #include "base/strings/stringprintf.h" | |
28 | |
29 // Severity level for general info logging purpose. | |
30 #define INFO_SEVERITY INFO | |
spang
2014/09/18 21:23:16
Please don't rename this. You shouldn't use LOG(IN
Shecky Lin
2014/09/19 09:31:08
Done.
| |
31 | |
32 /* Implementation of GesturesProp declared in gestures.h | |
33 * | |
34 * libgestures requires that this be in the top level namespace. | |
35 * */ | |
36 struct GesturesProp { | |
37 GesturesProp() | |
38 : type(ui::GesturePropertyProvider::PT_INT), | |
39 count(1), | |
40 is_allocated(false), | |
41 is_managed_externally(true), | |
42 write_back(NULL), | |
43 handler_data(NULL), | |
44 get(NULL), | |
45 set(NULL) { | |
46 val.v = NULL; | |
47 } | |
48 // Free the data pointer. Should never be called if the GesturesProp is | |
49 // created from the gesture lib side. | |
50 void Free() { | |
51 if (count > 1) | |
52 (*data_pointer_deallocator_map_[type])(val); | |
53 else | |
54 (*data_array_pointer_deallocator_map_[type])(val); | |
55 } | |
56 | |
57 // For logging purpose. | |
58 friend std::ostream& operator<<(std::ostream& os, const GesturesProp& prop); | |
59 | |
60 // Property name, type and number of elements. | |
61 std::string name; | |
62 ui::GesturePropertyProvider::PropertyType type; | |
63 size_t count; | |
64 | |
65 // Data pointer. | |
66 union Data { | |
67 void* v; | |
68 int* i; | |
69 short* h; | |
70 GesturesPropBool* b; | |
71 std::string* s; | |
72 double* r; | |
73 } val; | |
74 | |
75 // If the flag is on, it means the memory that the data pointer points to is | |
76 // allocated here. We will need to free the memory by ourselves when the | |
77 // GesturesProp is destroyed (GesturesPropFunctionsWrapper::Free is called). | |
78 bool is_allocated; | |
79 | |
80 // If the flag is off, it means the GesturesProp is created by passing a NULL | |
81 // data pointer to the creator functions. We will need to free the memory by | |
82 // ourselves when the GesturePropertyProvider is destroyed because no other | |
83 // people will be able to do it. For GesturesProp created with non-NULL | |
84 // pointers, external users are responsible of handling its data memory (via | |
85 // the GesturesPropProvider APIs). | |
86 // | |
87 // Note that the flag is different from is_allocated because the string | |
88 // properties are always allocated here whether it is created with NULL or | |
89 // not. In short, if this flag is false, then is_allocated must be true. | |
90 bool is_managed_externally; | |
91 | |
92 // In some cases, we don't directly use the data pointer provided by the | |
93 // creators due to its limitation and instead use our own types (e.g., in | |
94 // the case of string). We thus need to store the write back pointer so that | |
95 // we can update the value in the gesture lib if the property value gets | |
96 // changed. | |
97 void* write_back; | |
98 | |
99 // Handler function pointers and the data to be passed to them when the | |
100 // property is accessed. | |
101 void* handler_data; | |
102 GesturesPropGetHandler get; | |
103 GesturesPropSetHandler set; | |
104 private: | |
105 // Trick for appropriately deallocating the data memory based on its type. | |
106 typedef std::map<ui::GesturePropertyProvider::PropertyType, | |
107 void (*)(GesturesProp::Data&)> DeallocatorMap; | |
108 template <typename T> | |
109 static void DataPointerDeallocator(Data& data) { | |
110 delete reinterpret_cast<T>(data.v); | |
111 } | |
112 template <typename T> | |
113 static void DataArrayPointerDeallocator(Data& data) { | |
114 delete[] reinterpret_cast<T>(data.v); | |
115 } | |
116 template <typename T> | |
117 static void InsertDeallocator(DeallocatorMap* deallocator_map, | |
118 ui::GesturePropertyProvider::PropertyType type, | |
119 const bool is_array) { | |
120 if (is_array) { | |
121 deallocator_map->insert( | |
122 std::make_pair(type, &DataArrayPointerDeallocator<T*>)); | |
123 } else { | |
124 deallocator_map->insert( | |
125 std::make_pair(type, &DataPointerDeallocator<T*>)); | |
126 } | |
127 } | |
128 | |
129 // Populate deallocators in the deallocator map. | |
130 static DeallocatorMap CreateDataPointerDeallocatorMap(const bool is_array) { | |
131 DeallocatorMap result; | |
132 // Macro for adding GesturesProp data pointer deallocators. | |
133 #define REGISTER_GPROP_DEALLOCATOR(NAME, TYPE) \ | |
134 InsertDeallocator<TYPE>(&result, ui::GesturePropertyProvider::NAME, is_array) | |
135 REGISTER_GPROP_DEALLOCATOR(PT_INT, int); | |
136 REGISTER_GPROP_DEALLOCATOR(PT_SHORT, short); | |
137 REGISTER_GPROP_DEALLOCATOR(PT_BOOL, GesturesPropBool); | |
138 REGISTER_GPROP_DEALLOCATOR(PT_STRING, std::string); | |
139 REGISTER_GPROP_DEALLOCATOR(PT_REAL, double); | |
140 #undef REGISTER_GPROP_DEALLOCATOR | |
141 return result; | |
142 } | |
143 | |
144 // These deallocator maps store deallocators for different data types. | |
145 static DeallocatorMap data_pointer_deallocator_map_; | |
spang
2014/09/18 21:23:16
static objects with nontrivial initialization aren
Shecky Lin
2014/09/19 09:31:08
Done. Thanks for the great suggestion.
| |
146 static DeallocatorMap data_array_pointer_deallocator_map_; | |
147 }; | |
148 | |
149 // Trick for initializing static map members. | |
150 GesturesProp::DeallocatorMap GesturesProp::data_pointer_deallocator_map_( | |
151 GesturesProp::CreateDataPointerDeallocatorMap(false)); | |
152 GesturesProp::DeallocatorMap GesturesProp::data_array_pointer_deallocator_map_( | |
153 GesturesProp::CreateDataPointerDeallocatorMap(true)); | |
154 | |
155 // Property type logging function. | |
156 std::ostream& operator<<(std::ostream& out, | |
157 const ui::GesturePropertyProvider::PropertyType type) { | |
158 std::string s; | |
159 #define TYPE_CASE(TYPE) \ | |
160 case (ui::GesturePropertyProvider::TYPE): \ | |
161 s = #TYPE; \ | |
162 break; | |
163 switch (type) { | |
164 TYPE_CASE(PT_INT); | |
165 TYPE_CASE(PT_SHORT); | |
166 TYPE_CASE(PT_BOOL); | |
167 TYPE_CASE(PT_STRING); | |
168 TYPE_CASE(PT_REAL); | |
169 default: | |
170 s = "Unknown type"; | |
171 break; | |
172 } | |
173 #undef TYPE_CASE | |
174 return out << s; | |
175 } | |
176 | |
177 // GesturesProp logging function. | |
178 std::ostream& operator<<(std::ostream& os, const GesturesProp& prop) { | |
179 os << "\"" << prop.name << "\", " << prop.type << ", " << prop.count << ", (" | |
180 << prop.is_allocated << ", " << prop.is_managed_externally << "), " | |
181 << prop.write_back << ", ("; | |
182 bool known_prop_type = true; | |
183 for (size_t i = 0; (i < prop.count) && known_prop_type; i++) { | |
184 switch (prop.type) { | |
185 case ui::GesturePropertyProvider::PT_INT: | |
186 os << prop.val.i[i]; | |
187 break; | |
188 case ui::GesturePropertyProvider::PT_SHORT: | |
189 os << prop.val.h[i]; | |
190 break; | |
191 case ui::GesturePropertyProvider::PT_BOOL: | |
192 // Prevent the value being printed as characters. | |
193 os << static_cast<int>(prop.val.b[i]); | |
194 break; | |
195 case ui::GesturePropertyProvider::PT_STRING: | |
196 os << prop.val.s[i]; | |
197 break; | |
198 case ui::GesturePropertyProvider::PT_REAL: | |
199 os << prop.val.r[i]; | |
200 break; | |
201 default: | |
202 LOG(ERROR) << "Unknown gesture property type: " << prop.type; | |
203 known_prop_type = false; | |
204 break; | |
205 } | |
206 os << ", "; | |
207 } | |
208 os << ")"; | |
209 return os; | |
210 } | |
211 | |
212 namespace ui { | |
213 | |
214 // The path that we will look for conf files. | |
215 const char kConfigurationFilePath[] = "/etc/gesture"; | |
216 | |
217 // We support only match types that have already been used. One should change | |
218 // this if we start using new types in the future. Note that most unsupported | |
219 // match types are either useless in CrOS or inapplicable to the non-X | |
220 // environment. | |
221 const char* kSupportedMatchTypes[] = {"MatchProduct", | |
222 "MatchDevicePath", | |
223 "MatchUSBID", | |
224 "MatchIsPointer", | |
225 "MatchIsTouchpad", | |
226 "MatchIsTouchscreen"}; | |
227 const char* kUnsupportedMatchTypes[] = {"MatchVendor", | |
228 "MatchOS", | |
229 "MatchPnPID", | |
230 "MatchDriver", | |
231 "MatchTag", | |
232 "MatchLayout", | |
233 "MatchIsKeyboard", | |
234 "MatchIsJoystick", | |
235 "MatchIsTablet"}; | |
236 | |
237 // Special keywords for boolean values. | |
238 const char* kTrue[] = {"on", "true", "yes"}; | |
239 const char* kFalse[] = {"off", "false", "no"}; | |
240 | |
241 // Trick to get the device path from a file descriptor. | |
242 std::string GetDeviceNodePath(void* dev) { | |
243 std::string proc_symlink = | |
244 "/proc/self/fd/" + base::IntToString(static_cast<Evdev*>(dev)->fd); | |
245 base::FilePath path; | |
246 if (!base::ReadSymbolicLink(base::FilePath(proc_symlink), &path)) | |
247 return std::string(); | |
248 return path.value(); | |
249 } | |
250 | |
251 GesturePropertyProvider* GesturePropertyProvider::GetInstance() { | |
252 return Singleton<GesturePropertyProvider>::get(); | |
253 } | |
254 | |
255 GesturePropertyProvider::GesturePropertyProvider() : device_id_counter_(0) { | |
256 RegisterMatchCriterias(); | |
257 LoadDeviceConfigurations(); | |
258 } | |
259 | |
260 GesturePropertyProvider::~GesturePropertyProvider() { | |
261 // Note that we don't need to free most GesturesProp data pointers - they are | |
262 // managed externally through the GesturesPropProvider APIs. However, some | |
263 // props are created by directly calling the APIs with NULL pointers so they | |
264 // still need to be handled here. | |
265 DevicePropertyMap::iterator it = properties_maps_.begin(); | |
266 for (; it != properties_maps_.end(); ++it) { | |
267 PropertyMap::iterator ip = it->second->begin(); | |
268 for (; ip != it->second->end(); ++ip) | |
269 if (!ip->second->is_managed_externally) | |
270 ip->second->Free(); | |
271 } | |
272 | |
273 // We do need to free the data pointers for props in configurations_ as they | |
274 // are maintained by ourselves. The props in default_properties_maps_ are all | |
275 // referenced from configurations_ so they are OK. | |
276 for (size_t i = 0; i < configurations_.size(); i++) { | |
277 for (size_t j = 0; j < configurations_[i].criterias.size(); j++) | |
278 delete configurations_[i].criterias[j]; | |
alexst (slow to review)
2014/09/18 16:13:29
Thank you for the scoped_ptr_hash map change. Can
Shecky Lin
2014/09/19 09:31:08
Yeah, I am still looking into this. Will do it at
| |
279 for (size_t j = 0; j < configurations_[i].properties.size(); j++) | |
280 configurations_[i].properties[j].Free(); | |
281 } | |
282 } | |
283 | |
284 void GesturePropertyProvider::RegisterMatchCriterias() { | |
285 // Macro for registering match criteria derived classes. | |
286 #define REGISTER_MATCH_CRITERIA(NAME) \ | |
287 match_criteria_map_[#NAME] = \ | |
288 &ui::GesturePropertyProvider::AllocateMatchCriteria<NAME> | |
289 REGISTER_MATCH_CRITERIA(MatchProduct); | |
290 REGISTER_MATCH_CRITERIA(MatchDevicePath); | |
291 REGISTER_MATCH_CRITERIA(MatchUSBID); | |
292 REGISTER_MATCH_CRITERIA(MatchIsPointer); | |
293 REGISTER_MATCH_CRITERIA(MatchIsTouchpad); | |
294 REGISTER_MATCH_CRITERIA(MatchIsTouchscreen); | |
295 #undef REGISTER_MATCH_CRITERIA | |
296 // TODO(sheckylin): Add a check here to make sure all match criterias in | |
297 // kSupportedMatchTypes is registered here. Otherwise, let the derived | |
298 // classess register themselves instead. | |
299 } | |
300 | |
301 void GesturePropertyProvider::GetDeviceIndices( | |
302 const DeviceType type, | |
303 std::vector<DeviceId>* device_ids) { | |
304 device_ids->clear(); | |
305 DeviceIdMap::iterator it = device_ids_map_.begin(); | |
306 for (; it != device_ids_map_.end(); ++it) { | |
307 // Check if the device matches the type. | |
308 it->first; | |
309 device_ids->push_back(it->second); | |
310 } | |
311 } | |
312 | |
313 GesturePropertyProvider::DeviceId GesturePropertyProvider::GetDeviceId( | |
314 DevicePtr dev, | |
315 const bool do_create) { | |
316 DeviceIdMap::iterator it = device_ids_map_.find(dev); | |
317 if (it != device_ids_map_.end()) | |
318 return it->second; | |
319 if (!do_create) | |
320 return -1; | |
321 | |
322 // Insert a new one if not exists. | |
323 // TODO(sheckylin): Replace this id generation scheme with a better one. | |
324 // The current way may result in memory leaks. | |
325 DeviceId id = device_id_counter_; | |
326 device_ids_map_[dev] = id; | |
327 properties_maps_.set(id, scoped_ptr<PropertyMap>(new PropertyMap)); | |
328 device_id_counter_ = (device_id_counter_ + 1) % kMaxDeviceNum; | |
329 | |
330 // Apply default prop values for the device. | |
331 SetupDefaultProperties(id, dev); | |
332 return id; | |
333 } | |
334 | |
335 void GesturePropertyProvider::GetProperty(const DeviceId device_id, | |
336 const std::string& name, | |
337 DeviceType* type, | |
338 PropertyValuePtr* val, | |
339 size_t* count) { | |
340 // Implement PropertyGet in cmt. | |
341 NOTIMPLEMENTED(); | |
342 } | |
343 | |
344 void GesturePropertyProvider::SetProperty(const DeviceId device_id, | |
345 const std::string& name, | |
346 const DeviceType type, | |
347 const PropertyValuePtr val, | |
348 const size_t count) { | |
349 // Implement PropertySet in cmt. | |
350 NOTIMPLEMENTED(); | |
351 } | |
352 | |
353 GesturePropertyProvider::MatchCriteria::MatchCriteria(const std::string& arg) { | |
354 // TODO(sheckylin): Should we trim all tokens here? | |
355 Tokenize(arg, "|", &args_); | |
356 } | |
357 | |
358 GesturePropertyProvider::MatchProduct::MatchProduct(const std::string& arg) | |
359 : MatchCriteria(arg) { | |
360 } | |
361 | |
362 bool GesturePropertyProvider::MatchProduct::Match(DevicePtr device) { | |
363 if (args_.empty()) | |
364 return true; | |
365 std::string name(device->info.name); | |
366 for (size_t i = 0; i < args_.size(); i++) | |
367 if (name.find(args_[i]) != std::string::npos) | |
368 return true; | |
369 return false; | |
370 } | |
371 | |
372 GesturePropertyProvider::MatchDevicePath::MatchDevicePath( | |
373 const std::string& arg) | |
374 : MatchCriteria(arg) { | |
375 } | |
376 | |
377 bool GesturePropertyProvider::MatchDevicePath::Match(DevicePtr device) { | |
378 if (args_.empty()) | |
379 return true; | |
380 | |
381 // Check if the device path matches any pattern. | |
382 std::string path = GetDeviceNodePath(device); | |
383 if (path.empty()) | |
384 return false; | |
385 for (size_t i = 0; i < args_.size(); i++) | |
386 if (fnmatch(args_[i].c_str(), path.c_str(), FNM_NOESCAPE) == 0) | |
387 return true; | |
388 return false; | |
389 } | |
390 | |
391 GesturePropertyProvider::MatchUSBID::MatchUSBID(const std::string& arg) | |
392 : MatchCriteria(arg) { | |
393 // Check each pattern and split valid ones into vids and pids. | |
394 for (size_t i = 0; i < args_.size(); i++) { | |
395 if (!IsValidPattern(args_[i])) | |
396 continue; | |
397 std::vector<std::string> tokens; | |
398 base::SplitString(args_[i], ':', &tokens); | |
399 vid_patterns_.push_back(base::StringToLowerASCII(tokens[0])); | |
400 pid_patterns_.push_back(base::StringToLowerASCII(tokens[1])); | |
401 } | |
402 } | |
403 | |
404 bool GesturePropertyProvider::MatchUSBID::Match(DevicePtr device) { | |
405 if (vid_patterns_.empty()) | |
406 return true; | |
407 std::string vid = base::StringPrintf("%04x", device->info.id.vendor); | |
408 std::string pid = base::StringPrintf("%04x", device->info.id.product); | |
409 for (size_t i = 0; i < vid_patterns_.size(); i++) { | |
410 if (fnmatch(vid_patterns_[i].c_str(), vid.c_str(), FNM_NOESCAPE) == 0 && | |
411 fnmatch(pid_patterns_[i].c_str(), pid.c_str(), FNM_NOESCAPE) == 0) { | |
412 return true; | |
413 } | |
414 } | |
415 return false; | |
416 } | |
417 | |
418 bool GesturePropertyProvider::MatchUSBID::IsValidPattern( | |
419 const std::string& pattern) { | |
420 // Each USB id should be in the lsusb format, i.e., xxxx:xxxx. We choose to do | |
421 // a lazy check here: if the pattern contains wrong characters not in the hex | |
422 // number range, it won't be matched anyway. | |
423 int number_of_colons = 0; | |
424 size_t pos_of_colon = 0; | |
425 for (size_t i = 0; i < pattern.size(); i++) | |
426 if (pattern[i] == ':') | |
427 ++number_of_colons, pos_of_colon = i; | |
428 return (number_of_colons == 1) && (pos_of_colon != 0) && | |
429 (pos_of_colon != pattern.size() - 1); | |
430 } | |
431 | |
432 GesturePropertyProvider::MatchDeviceType::MatchDeviceType( | |
433 const std::string& arg) | |
434 : MatchCriteria(arg), value_(true), is_valid_(false) { | |
435 // Default value of a match criteria is true. | |
436 if (args_.empty()) | |
437 args_.push_back("on"); | |
438 | |
439 // We care only about the first argument. | |
440 int value = ParseBooleanKeyword(args_[0]); | |
441 if (value) { | |
442 is_valid_ = true; | |
443 value_ = value > 0; | |
444 } | |
445 } | |
446 | |
447 GesturePropertyProvider::MatchIsPointer::MatchIsPointer(const std::string& arg) | |
448 : MatchDeviceType(arg) { | |
449 } | |
450 | |
451 bool GesturePropertyProvider::MatchIsPointer::Match(DevicePtr device) { | |
452 if (!is_valid_) | |
453 return true; | |
454 return (value_ == (device->info.evdev_class == EvdevClassMouse || | |
455 device->info.evdev_class == EvdevClassMultitouchMouse)); | |
456 } | |
457 | |
458 GesturePropertyProvider::MatchIsTouchpad::MatchIsTouchpad( | |
459 const std::string& arg) | |
460 : MatchDeviceType(arg) { | |
461 } | |
462 | |
463 bool GesturePropertyProvider::MatchIsTouchpad::Match(DevicePtr device) { | |
464 if (!is_valid_) | |
465 return true; | |
466 return (value_ == (device->info.evdev_class == EvdevClassTouchpad)); | |
467 } | |
468 | |
469 GesturePropertyProvider::MatchIsTouchscreen::MatchIsTouchscreen( | |
470 const std::string& arg) | |
471 : MatchDeviceType(arg) { | |
472 } | |
473 | |
474 bool GesturePropertyProvider::MatchIsTouchscreen::Match(DevicePtr device) { | |
475 if (!is_valid_) | |
476 return true; | |
477 return (value_ == (device->info.evdev_class == EvdevClassTouchscreen)); | |
478 } | |
479 | |
480 bool GesturePropertyProvider::ConfigurationSection::Match(DevicePtr device) { | |
481 for (size_t i = 0; i < criterias.size(); ++i) | |
482 if (!criterias[i]->Match(device)) | |
483 return false; | |
484 return true; | |
485 } | |
486 | |
487 void GesturePropertyProvider::AddProperty(const DeviceId device_id, | |
488 const std::string& name, | |
489 GesturesProp* prop) { | |
490 // The look-up should never fail because ideally a property can only be | |
491 // created with GesturesPropCreate* functions from the gesture lib side. | |
492 // Therefore, we simply return on failure. | |
493 DevicePropertyMap::iterator it = properties_maps_.find(device_id); | |
494 if (it != properties_maps_.end()) | |
495 it->second->insert(std::make_pair(name, prop)); | |
496 } | |
497 | |
498 void GesturePropertyProvider::DeleteProperty(const DeviceId device_id, | |
499 const std::string& name) { | |
500 DevicePropertyMap::iterator it = properties_maps_.find(device_id); | |
501 if (it != properties_maps_.end()) | |
502 it->second->erase(name); | |
503 } | |
504 | |
505 GesturesProp* GesturePropertyProvider::FindProperty(const DeviceId device_id, | |
506 const std::string& name) { | |
507 return FindProperty(properties_maps_, device_id, name); | |
508 } | |
509 | |
510 GesturesProp* GesturePropertyProvider::FindProperty( | |
511 const DevicePropertyMap& device_property_map, | |
512 const DeviceId device_id, | |
513 const std::string& name) { | |
514 DevicePropertyMap::const_iterator ia = device_property_map.find(device_id); | |
515 if (ia == properties_maps_.end()) | |
516 return NULL; | |
517 PropertyMap::const_iterator ib = ia->second->find(name); | |
518 if (ib == ia->second->end()) | |
519 return NULL; | |
520 return ib->second; | |
521 } | |
522 | |
523 GesturesProp* GesturePropertyProvider::GetDefaultProperty( | |
524 const DeviceId device_id, | |
525 const std::string& name) { | |
526 return FindProperty(default_properties_maps_, device_id, name); | |
527 } | |
528 | |
529 void GesturePropertyProvider::LoadDeviceConfigurations() { | |
530 // Enumerate conf files and sort them lexicographically. | |
531 std::set<base::FilePath> files; | |
532 base::FileEnumerator file_enum(base::FilePath(kConfigurationFilePath), | |
533 false, | |
534 base::FileEnumerator::FILES, | |
535 "*.conf"); | |
536 for (base::FilePath path = file_enum.Next(); !path.empty(); | |
537 path = file_enum.Next()) { | |
538 files.insert(path); | |
539 } | |
540 LOG(INFO_SEVERITY) << files.size() << " conf files were found"; | |
541 | |
542 // Parse conf files one-by-one. | |
543 for (std::set<base::FilePath>::iterator file_iter = files.begin(); | |
544 file_iter != files.end(); | |
545 ++file_iter) { | |
546 LOG(INFO_SEVERITY) << "Parsing conf file: " << (*file_iter).value(); | |
547 std::string content; | |
548 if (!base::ReadFileToString(*file_iter, &content)) { | |
549 LOG(ERROR) << "Can't loading gestures conf file: " | |
550 << (*file_iter).value(); | |
551 continue; | |
552 } | |
553 ParseXorgConfFile(content); | |
554 } | |
555 } | |
556 | |
557 void GesturePropertyProvider::ParseXorgConfFile(const std::string& content) { | |
558 // To simplify the parsing work, we made some assumption about the conf file | |
559 // format which doesn't exist in the original xorg-conf spec. Most important | |
560 // ones are: | |
561 // 1. All keywords and names are now case-sensitive. Also, underscores are not | |
562 // ignored. | |
563 // 2. Each entry takes up one and exactly one line in the file. | |
564 // 3. No negation of the option value even if the option name is prefixed with | |
565 // "No" as it may cause problems for option names that does start with "No" | |
566 // (e.g., "Non-linearity"). | |
567 | |
568 // Break the content into sections, lines and then pieces. | |
569 // Sections are delimited by the "EndSection" keyword. | |
570 // Lines are delimited by "\n". | |
571 // Pieces are delimited by all white-spaces. | |
572 std::vector<std::string> sections; | |
573 base::SplitStringUsingSubstr(content, "EndSection", §ions); | |
574 for (size_t i = 0; i < sections.size(); ++i) { | |
575 // Create a new configuration section. | |
576 configurations_.push_back(ConfigurationSection()); | |
577 ConfigurationSection& config = configurations_.back(); | |
578 | |
579 // Break the section into lines. | |
580 base::StringTokenizer lines(sections[i], "\n"); | |
581 bool is_input_class_section = true; | |
582 bool has_checked_section_type = false; | |
583 while (is_input_class_section && lines.GetNext()) { | |
584 // Parse the line w.r.t. the xorg-conf format. | |
585 std::string line(lines.token()); | |
586 | |
587 // Skip empty lines. | |
588 if (line.empty()) | |
589 continue; | |
590 | |
591 // Treat all whitespaces as delimiters. | |
592 base::StringTokenizer pieces(line, base::kWhitespaceASCII); | |
593 pieces.set_quote_chars("\""); | |
594 bool is_parsing = false; | |
595 bool has_error = false; | |
596 bool next_is_section_type = false; | |
597 bool next_is_option_name = false; | |
598 bool next_is_option_value = false; | |
599 bool next_is_match_criteria = false; | |
600 bool next_is_identifier = false; | |
601 std::string match_type, option_name; | |
602 while (pieces.GetNext()) { | |
603 std::string piece(pieces.token()); | |
604 | |
605 // Skip empty pieces. | |
606 if (piece.empty()) | |
607 continue; | |
608 | |
609 // See if we are currently parsing an entry or are still looking for | |
610 // one. | |
611 if (is_parsing) { | |
612 // Stop parsing the current line if the format is wrong. | |
613 if (piece.size() <= 2 || piece[0] != '\"' || | |
614 piece[piece.size() - 1] != '\"') { | |
615 LOG(ERROR) << "Error parsing line: " << lines.token(); | |
616 has_error = true; | |
617 if (next_is_section_type) | |
618 is_input_class_section = false; | |
619 break; | |
620 } | |
621 | |
622 // Parse the arguments. Note that we don't break even if a whitespace | |
623 // string is passed. It will just be handled in various ways based on | |
624 // the entry type. | |
625 std::string arg; | |
626 base::TrimWhitespaceASCII( | |
627 piece.substr(1, piece.size() - 2), base::TRIM_ALL, &arg); | |
628 if (next_is_section_type) { | |
629 // We only care about InputClass sections. | |
630 if (arg != "InputClass") { | |
631 has_error = true; | |
632 is_input_class_section = false; | |
633 } else { | |
634 LOG(INFO_SEVERITY) << "New InputClass section found"; | |
635 has_checked_section_type = true; | |
636 } | |
637 break; | |
638 } else if (next_is_identifier) { | |
639 LOG(INFO_SEVERITY) << "Identifier: " << arg; | |
640 config.identifier = arg; | |
641 next_is_identifier = false; | |
642 break; | |
643 } else if (next_is_option_name) { | |
644 // TODO(sheckylin): Support option "Ignore". | |
645 option_name = arg; | |
646 next_is_option_value = true; | |
647 next_is_option_name = false; | |
648 } else if (next_is_option_value) { | |
649 GesturesProp prop; | |
650 if(CreateDefaultProperty(option_name, arg, &prop)) | |
651 config.properties.push_back(prop); | |
652 next_is_option_value = false; | |
653 break; | |
654 } else if (next_is_match_criteria) { | |
655 // Skip all match types that are not supported. | |
656 if (IsMatchTypeSupported(match_type)) { | |
657 MatchCriteria *criteria = CreateMatchCriteria(match_type, arg); | |
658 if (criteria) | |
659 config.criterias.push_back(criteria); | |
660 } | |
661 next_is_match_criteria = false; | |
662 break; | |
663 } | |
664 } else { | |
665 // If the section type hasn't been decided yet, look for it. | |
666 // Otherwise, look for valid entries according to the spec. | |
667 if (has_checked_section_type) { | |
668 if (piece == "Driver") { | |
669 // TODO(sheckylin): Support "Driver" so that we can force a device | |
670 // not to use the gesture lib. | |
671 NOTIMPLEMENTED(); | |
672 break; | |
673 } else if (piece == "Identifier") { | |
674 is_parsing = true; | |
675 next_is_identifier = true; | |
676 continue; | |
677 } else if (piece == "Option") { | |
678 is_parsing = true; | |
679 next_is_option_name = true; | |
680 continue; | |
681 } else if (piece.size() > 5 && piece.compare(0, 5, "Match") == 0) { | |
682 match_type = piece; | |
683 is_parsing = true; | |
684 next_is_match_criteria = true; | |
685 continue; | |
686 } | |
687 } else if (piece == "Section") { | |
688 is_parsing = true; | |
689 next_is_section_type = true; | |
690 continue; | |
691 } | |
692 | |
693 // If none of the above is found, check if the current piece starts a | |
694 // comment. | |
695 if (piece.empty() || piece[0] != '#') { | |
696 LOG(ERROR) << "Error parsing line: " << lines.token(); | |
697 has_error = true; | |
698 } | |
699 break; | |
700 } | |
701 } | |
702 | |
703 // The value of a boolean option is skipped (default is true). | |
704 if (!has_error && (next_is_option_value || next_is_match_criteria)) { | |
705 if (next_is_option_value) { | |
706 GesturesProp prop; | |
707 if (CreateDefaultProperty(option_name, "on", &prop)) | |
708 config.properties.push_back(prop); | |
709 } else if (IsMatchTypeSupported(match_type) && | |
710 IsDeviceMatchType(match_type)) { | |
711 MatchCriteria* criteria = CreateMatchCriteria(match_type, "on"); | |
712 if (criteria) | |
713 config.criterias.push_back(criteria); | |
714 } | |
715 } | |
716 } | |
717 | |
718 // Remove useless config sections. | |
719 if (!is_input_class_section || | |
720 (config.criterias.empty() && config.properties.empty())) { | |
721 configurations_.pop_back(); | |
722 } | |
723 } | |
724 } | |
725 | |
726 bool GesturePropertyProvider::IsMatchTypeSupported( | |
727 const std::string& match_type) { | |
728 for (size_t i = 0; i < arraysize(kSupportedMatchTypes); i++) | |
729 if (match_type == kSupportedMatchTypes[i]) | |
730 return true; | |
731 for (size_t i = 0; i < arraysize(kUnsupportedMatchTypes); i++) { | |
732 if (match_type == kUnsupportedMatchTypes[i]) { | |
733 LOG(ERROR) << "Unsupported gestures input class match type: " | |
734 << match_type; | |
735 return false; | |
736 } | |
737 } | |
738 return false; | |
739 } | |
740 | |
741 bool GesturePropertyProvider::IsDeviceMatchType( | |
742 const std::string& match_type) { | |
743 return StartsWithASCII(match_type, "MatchIs", true); | |
744 } | |
745 | |
746 GesturePropertyProvider::MatchCriteria* | |
747 GesturePropertyProvider::CreateMatchCriteria(const std::string& match_type, | |
748 const std::string& arg) { | |
749 LOG(INFO_SEVERITY) << "Creating match criteria: (" << match_type << ", " | |
750 << arg << ")"; | |
751 return (this->*match_criteria_map_[match_type])(arg); | |
752 } | |
753 | |
754 bool GesturePropertyProvider::CreateDefaultProperty(const std::string& name, | |
755 const std::string& value, | |
756 GesturesProp* prop) { | |
757 // Our parsing rule: | |
758 // 1. No hex or oct number is accepted. | |
759 // 2. All numbers will be stored as double. | |
760 // 3. Array elements can be separated by both white-spaces or commas. | |
761 // 4. A token is treated as numeric either if it is one of the special | |
762 // keywords for boolean values (on, true, yes, off, false, no) or if | |
763 // base::StringToDouble succeeds. | |
764 // 5. The property is treated as numeric if and only if all of its elements | |
765 // (if any) are numerics. Otherwise, it will be treated as a string. | |
766 // 6. A string property will be trimmed before storing its value. | |
767 LOG(INFO_SEVERITY) << "Creating default property: (" << name << ", " << value | |
768 << ")"; | |
769 | |
770 // Parse elements one-by-one. | |
771 std::string delimiters(base::kWhitespaceASCII); | |
772 delimiters.append(","); | |
773 base::StringTokenizer tokens(value, delimiters); | |
774 bool is_all_numeric = true; | |
775 std::vector<double> numbers; | |
776 while (tokens.GetNext()) { | |
777 // Skip empty tokens. | |
778 std::string token(tokens.token()); | |
779 if (token.empty()) | |
780 continue; | |
781 | |
782 // Check if it is a boolean keyword. | |
783 int bool_result = ParseBooleanKeyword(token); | |
784 if (bool_result) { | |
785 numbers.push_back(bool_result > 0); | |
786 continue; | |
787 } | |
788 | |
789 // Check if it is a number. | |
790 double real_result; | |
791 bool success = base::StringToDouble(token, &real_result); | |
792 if (!success) { | |
793 is_all_numeric = false; | |
794 break; | |
795 } | |
796 numbers.push_back(real_result); | |
797 } | |
798 | |
799 // Setup GesturesProp data. | |
800 prop->name = name; | |
801 // Arrays need to contain at least one number and may contain numbers only. | |
802 if (is_all_numeric && numbers.size()) { | |
803 prop->type = PT_REAL; | |
804 prop->count = numbers.size(); | |
805 prop->val.r = new double[numbers.size()]; | |
806 std::copy(numbers.begin(), numbers.end(), prop->val.r); | |
807 } else { | |
808 prop->type = PT_STRING; | |
809 prop->count = 1; | |
810 std::string trimmed; | |
811 base::TrimWhitespaceASCII(value, base::TRIM_ALL, &trimmed); | |
812 prop->val.s = new std::string(trimmed); | |
813 } | |
814 | |
815 LOG(INFO_SEVERITY) << "Prop: " << *prop; | |
816 // The function will always succeed for now but it may change later if we | |
817 // specify some name or args as invalid. | |
818 return true; | |
819 } | |
820 | |
821 int GesturePropertyProvider::ParseBooleanKeyword(const std::string& value) { | |
822 for (size_t i = 0; i < arraysize(kTrue); i++) | |
823 if (LowerCaseEqualsASCII(value, kTrue[i])) | |
824 return 1; | |
825 for (size_t i = 0; i < arraysize(kFalse); i++) | |
826 if (LowerCaseEqualsASCII(value, kFalse[i])) | |
827 return -1; | |
828 return 0; | |
829 } | |
830 | |
831 void GesturePropertyProvider::SetupDefaultProperties( | |
832 const DeviceId device_id, | |
833 DevicePtr dev) { | |
834 LOG(INFO_SEVERITY) << "Setting up default properties for (" << dev << ", " | |
835 << device_id << ", " << dev->info.name << ")"; | |
836 | |
837 // Go through all parsed sections. | |
838 scoped_ptr<PropertyMap> prop_map(new PropertyMap); | |
839 for (size_t i = 0; i < configurations_.size(); i++) { | |
840 if (configurations_[i].Match(dev)) { | |
841 LOG(INFO_SEVERITY) << "Conf section \"" << configurations_[i].identifier | |
842 << "\" is matched"; | |
843 for (size_t j = 0; j < configurations_[i].properties.size(); j++) { | |
844 GesturesProp& prop = configurations_[i].properties[j]; | |
845 // We can't use insert here because a property may be set for several | |
846 // times along the way. | |
847 (*prop_map)[prop.name] = ∝ | |
848 } | |
849 } | |
850 } | |
851 default_properties_maps_.set(device_id, prop_map.Pass()); | |
852 } | |
853 | |
854 template <typename T> | |
855 GesturesProp* GesturesPropFunctionsWrapper::Create( | |
856 void* dev, | |
857 const char* name, | |
858 GesturePropertyProvider::PropertyType type, | |
859 T* val, | |
860 size_t count) { | |
861 // Create a new PropertyMap for the device if not exists already. | |
862 GesturePropertyProvider::DeviceId device_id = | |
863 GesturePropertyProvider::GetInstance()->GetDeviceId(dev, true); | |
864 | |
865 /* Insert property and setup property values */ | |
866 | |
867 // First, see if the GesturesProp already exists. | |
868 DVLOG(3) << "Creating Property: \"" << name << "\""; | |
869 GesturesProp* prop = | |
870 GesturePropertyProvider::GetInstance()->FindProperty(device_id, name); | |
871 if (!prop) { | |
872 prop = new GesturesProp(); | |
873 GesturePropertyProvider::GetInstance()->AddProperty(device_id, name, prop); | |
874 } else if (prop->is_allocated) { | |
875 // Free the data pointer if it was allocated here. This shouldn't happen in | |
876 // normal use cases (that a read only property being declared again). | |
877 // However, we want to prevent any possible memory leak if it does happen. | |
878 prop->Free(); | |
879 } | |
880 | |
881 // Set the values. | |
882 prop->name = name; | |
883 prop->type = type; | |
884 prop->count = count; | |
885 | |
886 // Allocate memory for the data if a NULL pointer is provided. | |
887 if (!val) { | |
888 if (count > 1) | |
889 prop->val.v = new T[count]; | |
890 else | |
891 prop->val.v = new T; | |
892 prop->is_allocated = true; | |
893 } else { | |
894 prop->val.v = val; | |
895 } | |
896 return prop; | |
897 } | |
898 | |
899 template <typename T, GesturePropertyProvider::PropertyType type> | |
900 GesturesProp* GesturesPropFunctionsWrapper::CreateProperty(void* dev, | |
901 const char* name, | |
902 T* val, | |
903 size_t count, | |
904 const T* init) { | |
905 GesturesProp* result = Create(dev, name, type, val, count); | |
906 if (!val) | |
907 result->is_managed_externally = false; | |
908 | |
909 // We currently assumed that we won't specify any array property in the | |
910 // configuration files. The code needs to be updated if the assumption becomes | |
911 // invalid in the future. | |
912 if (count == 1) { | |
913 GesturesProp* default_prop = | |
914 GesturePropertyProvider::GetInstance()->GetDefaultProperty( | |
915 GesturePropertyProvider::GetInstance()->GetDeviceId(dev), name); | |
916 if (!default_prop || | |
917 default_prop->type == GesturePropertyProvider::PT_STRING) { | |
918 *(reinterpret_cast<T*>(result->val.v)) = *init; | |
919 } else { | |
920 LOG(INFO_SEVERITY) << "Default property found. Using its value ..."; | |
921 // TODO(sheckylin): Handle value out-of-range (e.g., double to int). | |
922 *(reinterpret_cast<T*>(result->val.v)) = | |
923 static_cast<T>(*(default_prop->val.r)); | |
924 } | |
925 } else { | |
926 memmove(result->val.v, init, count * sizeof(T)); | |
927 } | |
928 | |
929 LOG(INFO_SEVERITY) << "Created active prop: " << *result; | |
930 return result; | |
931 } | |
932 | |
933 GesturesProp* GesturesPropFunctionsWrapper::CreateInt(void* dev, | |
934 const char* name, | |
935 int* val, | |
936 size_t count, | |
937 const int* init) { | |
938 return CreateProperty<int, GesturePropertyProvider::PT_INT>( | |
939 dev, name, val, count, init); | |
940 } | |
941 | |
942 GesturesProp* GesturesPropFunctionsWrapper::CreateShort(void* dev, | |
943 const char* name, | |
944 short* val, | |
945 size_t count, | |
946 const short* init) { | |
947 return CreateProperty<short, GesturePropertyProvider::PT_SHORT>( | |
948 dev, name, val, count, init); | |
949 } | |
950 | |
951 GesturesProp* GesturesPropFunctionsWrapper::CreateBool( | |
952 void* dev, | |
953 const char* name, | |
954 GesturesPropBool* val, | |
955 size_t count, | |
956 const GesturesPropBool* init) { | |
957 return CreateProperty<GesturesPropBool, GesturePropertyProvider::PT_BOOL>( | |
958 dev, name, val, count, init); | |
959 } | |
960 | |
961 GesturesProp* GesturesPropFunctionsWrapper::CreateReal(void* dev, | |
962 const char* name, | |
963 double* val, | |
964 size_t count, | |
965 const double* init) { | |
966 return CreateProperty<double, GesturePropertyProvider::PT_REAL>( | |
967 dev, name, val, count, init); | |
968 } | |
969 | |
970 GesturesProp* GesturesPropFunctionsWrapper::CreateString(void* dev, | |
971 const char* name, | |
972 const char** val, | |
973 const char* init) { | |
974 // StringProperty's memory is always allocated on this side instead of | |
975 // externally in the gesture lib as the original one will be destroyed right | |
976 // after the constructor call (check the design of StringProperty). To do | |
977 // this, we call the Create function with NULL pointer so that it always | |
978 // allocates. | |
979 GesturesProp* result = Create(dev, | |
980 name, | |
981 GesturePropertyProvider::PT_STRING, | |
982 static_cast<std::string*>(NULL), | |
983 1); | |
984 if (!val) | |
985 result->is_managed_externally = false; | |
986 | |
987 GesturesProp* default_prop = | |
988 GesturePropertyProvider::GetInstance()->GetDefaultProperty( | |
989 GesturePropertyProvider::GetInstance()->GetDeviceId(dev), name); | |
990 | |
991 // Setup its value just like the other data types. | |
992 if (!default_prop || | |
993 default_prop->type != GesturePropertyProvider::PT_STRING) { | |
994 *(result->val.s) = init; | |
995 } else { | |
996 LOG(INFO_SEVERITY) << "Default property found. Using its value ..."; | |
997 *(result->val.s) = *(default_prop->val.s); | |
998 } | |
999 | |
1000 // If the provided pointer is not NULL, replace its content | |
1001 // (val_ of StringProperty) with the address of our allocated string. | |
1002 // Note that we don't have to do this for the other data types as they will | |
1003 // use the original data pointer if possible and it is unnecessary to do so | |
1004 // if the pointer is NULL. | |
1005 if (val) { | |
1006 *(val) = result->val.s->c_str(); | |
1007 result->write_back = val; | |
1008 } | |
1009 | |
1010 LOG(INFO_SEVERITY) << "Created active prop: " << *result; | |
1011 return result; | |
1012 } | |
1013 | |
1014 void GesturesPropFunctionsWrapper::RegisterHandlers( | |
1015 void* priv, | |
1016 GesturesProp* prop, | |
1017 void* handler_data, | |
1018 GesturesPropGetHandler get, | |
1019 GesturesPropSetHandler set) { | |
1020 // Sanity checks | |
1021 if (!priv || !prop) | |
1022 return; | |
1023 | |
1024 prop->handler_data = handler_data; | |
1025 prop->get = get; | |
1026 prop->set = set; | |
1027 } | |
1028 | |
1029 void GesturesPropFunctionsWrapper::Free(void* priv, GesturesProp* prop) { | |
1030 if (!prop) | |
1031 return; | |
1032 | |
1033 GesturePropertyProvider::DeviceId device_id = | |
1034 GesturePropertyProvider::GetInstance()->GetDeviceId(priv); | |
1035 if (device_id < 0) | |
1036 return; | |
1037 | |
1038 DVLOG(3) << "Freeing Property: \"" << prop->name << "\""; | |
1039 GesturePropertyProvider::GetInstance()->DeleteProperty(device_id, prop->name); | |
1040 if (prop->is_allocated) | |
1041 prop->Free(); | |
1042 delete prop; | |
1043 } | |
1044 | |
1045 GesturesProp* GesturesPropFunctionsWrapper::CreateIntSingle( | |
1046 void* dev, | |
1047 const char* name, | |
1048 int* val, | |
1049 int init) { | |
1050 return CreateInt(dev, name, val, 1, &init); | |
1051 } | |
1052 | |
1053 GesturesProp* GesturesPropFunctionsWrapper::CreateBoolSingle( | |
1054 void* dev, | |
1055 const char* name, | |
1056 GesturesPropBool* val, | |
1057 GesturesPropBool init) { | |
1058 return CreateBool(dev, name, val, 1, &init); | |
1059 } | |
1060 | |
1061 bool GesturesPropFunctionsWrapper::InitializeDeviceProperties( | |
1062 void* device, GesturePropertyProvider::DeviceProperty* props) { | |
1063 if (!device) | |
1064 return false; | |
1065 GesturePropertyProvider::DevicePtr dev = | |
1066 static_cast<GesturePropertyProvider::DevicePtr>(device); | |
1067 | |
1068 /* Create Device Properties */ | |
1069 | |
1070 // Read Only properties. | |
1071 CreateString(dev, "Device Node", NULL, GetDeviceNodePath(dev).c_str()); | |
1072 CreateShort(dev, | |
1073 "Device Vendor ID", | |
1074 NULL, | |
1075 1, | |
1076 reinterpret_cast<short*>(&(dev->info.id.vendor))); | |
1077 CreateShort(dev, | |
1078 "Device Product ID", | |
1079 NULL, | |
1080 1, | |
1081 reinterpret_cast<short*>(&(dev->info.id.product))); | |
1082 | |
1083 // Useable trackpad area. If not configured in .conf file, | |
1084 // use x/y valuator min/max as reported by kernel driver. | |
1085 CreateIntSingle( | |
1086 dev, "Active Area Left", &props->area_left, Event_Get_Left(dev)); | |
1087 CreateIntSingle( | |
1088 dev, "Active Area Right", &props->area_right, Event_Get_Right(dev)); | |
1089 CreateIntSingle(dev, "Active Area Top", &props->area_top, Event_Get_Top(dev)); | |
1090 CreateIntSingle( | |
1091 dev, "Active Area Bottom", &props->area_bottom, Event_Get_Bottom(dev)); | |
1092 | |
1093 // Trackpad resolution (pixels/mm). If not configured in .conf file, | |
1094 // use x/y resolution as reported by kernel driver. | |
1095 CreateIntSingle( | |
1096 dev, "Vertical Resolution", &props->res_y, Event_Get_Res_Y(dev)); | |
1097 CreateIntSingle( | |
1098 dev, "Horizontal Resolution", &props->res_x, Event_Get_Res_X(dev)); | |
1099 | |
1100 // Trackpad orientation minimum/maximum. If not configured in .conf file, | |
1101 // use min/max as reported by kernel driver. | |
1102 CreateIntSingle(dev, | |
1103 "Orientation Minimum", | |
1104 &props->orientation_minimum, | |
1105 Event_Get_Orientation_Minimum(dev)); | |
1106 CreateIntSingle(dev, | |
1107 "Orientation Maximum", | |
1108 &props->orientation_maximum, | |
1109 Event_Get_Orientation_Maximum(dev)); | |
1110 | |
1111 // Log dump property. Will call Event_Dump_Debug_Log when its value is being | |
1112 // set. | |
1113 GesturesProp* dump_debug_log_prop = | |
1114 CreateBoolSingle(dev, "Dump Debug Log", &props->dump_debug_log, false); | |
1115 RegisterHandlers(dev, dump_debug_log_prop, dev, NULL, Event_Dump_Debug_Log); | |
1116 | |
1117 // Whether to do the gesture recognition or just passing the multi-touch data | |
1118 // to upper layers. | |
1119 CreateBoolSingle( | |
1120 dev, "Raw Touch Passthrough", &props->raw_passthrough, false); | |
1121 return true; | |
1122 } | |
1123 | |
1124 /* Global GesturesPropProvider | |
1125 * | |
1126 * Used by PropRegistry in GestureInterpreter to forward property value | |
1127 * creations from there. | |
1128 * */ | |
1129 const GesturesPropProvider kGesturePropProvider = { | |
1130 GesturesPropFunctionsWrapper::CreateInt, | |
1131 GesturesPropFunctionsWrapper::CreateShort, | |
1132 GesturesPropFunctionsWrapper::CreateBool, | |
1133 GesturesPropFunctionsWrapper::CreateString, | |
1134 GesturesPropFunctionsWrapper::CreateReal, | |
1135 GesturesPropFunctionsWrapper::RegisterHandlers, | |
1136 GesturesPropFunctionsWrapper::Free}; | |
1137 | |
1138 } // namespace ui | |
OLD | NEW |