1 /*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <algorithm>
17 #include <cerrno>
18 #include <climits>
19 #include <cstring>
20 #include <fstream>
21 #include <iostream>
22 #include <iterator>
23 #include <optional>
24 #include <sstream>
25 #include <string>
26 #include <unordered_map>
27 #include <vector>
28
29 #include <unistd.h>
30
31 #include <libudev.h>
32 #include <linux/input.h>
33
34 #include "mmi_log.h"
35
36 #undef MMI_LOG_DOMAIN
37 #define MMI_LOG_DOMAIN MMI_LOG_SERVER
38 #undef MMI_LOG_TAG
39 #define MMI_LOG_TAG "MmiLibudev"
40
41 using namespace std::literals;
42 namespace {
43 constexpr int UTIL_PATH_SIZE { 1024 };
44 constexpr int UTIL_LINE_SIZE { 16384 };
45
StartsWith(std::string_view str,std::string_view prefix)46 bool StartsWith(std::string_view str, std::string_view prefix)
47 {
48 return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
49 }
50
ChopTail(std::string_view & str,char sep)51 bool ChopTail(std::string_view &str, char sep)
52 {
53 auto pos = str.rfind(sep);
54 if (pos == std::string_view::npos) {
55 return false;
56 }
57 str.remove_suffix(str.size() - pos);
58 return true;
59 }
60
ResolveSymLink(const std::string & syspath)61 std::string ResolveSymLink(const std::string &syspath)
62 {
63 constexpr auto backStr = "../"sv;
64 char linkTarget[UTIL_PATH_SIZE];
65
66 ssize_t len = readlink(syspath.c_str(), linkTarget, sizeof(linkTarget));
67 if (len <= 0 || len == static_cast<ssize_t>(sizeof(linkTarget))) {
68 return syspath;
69 }
70
71 std::string_view tail{ linkTarget, len };
72 int back = 0;
73 for (; StartsWith(tail, backStr); back++) {
74 tail.remove_prefix(backStr.size());
75 }
76
77 std::string_view base = syspath;
78 for (int i = 0; i <= back; i++) {
79 if (!ChopTail(base, '/')) {
80 return syspath;
81 }
82 }
83
84 return std::string{ base }.append("/").append(tail);
85 }
86
GetLinkValue(const std::string & slink,const std::string & syspath)87 std::optional<std::string> GetLinkValue(const std::string &slink, const std::string &syspath)
88 {
89 auto path = syspath + "/" + slink;
90
91 char target[UTIL_PATH_SIZE];
92 ssize_t len = readlink(path.c_str(), target, sizeof(target));
93 if (len <= 0 || len == static_cast<ssize_t>(sizeof(target))) {
94 MMI_HILOGE("Failed to read link");
95 return std::nullopt;
96 }
97
98 std::string_view result{ target, len };
99 auto pos = result.rfind('/');
100 if (pos == std::string_view::npos) {
101 MMI_HILOGE("Failed to get link value");
102 return std::nullopt;
103 }
104 return std::string{ result.substr(pos + 1) };
105 }
106
107 class BitVector {
108 public:
109 // This type depends on kernel definition
110 using val_t = unsigned long;
111
112 // Input string is hexadecimal 64-bit numbers separated by spaces with high bit number first
BitVector(const std::string & str)113 explicit BitVector(const std::string &str)
114 {
115 std::istringstream ss{ str };
116 ss >> std::hex;
117 std::copy(std::istream_iterator<val_t>(ss), std::istream_iterator<val_t>(), std::back_inserter(bits_));
118 // Since numbers in string starts with high number we need to reverse vector to count numbers from low to high
119 std::reverse(bits_.begin(), bits_.end());
120 }
121
CheckBit(size_t idx) const122 [[nodiscard]] bool CheckBit(size_t idx) const
123 {
124 auto vidx = idx / (sizeof(val_t) * CHAR_BIT);
125 auto bidx = idx % (sizeof(val_t) * CHAR_BIT);
126 if (vidx >= bits_.size()) {
127 return false;
128 }
129 return (bits_[vidx] & (1ULL << bidx)) != 0;
130 }
131
132 private:
133 std::vector<val_t> bits_;
134 };
135 } // namespace
136
137 struct udev {};
138
139 struct udev_device {
140 public:
141 // Not copyable and not movable
142 udev_device(udev_device &) = delete;
143 udev_device(udev_device &&) = delete;
144 udev_device &operator = (udev_device &) = delete;
145 udev_device &operator = (udev_device &&) = delete;
146
NewFromSyspathudev_device147 static udev_device *NewFromSyspath(const std::string &syspathParam)
148 {
149 // path starts in sys
150 if (!StartsWith(syspathParam, "/sys/") || syspathParam.back() == '/') {
151 errno = EINVAL;
152 return nullptr;
153 }
154
155 // resolve possible symlink to real path
156 std::string path = ResolveSymLink(syspathParam);
157 if (StartsWith(path, "/sys/devices/")) {
158 // all "devices" require a "uevent" file
159 struct stat statbuf;
160 std::string filename = path + "/uevent";
161 if (stat(filename.c_str(), &statbuf) != 0) {
162 return nullptr;
163 }
164 } else {
165 return nullptr;
166 }
167
168 auto *inst = new udev_device;
169 inst->SetSyspath(std::move(path));
170
171 return inst;
172 }
173
NewFromDevnumudev_device174 static udev_device *NewFromDevnum(char type, dev_t devnum)
175 {
176 const char *typeStr = nullptr;
177
178 if (type == 'b') {
179 typeStr = "block";
180 } else if (type == 'c') {
181 typeStr = "char";
182 } else {
183 MMI_HILOGE("Param invalid");
184 errno = EINVAL;
185 return nullptr;
186 }
187
188 // use /sys/dev/{block,char}/<maj>:<min> link
189 auto majStr = std::to_string(major(devnum));
190 auto minStr = std::to_string(minor(devnum));
191 return NewFromSyspath("/sys/dev/"s + typeStr + "/" + majStr + ":" + minStr);
192 }
193
Refudev_device194 void Ref()
195 {
196 refcount++;
197 }
198
Unrefudev_device199 void Unref()
200 {
201 if (--refcount <= 0) {
202 delete this;
203 }
204 }
205
GetParentudev_device206 udev_device *GetParent()
207 {
208 if (!parentDevice_.has_value()) {
209 parentDevice_ = NewFromChild(this);
210 }
211 return *parentDevice_;
212 }
213
GetSyspathudev_device214 const std::string &GetSyspath() const
215 {
216 return syspath;
217 }
218
GetSysnameudev_device219 const std::string &GetSysname() const
220 {
221 return sysname;
222 }
223
GetDevnodeudev_device224 const std::string &GetDevnode()
225 {
226 return GetProperty("DEVNAME");
227 }
228
IsInitializedudev_device229 bool IsInitialized()
230 {
231 if (!ueventLoaded) {
232 ReadUeventFile();
233 }
234 return ueventLoaded;
235 }
236
GetParentWithSubsystemudev_device237 udev_device *GetParentWithSubsystem(const std::string &subsystem)
238 {
239 udev_device *parent = GetParent();
240 while (parent != nullptr) {
241 auto parentSubsystem = parent->GetSubsystem();
242 if (parentSubsystem.has_value() && parentSubsystem.value() == subsystem) {
243 break;
244 }
245 parent = parent->GetParent();
246 }
247
248 if (parent == nullptr) {
249 errno = ENOENT;
250 }
251 return parent;
252 }
253
HasPropertyudev_device254 bool HasProperty(const std::string &key)
255 {
256 if (!ueventLoaded) {
257 ReadUeventFile();
258 }
259 return property_.find(key) != property_.end();
260 }
261
GetPropertyudev_device262 const std::string &GetProperty(const std::string &key)
263 {
264 if (!ueventLoaded) {
265 ReadUeventFile();
266 }
267 return property_[key];
268 }
269
270 private:
271 udev_device() = default;
272
~udev_deviceudev_device273 ~udev_device()
274 {
275 if (parentDevice_.has_value() && parentDevice_.value() != nullptr) {
276 parentDevice_.value()->Unref();
277 }
278 }
279
NewFromChildudev_device280 static udev_device *NewFromChild(udev_device *child)
281 {
282 std::string_view path = child->GetSyspath();
283
284 while (true) {
285 if (!ChopTail(path, '/')) {
286 break;
287 }
288 udev_device *parent = NewFromSyspath(std::string{ path });
289 if (parent != nullptr) {
290 return parent;
291 }
292 }
293
294 return nullptr;
295 }
296
SetSyspathudev_device297 void SetSyspath(std::string newSyspath)
298 {
299 syspath = std::move(newSyspath);
300
301 AddProperty("DEVPATH", syspath.substr(0, "/sys"sv.size()));
302
303 auto pos = syspath.rfind('/');
304 if (pos == std::string::npos) {
305 return;
306 }
307 sysname = syspath.substr(pos + 1);
308
309 // some devices have '!' in their name, change that to '/'
310 for (char &c : sysname) {
311 if (c == '!') {
312 c = '/';
313 }
314 }
315 }
316
AddPropertyFromStringudev_device317 void AddPropertyFromString(const std::string &line)
318 {
319 auto pos = line.find('=');
320 if (pos == std::string::npos) {
321 return;
322 }
323 std::string key = line.substr(0, pos);
324 if (key == "DEVNAME") {
325 SetDevnode(line.substr(pos + 1));
326 return;
327 }
328 AddProperty(std::move(key), line.substr(pos + 1));
329 }
330
ReadUeventFileudev_device331 void ReadUeventFile()
332 {
333 if (ueventLoaded) {
334 return;
335 }
336
337 auto filename = syspath + "/uevent";
338 char realPath[PATH_MAX] = {};
339 CHKPV(realpath(filename.c_str(), realPath));
340 std::ifstream f(realPath, std::ios_base::in);
341 if (!f.is_open()) {
342 MMI_HILOGE("ReadUeventFile(): path:%{private}s, error:%{public}s", realPath, std::strerror(errno));
343 return;
344 }
345 ueventLoaded = true;
346
347 char line[UTIL_LINE_SIZE];
348 while (f.getline(line, sizeof(line))) {
349 AddPropertyFromString(line);
350 }
351
352 CheckInputProperties();
353 }
354
CheckAcceludev_device355 bool CheckAccel(const BitVector &ev, const BitVector &abs, const BitVector &prop)
356 {
357 bool hasKeys = ev.CheckBit(EV_KEY);
358 bool has3dCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y) && abs.CheckBit(ABS_Z);
359 bool isAccelerometer = prop.CheckBit(INPUT_PROP_ACCELEROMETER);
360
361 if (!hasKeys && has3dCoordinates) {
362 isAccelerometer = true;
363 }
364
365 if (isAccelerometer) {
366 SetInputProperty("ID_INPUT_ACCELEROMETER");
367 }
368 return isAccelerometer;
369 }
370
HasJoystickAxesOrButtonsudev_device371 bool HasJoystickAxesOrButtons(const BitVector &abs, const BitVector &key)
372 {
373 bool hasJoystickAxesOrButtons = false;
374 // Some mouses have so much buttons that they overflow in joystick range, ignore them
375 if (!key.CheckBit(BTN_JOYSTICK - 1)) {
376 for (int button = BTN_JOYSTICK; button < BTN_DIGI && !hasJoystickAxesOrButtons; button++) {
377 hasJoystickAxesOrButtons = key.CheckBit(button);
378 }
379 for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40 && !hasJoystickAxesOrButtons;
380 button++) {
381 hasJoystickAxesOrButtons = key.CheckBit(button);
382 }
383 for (int button = BTN_DPAD_UP; button <= BTN_DPAD_RIGHT && !hasJoystickAxesOrButtons; button++) {
384 hasJoystickAxesOrButtons = key.CheckBit(button);
385 }
386 }
387 for (int axis = ABS_RX; axis < ABS_PRESSURE && !hasJoystickAxesOrButtons; axis++) {
388 hasJoystickAxesOrButtons = abs.CheckBit(axis);
389 }
390 return hasJoystickAxesOrButtons;
391 }
392
CheckPointingStickudev_device393 bool CheckPointingStick(const BitVector &prop)
394 {
395 if (prop.CheckBit(INPUT_PROP_POINTING_STICK)) {
396 SetInputProperty("ID_INPUT_POINTINGSTICK");
397 return true;
398 }
399 return false;
400 }
401
CheckAndSetPropudev_device402 void CheckAndSetProp(std::string prop, const bool &flag)
403 {
404 if (flag) {
405 SetInputProperty(prop);
406 MMI_HILOGD("device has prop with %{public}s", prop.c_str());
407 }
408 }
409
CheckMouseButtonudev_device410 void CheckMouseButton(const BitVector &key, bool &flag)
411 {
412 for (int button = BTN_MOUSE; button < BTN_JOYSTICK && !flag; button++) {
413 flag = key.CheckBit(button);
414 }
415 }
416
UpdateProByKeyudev_device417 void UpdateProByKey(const BitVector &key, const bool &isDirect, bool &probablyTablet, bool &probablyTouchpad,
418 bool &probablyTouchscreen)
419 {
420 probablyTablet = key.CheckBit(BTN_STYLUS) || key.CheckBit(BTN_TOOL_PEN);
421 probablyTouchpad = key.CheckBit(BTN_TOOL_FINGER) && !key.CheckBit(BTN_TOOL_PEN) && !isDirect;
422 probablyTouchscreen = key.CheckBit(BTN_TOUCH) && isDirect;
423 }
424
CheckMtCoordinatesudev_device425 bool CheckMtCoordinates(const BitVector &abs)
426 {
427 bool hasMtCoordinates = abs.CheckBit(ABS_MT_POSITION_X) && abs.CheckBit(ABS_MT_POSITION_Y);
428 /* unset hasMtCoordinates if devices claims to have all abs axis */
429 if (hasMtCoordinates && abs.CheckBit(ABS_MT_SLOT) && abs.CheckBit(ABS_MT_SLOT - 1)) {
430 hasMtCoordinates = false;
431 }
432 return hasMtCoordinates;
433 }
434
UpdateProByStatusudev_device435 void UpdateProByStatus(const bool &isMouse, const bool &isTouchpad, const bool &isTouchscreen,
436 const bool &isJoystick, const bool &isTablet)
437 {
438 CheckAndSetProp("ID_INPUT_MOUSE", isMouse);
439 CheckAndSetProp("ID_INPUT_TOUCHPAD", isTouchpad);
440 CheckAndSetProp("ID_INPUT_TOUCHSCREEN", isTouchscreen);
441 CheckAndSetProp("ID_INPUT_JOYSTICK", isJoystick);
442 CheckAndSetProp("ID_INPUT_TABLET", isTablet);
443 }
444
CheckPointersudev_device445 bool CheckPointers(const BitVector &ev, const BitVector &abs, const BitVector &key, const BitVector &rel,
446 const BitVector &prop)
447 {
448 bool isDirect = prop.CheckBit(INPUT_PROP_DIRECT);
449 bool hasAbsCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y);
450 bool hasRelCoordinates = ev.CheckBit(EV_REL) && rel.CheckBit(REL_X) && rel.CheckBit(REL_Y);
451 bool hasMtCoordinates = CheckMtCoordinates(abs);
452
453 bool hasMouseButton = false;
454 CheckMouseButton(key, hasMouseButton);
455
456 bool probablyTablet;
457 bool probablyTouchpad;
458 bool probablyTouchscreen;
459 UpdateProByKey(key, isDirect, probablyTablet, probablyTouchpad, probablyTouchscreen);
460 bool probablyJoystick = HasJoystickAxesOrButtons(abs, key);
461
462 bool isTablet = false;
463 bool isMouse = false;
464 bool isTouchpad = false;
465 bool isTouchscreen = false;
466 bool isJoystick = false;
467 if (hasAbsCoordinates) {
468 if (probablyTablet) {
469 isTablet = true;
470 } else if (probablyTouchpad) {
471 isTouchpad = true;
472 } else if (hasMouseButton) {
473 /* This path is taken by VMware's USB mouse, which has
474 * absolute axes, but no touch/pressure button. */
475 isMouse = true;
476 } else if (probablyTouchscreen) {
477 isTouchscreen = true;
478 } else {
479 isJoystick = probablyJoystick;
480 }
481 } else {
482 isJoystick = probablyJoystick;
483 }
484
485 if (hasMtCoordinates) {
486 if (probablyTablet) {
487 isTablet = true;
488 } else if (probablyTouchpad) {
489 isTouchpad = true;
490 } else if (probablyTouchscreen) {
491 isTouchscreen = true;
492 }
493 }
494
495 /* mouse buttons and no axis */
496 if (!isTablet && !isTouchpad && !isJoystick && hasMouseButton && (hasRelCoordinates || !hasAbsCoordinates)) {
497 isMouse = true;
498 }
499
500 UpdateProByStatus(isMouse, isTouchpad, isTouchscreen, isJoystick, isTablet);
501
502 return isTablet || isMouse || isTouchpad || isTouchscreen || isJoystick || CheckPointingStick(prop);
503 }
504
CheckKeysudev_device505 bool CheckKeys(const BitVector &ev, const BitVector &key)
506 {
507 if (!ev.CheckBit(EV_KEY)) {
508 return false;
509 }
510
511 /* only consider KEY_* here, not BTN_* */
512 bool found = false;
513 for (int i = 0; i < BTN_MISC && !found; ++i) {
514 found = key.CheckBit(i);
515 }
516 /* If there are no keys in the lower block, check the higher blocks */
517 for (int i = KEY_OK; i < BTN_DPAD_UP && !found; ++i) {
518 found = key.CheckBit(i);
519 }
520 for (int i = KEY_ALS_TOGGLE; i < BTN_TRIGGER_HAPPY && !found; ++i) {
521 found = key.CheckBit(i);
522 }
523
524 if (found) {
525 SetInputProperty("ID_INPUT_KEY");
526 }
527
528 /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
529 * those, consider it a full keyboard; do not test KEY_RESERVED, though */
530 bool isKeyboard = true;
531 for (int i = KEY_ESC; i < KEY_D && isKeyboard; i++) {
532 isKeyboard = key.CheckBit(i);
533 }
534 if (isKeyboard) {
535 SetInputProperty("ID_INPUT_KEYBOARD");
536 }
537
538 return found || isKeyboard;
539 }
540
SetInputPropertyudev_device541 void SetInputProperty(std::string prop)
542 {
543 AddProperty("ID_INPUT", "1");
544 AddProperty(std::move(prop), "1");
545 }
546
CheckInputPropertiesudev_device547 void CheckInputProperties()
548 {
549 BitVector ev{ GetProperty("EV") };
550 BitVector abs{ GetProperty("ABS") };
551 BitVector key{ GetProperty("KEY") };
552 BitVector rel{ GetProperty("REL") };
553 BitVector prop{ GetProperty("PROP") };
554
555 bool isPointer = CheckAccel(ev, abs, prop) || CheckPointers(ev, abs, key, rel, prop);
556 bool isKey = CheckKeys(ev, key);
557 /* Some evdev nodes have only a scrollwheel */
558 if (!isPointer && !isKey && ev.CheckBit(EV_REL) && (rel.CheckBit(REL_WHEEL) || rel.CheckBit(REL_HWHEEL))) {
559 SetInputProperty("ID_INPUT_KEY");
560 }
561 if (ev.CheckBit(EV_SW)) {
562 SetInputProperty("ID_INPUT_SWITCH");
563 }
564 }
565
SetDevnodeudev_device566 void SetDevnode(std::string newDevnode)
567 {
568 if (newDevnode[0] != '/') {
569 newDevnode = "/dev/" + newDevnode;
570 }
571 AddProperty("DEVNAME", std::move(newDevnode));
572 }
573
AddPropertyudev_device574 void AddProperty(std::string key, std::string value)
575 {
576 property_[std::move(key)] = std::move(value);
577 }
578
GetSubsystemudev_device579 std::optional<std::string> GetSubsystem()
580 {
581 if (!subsystem_.has_value()) {
582 auto res = GetLinkValue("subsystem", syspath);
583 // read "subsystem" link
584 if (res.has_value()) {
585 SetSubsystem(std::move(*res));
586 return subsystem_;
587 }
588 subsystem_ = "";
589 }
590 return subsystem_;
591 }
592
SetSubsystemudev_device593 void SetSubsystem(std::string newSubsystem)
594 {
595 subsystem_ = newSubsystem;
596 AddProperty("SUBSYSTEM", std::move(newSubsystem));
597 }
598
599 private:
600 int refcount = 1;
601 std::string syspath;
602 std::string sysname;
603
604 std::optional<udev_device *> parentDevice_;
605 std::optional<std::string> subsystem_;
606
607 bool ueventLoaded = false;
608 std::unordered_map<std::string, std::string> property_;
609 };
610
611 // C-style interface
612
udev_new(void)613 udev *udev_new(void)
614 {
615 static udev instance{};
616 return &instance;
617 }
618
619 udev *udev_unref([[maybe_unused]] udev *udev)
620 {
621 return nullptr;
622 }
623
udev_device_ref(udev_device * device)624 udev_device *udev_device_ref(udev_device *device)
625 {
626 CHKPP(device);
627 device->Ref();
628 return device;
629 }
630
udev_device_unref(udev_device * device)631 udev_device *udev_device_unref(udev_device *device)
632 {
633 CHKPP(device);
634 device->Unref();
635 return nullptr;
636 }
637
udev_device_get_udev(udev_device * device)638 udev *udev_device_get_udev(udev_device *device)
639 {
640 CHKPP(device);
641 return udev_new();
642 }
643
udev_device_new_from_syspath(udev * udev,const char * syspath)644 udev_device *udev_device_new_from_syspath(udev *udev, const char *syspath)
645 {
646 if (udev == nullptr || syspath == nullptr) {
647 errno = EINVAL;
648 return nullptr;
649 }
650 return udev_device::NewFromSyspath(syspath);
651 }
652
udev_device_new_from_devnum(udev * udev,char type,dev_t devnum)653 udev_device *udev_device_new_from_devnum(udev *udev, char type, dev_t devnum)
654 {
655 if (udev == nullptr) {
656 errno = EINVAL;
657 return nullptr;
658 }
659 return udev_device::NewFromDevnum(type, devnum);
660 }
661
udev_device_get_parent(udev_device * device)662 udev_device *udev_device_get_parent(udev_device *device)
663 {
664 if (device == nullptr) {
665 errno = EINVAL;
666 return nullptr;
667 }
668 return device->GetParent();
669 }
670
udev_device_get_parent_with_subsystem_devtype(udev_device * device,const char * subsystem,const char * devtype)671 udev_device *udev_device_get_parent_with_subsystem_devtype(udev_device *device, const char *subsystem,
672 const char *devtype)
673 {
674 CHKPP(device);
675 if (subsystem == nullptr) {
676 errno = EINVAL;
677 return nullptr;
678 }
679 // Searching with specific devtype is not supported, since not used by libinput
680 CHKPP(devtype);
681 return device->GetParentWithSubsystem(subsystem);
682 }
683
udev_device_get_syspath(udev_device * device)684 const char *udev_device_get_syspath(udev_device *device)
685 {
686 CHKPP(device);
687 return device->GetSyspath().c_str();
688 }
689
udev_device_get_sysname(udev_device * device)690 const char *udev_device_get_sysname(udev_device *device)
691 {
692 CHKPP(device);
693 return device->GetSysname().c_str();
694 }
695
udev_device_get_devnode(udev_device * device)696 const char *udev_device_get_devnode(udev_device *device)
697 {
698 CHKPP(device);
699 return device->GetDevnode().c_str();
700 }
701
udev_device_get_is_initialized(udev_device * device)702 int udev_device_get_is_initialized(udev_device *device)
703 {
704 return (device != nullptr) ? static_cast<int>(device->IsInitialized()) : -1;
705 }
706
udev_device_get_property_value(udev_device * device,const char * key)707 const char *udev_device_get_property_value(udev_device *device, const char *key)
708 {
709 CHKPP(device);
710 CHKPP(key);
711 std::string skey{ key };
712 if (!device->HasProperty(key)) {
713 return nullptr;
714 }
715 return device->GetProperty(key).c_str();
716 }
717