diff --git a/shell/panelshadows.cpp b/shell/panelshadows.cpp --- a/shell/panelshadows.cpp +++ b/shell/panelshadows.cpp @@ -18,98 +18,33 @@ #include "panelshadows_p.h" -#include -#include - -#include - -#include -#if HAVE_X11 -#include -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include - -#include +#include class PanelShadows::Private { public: Private(PanelShadows *shadows) : q(shadows) -#if HAVE_X11 - ,_connection( nullptr ), - _gc( 0x0 ) - , m_isX11(KWindowSystem::isPlatformX11()) -#endif { - setupWaylandIntegration(); } ~Private() { - // Do not call clearPixmaps() from here: it creates new QPixmap(), - // which causes a crash when application is stopping. - freeX11Pixmaps(); } - void freeX11Pixmaps(); - void freeWaylandBuffers(); - void clearPixmaps(); - void setupPixmaps(); - Qt::HANDLE createPixmap(const QPixmap& source); - void initPixmap(const QString &element); - QPixmap initEmptyPixmap(const QSize &size); + void clearTiles(); + void setupTiles(); + void initTile(const QString &element); void updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders); - void updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders); - void updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void clearShadow(const QWindow *window); - void clearShadowX11(const QWindow *window); - void clearShadowWayland(const QWindow *window); void updateShadows(); - void setupData(Plasma::FrameSvg::EnabledBorders enabledBorders); bool hasShadows() const; - void setupWaylandIntegration(); - PanelShadows *q; - QList m_shadowPixmaps; - - QPixmap m_emptyCornerPix; - QPixmap m_emptyCornerLeftPix; - QPixmap m_emptyCornerTopPix; - QPixmap m_emptyCornerRightPix; - QPixmap m_emptyCornerBottomPix; - QPixmap m_emptyVerticalPix; - QPixmap m_emptyHorizontalPix; - -#if HAVE_X11 - //! xcb connection - xcb_connection_t* _connection; - - //! graphical context - xcb_gcontext_t _gc; - bool m_isX11; -#endif - - struct Wayland { - KWayland::Client::ShadowManager *manager = nullptr; - KWayland::Client::ShmPool *shmPool = nullptr; - - QList shadowBuffers; - }; - Wayland m_wayland; - - QHash > data; + QHash m_windows; + QHash m_shadows; + QVector m_tiles; }; class PanelShadowsSingleton @@ -155,7 +90,7 @@ connect(window, &QObject::destroyed, this, [this, window]() { d->m_windows.remove(window); if (d->m_windows.isEmpty()) { - d->clearPixmaps(); + d->clearTiles(); } }); } @@ -171,7 +106,7 @@ d->clearShadow(window); if (d->m_windows.isEmpty()) { - d->clearPixmaps(); + d->clearTiles(); } } @@ -187,12 +122,12 @@ void PanelShadows::Private::updateShadows() { - const bool hadShadowsBefore = !m_shadowPixmaps.isEmpty(); + const bool hadShadowsBefore = !m_tiles.isEmpty(); // has shadows now? if (hasShadows()) { if (hadShadowsBefore) { - clearPixmaps(); + clearTiles(); } for (auto i = m_windows.constBegin(); i != m_windows.constEnd(); ++i) { updateShadow(i.key(), i.value()); @@ -202,524 +137,167 @@ for (auto i = m_windows.constBegin(); i != m_windows.constEnd(); ++i) { clearShadow(i.key()); } - clearPixmaps(); + clearTiles(); } } } -Qt::HANDLE PanelShadows::Private::createPixmap(const QPixmap& source) +void PanelShadows::Private::initTile(const QString &element) { + const QImage image = q->pixmap(element).toImage(); - // do nothing for invalid pixmaps - if( source.isNull() ) return nullptr; - - /* - in some cases, pixmap handle is invalid. This is the case notably - when Qt uses to RasterEngine. In this case, we create an X11 Pixmap - explicitly and draw the source pixmap on it. - */ - - #if HAVE_X11 - if (!m_isX11) { - return nullptr; - } - - // check connection - if( !_connection ) _connection = QX11Info::connection(); - - const int width( source.width() ); - const int height( source.height() ); - - // create X11 pixmap - Pixmap pixmap = XCreatePixmap( QX11Info::display(), QX11Info::appRootWindow(), width, height, 32 ); - - // check gc - if( !_gc ) - { - _gc = xcb_generate_id( _connection ); - xcb_create_gc( _connection, _gc, pixmap, 0, nullptr ); - } - -// // create explicitly shared QPixmap from it -// QPixmap dest( QPixmap::fromX11Pixmap( pixmap, QPixmap::ExplicitlyShared ) ); -// -// // create surface for pixmap -// { -// QPainter painter( &dest ); -// painter.setCompositionMode( QPainter::CompositionMode_Source ); -// painter.drawPixmap( 0, 0, source ); -// } -// -// -// return pixmap; - QImage image( source.toImage() ); - xcb_put_image( - _connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, - image.width(), image.height(), 0, 0, - 0, 32, - image.sizeInBytes(), image.constBits()); - - return (Qt::HANDLE)pixmap; - - #else - return 0; - #endif + KWindowShadowTile::Ptr tile = KWindowShadowTile::Ptr::create(); + tile->setImage(image); + m_tiles << tile; } -void PanelShadows::Private::initPixmap(const QString &element) +void PanelShadows::Private::setupTiles() { - m_shadowPixmaps << q->pixmap(element); + clearTiles(); + + initTile(QStringLiteral("shadow-top")); + initTile(QStringLiteral("shadow-topright")); + initTile(QStringLiteral("shadow-right")); + initTile(QStringLiteral("shadow-bottomright")); + initTile(QStringLiteral("shadow-bottom")); + initTile(QStringLiteral("shadow-bottomleft")); + initTile(QStringLiteral("shadow-left")); + initTile(QStringLiteral("shadow-topleft")); } -QPixmap PanelShadows::Private::initEmptyPixmap(const QSize &size) +void PanelShadows::Private::clearTiles() { -#if HAVE_X11 - if (!m_isX11) { - return QPixmap(); - } - QPixmap tempEmptyPix(size); - if (!size.isEmpty()) { - tempEmptyPix.fill(Qt::transparent); - } - return tempEmptyPix; -#else - Q_UNUSED(size) - return QPixmap(); -#endif + m_tiles.clear(); } -void PanelShadows::Private::setupPixmaps() +void PanelShadows::Private::updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { - clearPixmaps(); - initPixmap(QStringLiteral("shadow-top")); - initPixmap(QStringLiteral("shadow-topright")); - initPixmap(QStringLiteral("shadow-right")); - initPixmap(QStringLiteral("shadow-bottomright")); - initPixmap(QStringLiteral("shadow-bottom")); - initPixmap(QStringLiteral("shadow-bottomleft")); - initPixmap(QStringLiteral("shadow-left")); - initPixmap(QStringLiteral("shadow-topleft")); - - m_emptyCornerPix = initEmptyPixmap(QSize(1,1)); - m_emptyCornerLeftPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-topleft")).width(), 1)); - m_emptyCornerTopPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-topleft")).height())); - m_emptyCornerRightPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-bottomright")).width(), 1)); - m_emptyCornerBottomPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-bottomright")).height())); - m_emptyVerticalPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-left")).height())); - m_emptyHorizontalPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-top")).width(), 1)); - - if (m_wayland.shmPool) { - for (auto it = m_shadowPixmaps.constBegin(); it != m_shadowPixmaps.constEnd(); ++it) { - m_wayland.shadowBuffers << m_wayland.shmPool->createBuffer(it->toImage()); - } + if (!hasShadows()) { + return; + } + + if (m_tiles.isEmpty()) { + setupTiles(); } -} + KWindowShadow *&shadow = m_shadows[window]; -void PanelShadows::Private::setupData(Plasma::FrameSvg::EnabledBorders enabledBorders) -{ -#if HAVE_X11 - if (!m_isX11) { - return; + if (!shadow) { + shadow = new KWindowShadow(q); + } + + if (shadow->isCreated()) { + shadow->destroy(); } - //shadow-top + if (enabledBorders & Plasma::FrameSvg::TopBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[0])); + shadow->setTopTile(m_tiles.at(0)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); + shadow->setTopTile(nullptr); } - //shadow-topright if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[1])); - } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); - } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); + shadow->setTopRightTile(m_tiles.at(1)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); + shadow->setTopRightTile(nullptr); } - //shadow-right if (enabledBorders & Plasma::FrameSvg::RightBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[2])); + shadow->setRightTile(m_tiles.at(2)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); + shadow->setRightTile(nullptr); } - //shadow-bottomright if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[3])); - } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); - } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); + shadow->setBottomRightTile(m_tiles.at(3)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); + shadow->setBottomRightTile(nullptr); } - //shadow-bottom if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[4])); + shadow->setBottomTile(m_tiles.at(4)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); + shadow->setBottomTile(nullptr); } - //shadow-bottomleft if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[5])); - } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); - } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); + shadow->setBottomLeftTile(m_tiles.at(5)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); + shadow->setBottomLeftTile(nullptr); } - //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[6])); + shadow->setLeftTile(m_tiles.at(6)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); + shadow->setLeftTile(nullptr); } - //shadow-topleft if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[7])); - } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); - } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); + shadow->setTopLeftTile(m_tiles.at(7)); } else { - data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); + shadow->setTopLeftTile(nullptr); } -#endif - int left, top, right, bottom = 0; + QMargins padding; - QSize marginHint; if (enabledBorders & Plasma::FrameSvg::TopBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); + const QSize marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); if (marginHint.isValid()) { - top = marginHint.height(); + padding.setTop(marginHint.height()); } else { - top = m_shadowPixmaps[0].height(); // top + padding.setTop(m_tiles[0]->image().height()); } - } else { - top = 1; } if (enabledBorders & Plasma::FrameSvg::RightBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); + const QSize marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); if (marginHint.isValid()) { - right = marginHint.width(); + padding.setRight(marginHint.width()); } else { - right = m_shadowPixmaps[2].width(); // right + padding.setRight(m_tiles[2]->image().width()); } - } else { - right = 1; } if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); + const QSize marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); if (marginHint.isValid()) { - bottom = marginHint.height(); + padding.setBottom(marginHint.height()); } else { - bottom = m_shadowPixmaps[4].height(); // bottom + padding.setBottom(m_tiles[4]->image().height()); } - } else { - bottom = 1; - } - - if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); - if (marginHint.isValid()) { - left = marginHint.width(); - } else { - left = m_shadowPixmaps[6].width(); // left - } - } else { - left = 1; - } - - data[enabledBorders] << top << right << bottom << left; -} - -void PanelShadows::Private::freeX11Pixmaps() -{ -#if HAVE_X11 - if (!m_isX11) { - return; - } - - auto *display = QX11Info::display(); - if (!display) { - return; - } - - foreach (const QPixmap &pixmap, m_shadowPixmaps) { - if (!pixmap.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(pixmap))); - } - } - - if (!m_emptyCornerPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerPix))); - } - if (!m_emptyCornerBottomPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerBottomPix))); - } - if (!m_emptyCornerLeftPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerLeftPix))); - } - if (!m_emptyCornerRightPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerRightPix))); - } - if (!m_emptyCornerTopPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerTopPix))); - } - if (!m_emptyVerticalPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyVerticalPix))); - } - if (!m_emptyHorizontalPix.isNull()) { - XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyHorizontalPix))); - } -#endif -} - -void PanelShadows::Private::clearPixmaps() -{ -#if HAVE_X11 - freeX11Pixmaps(); - - m_emptyCornerPix = QPixmap(); - m_emptyCornerBottomPix = QPixmap(); - m_emptyCornerLeftPix = QPixmap(); - m_emptyCornerRightPix = QPixmap(); - m_emptyCornerTopPix = QPixmap(); - m_emptyVerticalPix = QPixmap(); - m_emptyHorizontalPix = QPixmap(); -#endif - freeWaylandBuffers(); - m_shadowPixmaps.clear(); - data.clear(); -} - -void PanelShadows::Private::freeWaylandBuffers() -{ - m_wayland.shadowBuffers.clear(); -} - -void PanelShadows::Private::updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) -{ - if (!hasShadows()) { - return; - } - -#if HAVE_X11 - if (m_isX11) { - updateShadowX11(window, enabledBorders); - } -#endif - if (m_wayland.manager) { - updateShadowWayland(window, enabledBorders); - } -} - -void PanelShadows::Private::updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) -{ -#if HAVE_X11 - if (m_shadowPixmaps.isEmpty()) { - setupPixmaps(); } - if (!data.contains(enabledBorders)) { - setupData(enabledBorders); - } - - Display *dpy = QX11Info::display(); - Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); - -// qDebug() << "going to set the shadow of" << window->winId() << "to" << data; - XChangeProperty(dpy, window->winId(), atom, XA_CARDINAL, 32, PropModeReplace, - reinterpret_cast(data[enabledBorders].constData()), data[enabledBorders].size()); -#endif -} - -void PanelShadows::Private::updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) -{ - if (!m_wayland.shmPool) { - return; - } - if (m_wayland.shadowBuffers.isEmpty()) { - setupPixmaps(); - } - // TODO: check whether the surface already has a shadow - KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); - if (!surface) { - return; - } - auto shadow = m_wayland.manager->createShadow(surface, surface); - - //shadow-top - if (enabledBorders & Plasma::FrameSvg::TopBorder) { - shadow->attachTop(m_wayland.shadowBuffers.at(0)); - } - - //shadow-topright - if (enabledBorders & Plasma::FrameSvg::TopBorder && - enabledBorders & Plasma::FrameSvg::RightBorder) { - shadow->attachTopRight(m_wayland.shadowBuffers.at(1)); - } - - //shadow-right - if (enabledBorders & Plasma::FrameSvg::RightBorder) { - shadow->attachRight(m_wayland.shadowBuffers.at(2)); - } - - //shadow-bottomright - if (enabledBorders & Plasma::FrameSvg::BottomBorder && - enabledBorders & Plasma::FrameSvg::RightBorder) { - shadow->attachBottomRight(m_wayland.shadowBuffers.at(3)); - } - - //shadow-bottom - if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - shadow->attachBottom(m_wayland.shadowBuffers.at(4)); - } - - //shadow-bottomleft - if (enabledBorders & Plasma::FrameSvg::BottomBorder && - enabledBorders & Plasma::FrameSvg::LeftBorder) { - shadow->attachBottomLeft(m_wayland.shadowBuffers.at(5)); - } - - //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - shadow->attachLeft(m_wayland.shadowBuffers.at(6)); - } - - //shadow-topleft - if (enabledBorders & Plasma::FrameSvg::TopBorder && - enabledBorders & Plasma::FrameSvg::LeftBorder) { - shadow->attachTopLeft(m_wayland.shadowBuffers.at(7)); - } - - QSize marginHint; - QMarginsF margins; - if (enabledBorders & Plasma::FrameSvg::TopBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); + const QSize marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); if (marginHint.isValid()) { - margins.setTop(marginHint.height()); + padding.setLeft(marginHint.width()); } else { - margins.setTop(m_shadowPixmaps[0].height()); + padding.setLeft(m_tiles[6]->image().width()); } } - if (enabledBorders & Plasma::FrameSvg::RightBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); - if (marginHint.isValid()) { - margins.setRight(marginHint.width()); - } else { - margins.setRight(m_shadowPixmaps[2].width()); - } - } + shadow->setPadding(padding); + shadow->setWindow(const_cast(window)); - if (enabledBorders & Plasma::FrameSvg::BottomBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); - if (marginHint.isValid()) { - margins.setBottom(marginHint.height()); - } else { - margins.setBottom(m_shadowPixmaps[4].height()); - } + if (!shadow->create()) { + qWarning() << "Couldn't create KWindowShadow for" << window; } - - if (enabledBorders & Plasma::FrameSvg::LeftBorder) { - marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); - if (marginHint.isValid()) { - margins.setLeft(marginHint.width()); - } else { - margins.setLeft(m_shadowPixmaps[6].width()); - } - } - - shadow->setOffsets(margins); - shadow->commit(); - surface->commit(KWayland::Client::Surface::CommitFlag::None); } void PanelShadows::Private::clearShadow(const QWindow *window) { - if (!static_cast(window)->surfaceHandle()) { - qWarning() << "Cannot clear shadow from window without native surface!"; - return; - } -#if HAVE_X11 - if (m_isX11) { - clearShadowX11(window); - } -#endif - if (m_wayland.manager) { - clearShadowWayland(window); - } -} - -void PanelShadows::Private::clearShadowX11(const QWindow* window) -{ -#if HAVE_X11 - Display *dpy = QX11Info::display(); - Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); - XDeleteProperty(dpy, window->winId(), atom); -#endif -} - -void PanelShadows::Private::clearShadowWayland(const QWindow *window) -{ - KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); - if (!surface) { - return; - } - m_wayland.manager->removeShadow(surface); - surface->commit(KWayland::Client::Surface::CommitFlag::None); + delete m_shadows.take(window); } bool PanelShadows::Private::hasShadows() const { return q->hasElement(QStringLiteral("shadow-left")); } -void PanelShadows::Private::setupWaylandIntegration() -{ - if (!KWindowSystem::isPlatformWayland()) { - return; - } - using namespace KWayland::Client; - ConnectionThread *connection = ConnectionThread::fromApplication(q); - if (!connection) { - return; - } - Registry *registry = new Registry(q); - registry->create(connection); - connect(registry, &Registry::shadowAnnounced, q, - [this, registry] (quint32 name, quint32 version) { - m_wayland.manager = registry->createShadowManager(name, version, q); - updateShadows(); - }, Qt::QueuedConnection - ); - connect(registry, &Registry::shmAnnounced, q, - [this, registry] (quint32 name, quint32 version) { - m_wayland.shmPool = registry->createShmPool(name, version, q); - updateShadows(); - }, Qt::QueuedConnection - ); - registry->setup(); - connection->roundtrip(); -} - #include "moc_panelshadows_p.cpp"