1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qevdevkeyboardhandler_p.h"
41
42#include <qplatformdefs.h>
43
44#include <QFile>
45#include <QSocketNotifier>
46#include <QStringList>
47#include <QCoreApplication>
48#include <QLoggingCategory>
49#include <qpa/qwindowsysteminterface.h>
50#include <private/qcore_unix_p.h>
51
52#include <QtGui/private/qguiapplication_p.h>
53#include <QtGui/private/qinputdevicemanager_p.h>
54
55#ifdef Q_OS_FREEBSD
56#include <dev/evdev/input.h>
57#else
58#include <linux/input.h>
59#endif
60
61#ifndef input_event_sec
62#define input_event_sec time.tv_sec
63#endif
64
65#ifndef input_event_usec
66#define input_event_usec time.tv_usec
67#endif
68
69QT_BEGIN_NAMESPACE
70
71Q_LOGGING_CATEGORY(qLcEvdevKey, "qt.qpa.input")
72Q_LOGGING_CATEGORY(qLcEvdevKeyMap, "qt.qpa.input.keymap")
73
74// simple builtin US keymap
75#include "qevdevkeyboard_defaultmap_p.h"
76
77void QFdContainer::reset() noexcept
78{
79 if (m_fd >= 0)
80 qt_safe_close(fd: m_fd);
81 m_fd = -1;
82}
83
84QEvdevKeyboardHandler::QEvdevKeyboardHandler(const QString &device, QFdContainer &fd, bool disableZap, bool enableCompose, const QString &keymapFile)
85 : m_device(device), m_fd(fd.release()), m_notify(nullptr),
86 m_modifiers(0), m_composing(0), m_dead_unicode(0xffff),
87 m_langLock(0), m_no_zap(disableZap), m_do_compose(enableCompose),
88 m_keymap(0), m_keymap_size(0), m_keycompose(0), m_keycompose_size(0)
89{
90 qCDebug(qLcEvdevKey) << "Create keyboard handler with for device" << device;
91
92 setObjectName(QLatin1String("LinuxInput Keyboard Handler"));
93
94 memset(s: m_locks, c: 0, n: sizeof(m_locks));
95
96 if (keymapFile.isEmpty() || !loadKeymap(file: keymapFile))
97 unloadKeymap();
98
99 // socket notifier for events on the keyboard device
100 m_notify = new QSocketNotifier(m_fd.get(), QSocketNotifier::Read, this);
101 connect(sender: m_notify, signal: &QSocketNotifier::activated, receiver: this, slot: &QEvdevKeyboardHandler::readKeycode);
102}
103
104QEvdevKeyboardHandler::~QEvdevKeyboardHandler()
105{
106 unloadKeymap();
107}
108
109std::unique_ptr<QEvdevKeyboardHandler> QEvdevKeyboardHandler::create(const QString &device,
110 const QString &specification,
111 const QString &defaultKeymapFile)
112{
113 qCDebug(qLcEvdevKey, "Try to create keyboard handler for \"%ls\" \"%ls\"",
114 qUtf16Printable(device), qUtf16Printable(specification));
115
116 QString keymapFile = defaultKeymapFile;
117 int repeatDelay = 400;
118 int repeatRate = 80;
119 bool disableZap = false;
120 bool enableCompose = false;
121 int grab = 0;
122
123 const auto args = specification.splitRef(sep: QLatin1Char(':'));
124 for (const QStringRef &arg : args) {
125 if (arg.startsWith(s: QLatin1String("keymap=")))
126 keymapFile = arg.mid(pos: 7).toString();
127 else if (arg == QLatin1String("disable-zap"))
128 disableZap = true;
129 else if (arg == QLatin1String("enable-compose"))
130 enableCompose = true;
131 else if (arg.startsWith(s: QLatin1String("repeat-delay=")))
132 repeatDelay = arg.mid(pos: 13).toInt();
133 else if (arg.startsWith(s: QLatin1String("repeat-rate=")))
134 repeatRate = arg.mid(pos: 12).toInt();
135 else if (arg.startsWith(s: QLatin1String("grab=")))
136 grab = arg.mid(pos: 5).toInt();
137 }
138
139 qCDebug(qLcEvdevKey, "Opening keyboard at %ls", qUtf16Printable(device));
140
141 QFdContainer fd(qt_safe_open(pathname: device.toLocal8Bit().constData(), O_RDWR | O_NDELAY, mode: 0));
142 if (fd.get() < 0) {
143 qCDebug(qLcEvdevKey, "Keyboard device could not be opened as read-write, trying read-only");
144 fd.reset(fd: qt_safe_open(pathname: device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, mode: 0));
145 }
146 if (fd.get() >= 0) {
147 ::ioctl(fd: fd.get(), EVIOCGRAB, grab);
148 if (repeatDelay > 0 && repeatRate > 0) {
149 int kbdrep[2] = { repeatDelay, repeatRate };
150 ::ioctl(fd: fd.get(), EVIOCSREP, kbdrep);
151 }
152
153 return std::unique_ptr<QEvdevKeyboardHandler>(new QEvdevKeyboardHandler(device, fd, disableZap, enableCompose, keymapFile));
154 } else {
155 qErrnoWarning(msg: "Cannot open keyboard input device '%ls'", qUtf16Printable(device));
156 return nullptr;
157 }
158}
159
160void QEvdevKeyboardHandler::switchLed(int led, bool state)
161{
162 qCDebug(qLcEvdevKey, "switchLed %d %d", led, int(state));
163
164 struct timeval tv;
165 ::gettimeofday(tv: &tv, tz: 0);
166 struct ::input_event led_ie;
167 led_ie.input_event_sec = tv.tv_sec;
168 led_ie.input_event_usec = tv.tv_usec;
169 led_ie.type = EV_LED;
170 led_ie.code = led;
171 led_ie.value = state;
172
173 qt_safe_write(fd: m_fd.get(), data: &led_ie, len: sizeof(led_ie));
174}
175
176void QEvdevKeyboardHandler::readKeycode()
177{
178 struct ::input_event buffer[32];
179 int n = 0;
180
181 forever {
182 int result = qt_safe_read(fd: m_fd.get(), data: reinterpret_cast<char *>(buffer) + n, maxlen: sizeof(buffer) - n);
183
184 if (result == 0) {
185 qWarning(msg: "evdevkeyboard: Got EOF from the input device");
186 return;
187 } else if (result < 0) {
188 if (errno != EINTR && errno != EAGAIN) {
189 qErrnoWarning(msg: "evdevkeyboard: Could not read from input device");
190 // If the device got disconnected, stop reading, otherwise we get flooded
191 // by the above error over and over again.
192 if (errno == ENODEV) {
193 delete m_notify;
194 m_notify = nullptr;
195 m_fd.reset();
196 }
197 return;
198 }
199 } else {
200 n += result;
201 if (n % sizeof(buffer[0]) == 0)
202 break;
203 }
204 }
205
206 n /= sizeof(buffer[0]);
207
208 for (int i = 0; i < n; ++i) {
209 if (buffer[i].type != EV_KEY)
210 continue;
211
212 quint16 code = buffer[i].code;
213 qint32 value = buffer[i].value;
214
215 QEvdevKeyboardHandler::KeycodeAction ka;
216 ka = processKeycode(keycode: code, pressed: value != 0, autorepeat: value == 2);
217
218 switch (ka) {
219 case QEvdevKeyboardHandler::CapsLockOn:
220 case QEvdevKeyboardHandler::CapsLockOff:
221 switchLed(LED_CAPSL, state: ka == QEvdevKeyboardHandler::CapsLockOn);
222 break;
223
224 case QEvdevKeyboardHandler::NumLockOn:
225 case QEvdevKeyboardHandler::NumLockOff:
226 switchLed(LED_NUML, state: ka == QEvdevKeyboardHandler::NumLockOn);
227 break;
228
229 case QEvdevKeyboardHandler::ScrollLockOn:
230 case QEvdevKeyboardHandler::ScrollLockOff:
231 switchLed(LED_SCROLLL, state: ka == QEvdevKeyboardHandler::ScrollLockOn);
232 break;
233
234 default:
235 // ignore console switching and reboot
236 break;
237 }
238 }
239}
240
241void QEvdevKeyboardHandler::processKeyEvent(int nativecode, int unicode, int qtcode,
242 Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat)
243{
244 if (!autoRepeat)
245 QGuiApplicationPrivate::inputDeviceManager()->setKeyboardModifiers(QEvdevKeyboardHandler::toQtModifiers(mod: m_modifiers));
246
247 QWindowSystemInterface::handleExtendedKeyEvent(window: 0, type: (isPress ? QEvent::KeyPress : QEvent::KeyRelease),
248 key: qtcode, modifiers, nativeScanCode: nativecode + 8, nativeVirtualKey: 0, nativeModifiers: int(modifiers),
249 text: (unicode != 0xffff ) ? QString(QChar(unicode)) : QString(), autorep: autoRepeat);
250}
251
252QEvdevKeyboardHandler::KeycodeAction QEvdevKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat)
253{
254 KeycodeAction result = None;
255 bool first_press = pressed && !autorepeat;
256
257 const QEvdevKeyboardMap::Mapping *map_plain = 0;
258 const QEvdevKeyboardMap::Mapping *map_withmod = 0;
259
260 quint8 modifiers = m_modifiers;
261
262 // get a specific and plain mapping for the keycode and the current modifiers
263 for (int i = 0; i < m_keymap_size && !(map_plain && map_withmod); ++i) {
264 const QEvdevKeyboardMap::Mapping *m = m_keymap + i;
265 if (m->keycode == keycode) {
266 if (m->modifiers == 0)
267 map_plain = m;
268
269 quint8 testmods = m_modifiers;
270 if (m_locks[0] /*CapsLock*/ && (m->flags & QEvdevKeyboardMap::IsLetter))
271 testmods ^= QEvdevKeyboardMap::ModShift;
272 if (m_langLock)
273 testmods ^= QEvdevKeyboardMap::ModAltGr;
274 if (m->modifiers == testmods)
275 map_withmod = m;
276 }
277 }
278
279 if (m_locks[0] /*CapsLock*/ && map_withmod && (map_withmod->flags & QEvdevKeyboardMap::IsLetter))
280 modifiers ^= QEvdevKeyboardMap::ModShift;
281
282 qCDebug(qLcEvdevKeyMap, "Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d | plain=%d, withmod=%d, size=%d",
283 keycode, modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0,
284 int(map_plain ? map_plain - m_keymap : -1),
285 int(map_withmod ? map_withmod - m_keymap : -1),
286 m_keymap_size);
287
288 const QEvdevKeyboardMap::Mapping *it = map_withmod ? map_withmod : map_plain;
289
290 if (!it) {
291 // we couldn't even find a plain mapping
292 qCDebug(qLcEvdevKeyMap, "Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, modifiers);
293 return result;
294 }
295
296 bool skip = false;
297 quint16 unicode = it->unicode;
298 quint32 qtcode = it->qtcode;
299
300 if ((it->flags & QEvdevKeyboardMap::IsModifier) && it->special) {
301 // this is a modifier, i.e. Shift, Alt, ...
302 if (pressed)
303 m_modifiers |= quint8(it->special);
304 else
305 m_modifiers &= ~quint8(it->special);
306 } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) {
307 // (Caps|Num|Scroll)Lock
308 if (first_press) {
309 quint8 &lock = m_locks[qtcode - Qt::Key_CapsLock];
310 lock ^= 1;
311
312 switch (qtcode) {
313 case Qt::Key_CapsLock : result = lock ? CapsLockOn : CapsLockOff; break;
314 case Qt::Key_NumLock : result = lock ? NumLockOn : NumLockOff; break;
315 case Qt::Key_ScrollLock: result = lock ? ScrollLockOn : ScrollLockOff; break;
316 default : break;
317 }
318 }
319 } else if ((it->flags & QEvdevKeyboardMap::IsSystem) && it->special && first_press) {
320 switch (it->special) {
321 case QEvdevKeyboardMap::SystemReboot:
322 result = Reboot;
323 break;
324
325 case QEvdevKeyboardMap::SystemZap:
326 if (!m_no_zap)
327 qApp->quit();
328 break;
329
330 case QEvdevKeyboardMap::SystemConsolePrevious:
331 result = PreviousConsole;
332 break;
333
334 case QEvdevKeyboardMap::SystemConsoleNext:
335 result = NextConsole;
336 break;
337
338 default:
339 if (it->special >= QEvdevKeyboardMap::SystemConsoleFirst &&
340 it->special <= QEvdevKeyboardMap::SystemConsoleLast) {
341 result = KeycodeAction(SwitchConsoleFirst + ((it->special & QEvdevKeyboardMap::SystemConsoleMask) & SwitchConsoleMask));
342 }
343 break;
344 }
345
346 skip = true; // no need to tell Qt about it
347 } else if ((qtcode == Qt::Key_Multi_key) && m_do_compose) {
348 // the Compose key was pressed
349 if (first_press)
350 m_composing = 2;
351 skip = true;
352 } else if ((it->flags & QEvdevKeyboardMap::IsDead) && m_do_compose) {
353 // a Dead key was pressed
354 if (first_press && m_composing == 1 && m_dead_unicode == unicode) { // twice
355 m_composing = 0;
356 qtcode = Qt::Key_unknown; // otherwise it would be Qt::Key_Dead...
357 } else if (first_press && unicode != 0xffff) {
358 m_dead_unicode = unicode;
359 m_composing = 1;
360 skip = true;
361 } else {
362 skip = true;
363 }
364 }
365
366 if (!skip) {
367 // a normal key was pressed
368 const int modmask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier;
369
370 // we couldn't find a specific mapping for the current modifiers,
371 // or that mapping didn't have special modifiers:
372 // so just report the plain mapping with additional modifiers.
373 if ((it == map_plain && it != map_withmod) ||
374 (map_withmod && !(map_withmod->qtcode & modmask))) {
375 qtcode |= QEvdevKeyboardHandler::toQtModifiers(mod: modifiers);
376 }
377
378 if (m_composing == 2 && first_press && !(it->flags & QEvdevKeyboardMap::IsModifier)) {
379 // the last key press was the Compose key
380 if (unicode != 0xffff) {
381 int idx = 0;
382 // check if this code is in the compose table at all
383 for ( ; idx < m_keycompose_size; ++idx) {
384 if (m_keycompose[idx].first == unicode)
385 break;
386 }
387 if (idx < m_keycompose_size) {
388 // found it -> simulate a Dead key press
389 m_dead_unicode = unicode;
390 unicode = 0xffff;
391 m_composing = 1;
392 skip = true;
393 } else {
394 m_composing = 0;
395 }
396 } else {
397 m_composing = 0;
398 }
399 } else if (m_composing == 1 && first_press && !(it->flags & QEvdevKeyboardMap::IsModifier)) {
400 // the last key press was a Dead key
401 bool valid = false;
402 if (unicode != 0xffff) {
403 int idx = 0;
404 // check if this code is in the compose table at all
405 for ( ; idx < m_keycompose_size; ++idx) {
406 if (m_keycompose[idx].first == m_dead_unicode && m_keycompose[idx].second == unicode)
407 break;
408 }
409 if (idx < m_keycompose_size) {
410 quint16 composed = m_keycompose[idx].result;
411 if (composed != 0xffff) {
412 unicode = composed;
413 qtcode = Qt::Key_unknown;
414 valid = true;
415 }
416 }
417 }
418 if (!valid) {
419 unicode = m_dead_unicode;
420 qtcode = Qt::Key_unknown;
421 }
422 m_composing = 0;
423 }
424
425 if (!skip) {
426 // Up until now qtcode contained both the key and modifiers. Split it.
427 Qt::KeyboardModifiers qtmods = Qt::KeyboardModifiers(qtcode & modmask);
428 qtcode &= ~modmask;
429
430 // qtmods here is the modifier state before the event, i.e. not
431 // including the current key in case it is a modifier.
432 qCDebug(qLcEvdevKeyMap, "Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode, int(qtmods));
433
434 // If NumLockOff and keypad key pressed remap event sent
435 if (!m_locks[1] && (qtmods & Qt::KeypadModifier) &&
436 keycode >= 71 &&
437 keycode <= 83 &&
438 keycode != 74 &&
439 keycode != 78) {
440
441 unicode = 0xffff;
442 switch (keycode) {
443 case 71: //7 --> Home
444 qtcode = Qt::Key_Home;
445 break;
446 case 72: //8 --> Up
447 qtcode = Qt::Key_Up;
448 break;
449 case 73: //9 --> PgUp
450 qtcode = Qt::Key_PageUp;
451 break;
452 case 75: //4 --> Left
453 qtcode = Qt::Key_Left;
454 break;
455 case 76: //5 --> Clear
456 qtcode = Qt::Key_Clear;
457 break;
458 case 77: //6 --> right
459 qtcode = Qt::Key_Right;
460 break;
461 case 79: //1 --> End
462 qtcode = Qt::Key_End;
463 break;
464 case 80: //2 --> Down
465 qtcode = Qt::Key_Down;
466 break;
467 case 81: //3 --> PgDn
468 qtcode = Qt::Key_PageDown;
469 break;
470 case 82: //0 --> Ins
471 qtcode = Qt::Key_Insert;
472 break;
473 case 83: //, --> Del
474 qtcode = Qt::Key_Delete;
475 break;
476 }
477 }
478
479 // Map SHIFT + Tab to SHIFT + Backtab, QShortcutMap knows about this translation
480 if (qtcode == Qt::Key_Tab && (qtmods & Qt::ShiftModifier) == Qt::ShiftModifier)
481 qtcode = Qt::Key_Backtab;
482
483 // Generate the QPA event.
484 processKeyEvent(nativecode: keycode, unicode, qtcode, modifiers: qtmods, isPress: pressed, autoRepeat: autorepeat);
485 }
486 }
487 return result;
488}
489
490void QEvdevKeyboardHandler::unloadKeymap()
491{
492 qCDebug(qLcEvdevKey, "Unload current keymap and restore built-in");
493
494 if (m_keymap && m_keymap != s_keymap_default)
495 delete [] m_keymap;
496 if (m_keycompose && m_keycompose != s_keycompose_default)
497 delete [] m_keycompose;
498
499 m_keymap = s_keymap_default;
500 m_keymap_size = sizeof(s_keymap_default) / sizeof(s_keymap_default[0]);
501 m_keycompose = s_keycompose_default;
502 m_keycompose_size = sizeof(s_keycompose_default) / sizeof(s_keycompose_default[0]);
503
504 // reset state, so we could switch keymaps at runtime
505 m_modifiers = 0;
506 memset(s: m_locks, c: 0, n: sizeof(m_locks));
507 m_composing = 0;
508 m_dead_unicode = 0xffff;
509
510 //Set locks according to keyboard leds
511 quint16 ledbits[1];
512 memset(s: ledbits, c: 0, n: sizeof(ledbits));
513 if (::ioctl(fd: m_fd.get(), EVIOCGLED(sizeof(ledbits)), ledbits) < 0) {
514 qWarning(msg: "evdevkeyboard: Failed to query led states");
515 switchLed(LED_NUML,state: false);
516 switchLed(LED_CAPSL, state: false);
517 switchLed(LED_SCROLLL,state: false);
518 } else {
519 //Capslock
520 if ((ledbits[0]&0x02) > 0)
521 m_locks[0] = 1;
522 //Numlock
523 if ((ledbits[0]&0x01) > 0)
524 m_locks[1] = 1;
525 //Scrollock
526 if ((ledbits[0]&0x04) > 0)
527 m_locks[2] = 1;
528 qCDebug(qLcEvdevKey, "numlock=%d , capslock=%d, scrolllock=%d", m_locks[1], m_locks[0], m_locks[2]);
529 }
530
531 m_langLock = 0;
532}
533
534bool QEvdevKeyboardHandler::loadKeymap(const QString &file)
535{
536 qCDebug(qLcEvdevKey, "Loading keymap %ls", qUtf16Printable(file));
537
538 QFile f(file);
539
540 if (!f.open(flags: QIODevice::ReadOnly)) {
541 qWarning(msg: "Could not open keymap file '%ls'", qUtf16Printable(file));
542 return false;
543 }
544
545 // .qmap files have a very simple structure:
546 // quint32 magic (QKeyboard::FileMagic)
547 // quint32 version (1)
548 // quint32 keymap_size (# of struct QKeyboard::Mappings)
549 // quint32 keycompose_size (# of struct QKeyboard::Composings)
550 // all QKeyboard::Mappings via QDataStream::operator(<<|>>)
551 // all QKeyboard::Composings via QDataStream::operator(<<|>>)
552
553 quint32 qmap_magic, qmap_version, qmap_keymap_size, qmap_keycompose_size;
554
555 QDataStream ds(&f);
556
557 ds >> qmap_magic >> qmap_version >> qmap_keymap_size >> qmap_keycompose_size;
558
559 if (ds.status() != QDataStream::Ok || qmap_magic != QEvdevKeyboardMap::FileMagic || qmap_version != 1 || qmap_keymap_size == 0) {
560 qWarning(msg: "'%ls' is not a valid .qmap keymap file", qUtf16Printable(file));
561 return false;
562 }
563
564 QEvdevKeyboardMap::Mapping *qmap_keymap = new QEvdevKeyboardMap::Mapping[qmap_keymap_size];
565 QEvdevKeyboardMap::Composing *qmap_keycompose = qmap_keycompose_size ? new QEvdevKeyboardMap::Composing[qmap_keycompose_size] : 0;
566
567 for (quint32 i = 0; i < qmap_keymap_size; ++i)
568 ds >> qmap_keymap[i];
569 for (quint32 i = 0; i < qmap_keycompose_size; ++i)
570 ds >> qmap_keycompose[i];
571
572 if (ds.status() != QDataStream::Ok) {
573 delete [] qmap_keymap;
574 delete [] qmap_keycompose;
575
576 qWarning(msg: "Keymap file '%ls' cannot be loaded.", qUtf16Printable(file));
577 return false;
578 }
579
580 // unload currently active and clear state
581 unloadKeymap();
582
583 m_keymap = qmap_keymap;
584 m_keymap_size = qmap_keymap_size;
585 m_keycompose = qmap_keycompose;
586 m_keycompose_size = qmap_keycompose_size;
587
588 m_do_compose = true;
589
590 return true;
591}
592
593void QEvdevKeyboardHandler::switchLang()
594{
595 m_langLock ^= 1;
596}
597
598QT_END_NAMESPACE
599

source code of qtbase/src/platformsupport/input/evdevkeyboard/qevdevkeyboardhandler.cpp