当前位置: 首页 > news >正文

QT 之wayland 事件处理分析基于qt5wayland5.14.2

1. Qt  wayland 初始化 接收鼠标/案件,触摸屏等事件事件

QWaylandNativeInterface : public QPlatformNativeInterface

在QWaylandNativeInterface 继承qpa 接口类QPlatformNativeInterface;

1.1 初始化鼠标:

void *QWaylandNativeInterface::nativeResourceForIntegration(const QByteArray &resourceString)
{QByteArray lowerCaseResource = resourceString.toLower();if (lowerCaseResource == "display" || lowerCaseResource == "wl_display" || lowerCaseResource == "nativedisplay")return m_integration->display()->wl_display();if (lowerCaseResource == "compositor")return const_cast<wl_compositor *>(m_integration->display()->wl_compositor());if (lowerCaseResource == "server_buffer_integration")return m_integration->serverBufferIntegration();if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration())return m_integration->clientBufferIntegration()->nativeResource(QWaylandClientBufferIntegration::EglDisplay);if (lowerCaseResource == "wl_seat")return m_integration->display()->defaultInputDevice()->wl_seat();if (lowerCaseResource == "wl_keyboard") {auto *keyboard = m_integration->display()->defaultInputDevice()->keyboard();if (keyboard)return keyboard->wl_keyboard();return nullptr;}if (lowerCaseResource == "wl_pointer") {auto *pointer = m_integration->display()->defaultInputDevice()->pointer();if (pointer)return pointer->wl_pointer();return nullptr;}if (lowerCaseResource == "wl_touch") {auto *touch = m_integration->display()->defaultInputDevice()->touch();if (touch)return touch->wl_touch();return nullptr;}return nullptr;
}

QWaylandInputDevice::Point 主要实现 QtWayland::wl_pointer 类函数

 class Q_WAYLAND_CLIENT_WAYLAND_EXPORT wl_pointer{protected:virtual void pointer_enter(uint32_t serial, struct ::wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);virtual void pointer_leave(uint32_t serial, struct ::wl_surface *surface);virtual void pointer_motion(uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y);virtual void pointer_button(uint32_t serial, uint32_t time, uint32_t button, uint32_t state);virtual void pointer_axis(uint32_t time, uint32_t axis, wl_fixed_t value);virtual void pointer_frame();virtual void pointer_axis_source(uint32_t axis_source);virtual void pointer_axis_stop(uint32_t time, uint32_t axis);virtual void pointer_axis_discrete(uint32_t axis, int32_t discrete);private:void init_listener();static const struct wl_pointer_listener m_wl_pointer_listener;static void handle_enter(void *data,struct ::wl_pointer *object,uint32_t serial,struct ::wl_surface *surface,wl_fixed_t surface_x,wl_fixed_t surface_y);static void handle_leave(void *data,struct ::wl_pointer *object,uint32_t serial,struct ::wl_surface *surface);static void handle_motion(void *data,struct ::wl_pointer *object,uint32_t time,wl_fixed_t surface_x,wl_fixed_t surface_y);static void handle_button(void *data,struct ::wl_pointer *object,uint32_t serial,uint32_t time,uint32_t button,uint32_t state);static void handle_axis(void *data,struct ::wl_pointer *object,uint32_t time,uint32_t axis,wl_fixed_t value);static void handle_frame(void *data,struct ::wl_pointer *object);static void handle_axis_source(void *data,struct ::wl_pointer *object,uint32_t axis_source);static void handle_axis_stop(void *data,struct ::wl_pointer *object,uint32_t time,uint32_t axis);static void handle_axis_discrete(void *data,struct ::wl_pointer *object,uint32_t axis,int32_t discrete);struct ::wl_pointer *m_wl_pointer;};

2. wl_pointer 类向weston 注册监听鼠标事件

    const struct wl_pointer_listener wl_pointer::m_wl_pointer_listener = {wl_pointer::handle_enter,wl_pointer::handle_leave,wl_pointer::handle_motion,wl_pointer::handle_button,wl_pointer::handle_axis,wl_pointer::handle_frame,wl_pointer::handle_axis_source,wl_pointer::handle_axis_stop,wl_pointer::handle_axis_discrete,};void wl_pointer::init_listener(){wl_pointer_add_listener(m_wl_pointer, &m_wl_pointer_listener, this);}

weston 服务接收鼠标事件就调用m_wl_pointer_listener 函数集。

3. 事件产生流程:分析hand_enter 鼠标进入事件为例:

void wl_pointer::handle_enter(void *data,struct ::wl_pointer *object,uint32_t serial,struct ::wl_surface *surface,wl_fixed_t surface_x,wl_fixed_t surface_y){Q_UNUSED(object);static_cast<wl_pointer *>(data)->pointer_enter(serial,surface,surface_x,surface_y);}

hand_enter 处理调用虚函数pointer_enter。pointer_enter函数在QWaylandInputDevice::Pointe实现。

void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surface *surface,wl_fixed_t sx, wl_fixed_t sy)
{if (!surface)return;QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);if (!window)return; // Ignore foreign surfacesif (mFocus) {qCWarning(lcQpaWayland) << "The compositor sent a wl_pointer.enter event before sending a"<< "leave event first, this is not allowed by the wayland protocol"<< "attempting to work around it by invalidating the current focus";invalidateFocus();}mFocus = window->waylandSurface();connect(mFocus.data(), &QObject::destroyed, this, &Pointer::handleFocusDestroyed);mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy));mGlobalPos = window->window()->mapToGlobal(mSurfacePos.toPoint());mParent->mSerial = serial;mEnterSerial = serial;#if QT_CONFIG(cursor)// Depends on mEnterSerial being updatedupdateCursor();
#endifprintf("%s %d \n",__FUNCTION__,__LINE__);QWaylandWindow *grab = QWaylandWindow::mouseGrab();if (!grab)setFrameEvent(new EnterEvent(window, mSurfacePos, mGlobalPos));
}

setFrameEvent 发送EnterEvent 事件。

void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event)
{qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type;if (mFrameData.event && mFrameData.event->type != event->type) {qCDebug(lcQpaWaylandInput) << "Flushing; previous was " << mFrameData.event->type;flushFrameEvent();}mFrameData.event = event;if (mParent->mVersion < WL_POINTER_FRAME_SINCE_VERSION) {qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version";flushFrameEvent();}
}void QWaylandInputDevice::Pointer::flushFrameEvent()
{if (auto *event = mFrameData.event) {if (auto window = event->surface) {window->handleMouse(mParent, *event);} else if (mFrameData.event->type == QWaylandPointerEvent::Type::Release) {// If the window has been destroyed, we still need to report an up event, but it can't// be handled by the destroyed window (obviously), so send the event here instead.QWindowSystemInterface::handleMouseEvent(nullptr, event->timestamp, event->local,event->global, event->buttons, event->modifiers);}delete mFrameData.event;mFrameData.event = nullptr;}//TODO: do modifiers get passed correctly here?flushScrollEvent();
}void QWaylandInputDevice::Pointer::flushFrameEvent()
{if (auto *event = mFrameData.event) {if (auto window = event->surface) {window->handleMouse(mParent, *event);} else if (mFrameData.event->type == QWaylandPointerEvent::Type::Release) {// If the window has been destroyed, we still need to report an up event, but it can't// be handled by the destroyed window (obviously), so send the event here instead.QWindowSystemInterface::handleMouseEvent(nullptr, event->timestamp, event->local,event->global, event->buttons, event->modifiers);}delete mFrameData.event;mFrameData.event = nullptr;}//TODO: do modifiers get passed correctly here?flushScrollEvent();
}

window->handleMouse

void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e)
{if (e.type == QWaylandPointerEvent::Leave) {if (mWindowDecoration) {if (mMouseEventsInContentArea)QWindowSystemInterface::handleLeaveEvent(window());} else {QWindowSystemInterface::handleLeaveEvent(window());}
#if QT_CONFIG(cursor)restoreMouseCursor(inputDevice);
#endifreturn;}if (mWindowDecoration) {handleMouseEventWithDecoration(inputDevice, e);} else {switch (e.type) {case QWaylandPointerEvent::Enter:QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global);break;case QWaylandPointerEvent::Press:case QWaylandPointerEvent::Release:case QWaylandPointerEvent::Motion:QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, e.local, e.global, e.buttons, e.modifiers);break;case QWaylandPointerEvent::Wheel:QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global,e.pixelDelta, e.angleDelta, e.modifiers,e.phase, e.source, false);break;}}#if QT_CONFIG(cursor)if (e.type == QWaylandPointerEvent::Enter) {QRect contentGeometry = windowContentGeometry().marginsRemoved(frameMargins());if (contentGeometry.contains(e.local.toPoint()))restoreMouseCursor(inputDevice);}
#endif
}

调用 QWindowSystemInterface::handleMouseEvent(window()。 handleMouseEvent 函数是qtbase 代码中实现的。


QT_DEFINE_QPA_EVENT_HANDLER(void, handleMouseEvent, QWindow *window,const QPointF &local, const QPointF &global, Qt::MouseButtons state,Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods,Qt::MouseEventSource source)
{unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed();handleMouseEvent<Delivery>(window, time, local, global, state, button, type, mods, source);
}QT_DEFINE_QPA_EVENT_HANDLER(void, handleMouseEvent, QWindow *window, ulong timestamp,const QPointF &local, const QPointF &global, Qt::MouseButtons state,Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods,Qt::MouseEventSource source)
{Q_ASSERT_X(type != QEvent::MouseButtonDblClick && type != QEvent::NonClientAreaMouseButtonDblClick,"QWindowSystemInterface::handleMouseEvent","QTBUG-71263: Native double clicks are not implemented.");auto localPos = QHighDpi::fromNativeLocalPosition(local, window);auto globalPos = QHighDpi::fromNativePixels(global, window);QWindowSystemInterfacePrivate::MouseEvent *e =new QWindowSystemInterfacePrivate::MouseEvent(window, timestamp, localPos, globalPos,state, mods, button, type, source);QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
}

 handleMouseEvent 调用handleWindowSystemEvent 函数。

template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::DefaultDelivery>(QWindowSystemInterfacePrivate::WindowSystemEvent *ev)
{if (synchronousWindowSystemEvents)return handleWindowSystemEvent<QWindowSystemInterface::SynchronousDelivery>(ev);elsereturn handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(ev);
}

handleWindowSystemEvent 同步处理及异步处理

template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(WindowSystemEvent *ev)
{windowSystemEventQueue.append(ev);if (QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher())dispatcher->wakeUp();return true;
}
异步处理实现:将事件加入到windowSystemEventQueue 队列处理。

 同步处理:

template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::SynchronousDelivery>(WindowSystemEvent *ev)
{bool accepted = true;if (QThread::currentThread() == QGuiApplication::instance()->thread()) {// Process the event immediately on the current thread and return the accepted state.QGuiApplicationPrivate::processWindowSystemEvent(ev);accepted = ev->eventAccepted;delete ev;} else {// Post the event on the Qt main thread queue and flush the queue.// This will wake up the Gui thread which will process the event.// Return the accepted state for the last event on the queue,// which is the event posted by this function.handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(ev);accepted = QWindowSystemInterface::flushWindowSystemEvents();}return accepted;
}/*!Make Qt Gui process all events on the event queue immediately. Return theaccepted state for the last event on the queue.
*/
bool QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{const int count = QWindowSystemInterfacePrivate::windowSystemEventQueue.count();if (!count)return false;if (!QGuiApplication::instance()) {qWarning().nospace()<< "QWindowSystemInterface::flushWindowSystemEvents() invoked after ""QGuiApplication destruction, discarding " << count << " events.";QWindowSystemInterfacePrivate::windowSystemEventQueue.clear();return false;}if (QThread::currentThread() != QGuiApplication::instance()->thread()) {// Post a FlushEvents event which will trigger a call back to// deferredFlushWindowSystemEvents from the Gui thread.QMutexLocker locker(&QWindowSystemInterfacePrivate::flushEventMutex);QWindowSystemInterfacePrivate::FlushEventsEvent *e = new QWindowSystemInterfacePrivate::FlushEventsEvent(flags);QWindowSystemInterfacePrivate::handleWindowSystemEvent<AsynchronousDelivery>(e);QWindowSystemInterfacePrivate::eventsFlushed.wait(&QWindowSystemInterfacePrivate::flushEventMutex);} else {sendWindowSystemEvents(flags);}return QWindowSystemInterfacePrivate::eventAccepted.loadRelaxed() > 0;
}
ool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{int nevents = 0;while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {QWindowSystemInterfacePrivate::WindowSystemEvent *event = nullptr;if (QWindowSystemInterfacePrivate::platformFiltersEvents) {event = QWindowSystemInterfacePrivate::getWindowSystemEvent();} else {event = flags & QEventLoop::ExcludeUserInputEvents ?QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :QWindowSystemInterfacePrivate::getWindowSystemEvent();}if (!event)break;if (QWindowSystemInterfacePrivate::eventHandler) {if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))nevents++;} else {nevents++;QGuiApplicationPrivate::processWindowSystemEvent(event);}// Record the accepted state for the processed event// (excluding flush events). This state can then be// returned by flushWindowSystemEvents().if (event->type != QWindowSystemInterfacePrivate::FlushEvents)QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event->eventAccepted);delete event;}return (nevents > 0);
}void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);switch(e->type) {case QWindowSystemInterfacePrivate::Mouse:QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));break;case QWindowSystemInterfacePrivate::Wheel:QGuiApplicationPrivate::processWheelEvent(static_cast<QWindowSystemInterfacePrivate::WheelEvent *>(e));break;case QWindowSystemInterfacePrivate::Key:QGuiApplicationPrivate::processKeyEvent(static_cast<QWindowSystemInterfacePrivate::KeyEvent *>(e));break;case QWindowSystemInterfacePrivate::Touch:QGuiApplicationPrivate::processTouchEvent(static_cast<QWindowSystemInterfacePrivate::TouchEvent *>(e));break;case QWindowSystemInterfacePrivate::GeometryChange:QGuiApplicationPrivate::processGeometryChangeEvent(static_cast<QWindowSystemInterfacePrivate::GeometryChangeEvent*>(e));break;case QWindowSystemInterfacePrivate::Enter:QGuiApplicationPrivate::processEnterEvent(static_cast<QWindowSystemInterfacePrivate::EnterEvent *>(e));break;case QWindowSystemInterfacePrivate::Leave:QGuiApplicationPrivate::processLeaveEvent(static_cast<QWindowSystemInterfacePrivate::LeaveEvent *>(e));break;case QWindowSystemInterfacePrivate::ActivatedWindow:QGuiApplicationPrivate::processActivatedEvent(static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>(e));break;case QWindowSystemInterfacePrivate::WindowStateChanged:QGuiApplicationPrivate::processWindowStateChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowStateChangedEvent *>(e));break;case QWindowSystemInterfacePrivate::WindowScreenChanged:QGuiApplicationPrivate::processWindowScreenChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowScreenChangedEvent *>(e));break;case QWindowSystemInterfacePrivate::SafeAreaMarginsChanged:QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(static_cast<QWindowSystemInterfacePrivate::SafeAreaMarginsChangedEvent *>(e));break;case QWindowSystemInterfacePrivate::ApplicationStateChanged: {QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast<QWindowSystemInterfacePrivate::ApplicationStateChangedEvent *>(e);QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); }break;case QWindowSystemInterfacePrivate::ApplicationTermination:QGuiApplicationPrivate::processApplicationTermination(e);break;case QWindowSystemInterfacePrivate::FlushEvents: {QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast<QWindowSystemInterfacePrivate::FlushEventsEvent *>(e);QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); }break;case QWindowSystemInterfacePrivate::Close:QGuiApplicationPrivate::processCloseEvent(static_cast<QWindowSystemInterfacePrivate::CloseEvent *>(e));break;case QWindowSystemInterfacePrivate::ScreenOrientation:QGuiApplicationPrivate::processScreenOrientationChange(static_cast<QWindowSystemInterfacePrivate::ScreenOrientationEvent *>(e));break;case QWindowSystemInterfacePrivate::ScreenGeometry:QGuiApplicationPrivate::processScreenGeometryChange(static_cast<QWindowSystemInterfacePrivate::ScreenGeometryEvent *>(e));break;case QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInch:QGuiApplicationPrivate::processScreenLogicalDotsPerInchChange(static_cast<QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent *>(e));break;case QWindowSystemInterfacePrivate::ScreenRefreshRate:QGuiApplicationPrivate::processScreenRefreshRateChange(static_cast<QWindowSystemInterfacePrivate::ScreenRefreshRateEvent *>(e));break;case QWindowSystemInterfacePrivate::ThemeChange:QGuiApplicationPrivate::processThemeChanged(static_cast<QWindowSystemInterfacePrivate::ThemeChangeEvent *>(e));break;case QWindowSystemInterfacePrivate::Expose:QGuiApplicationPrivate::processExposeEvent(static_cast<QWindowSystemInterfacePrivate::ExposeEvent *>(e));break;case QWindowSystemInterfacePrivate::Tablet:QGuiApplicationPrivate::processTabletEvent(static_cast<QWindowSystemInterfacePrivate::TabletEvent *>(e));break;case QWindowSystemInterfacePrivate::TabletEnterProximity:QGuiApplicationPrivate::processTabletEnterProximityEvent(static_cast<QWindowSystemInterfacePrivate::TabletEnterProximityEvent *>(e));break;case QWindowSystemInterfacePrivate::TabletLeaveProximity:QGuiApplicationPrivate::processTabletLeaveProximityEvent(static_cast<QWindowSystemInterfacePrivate::TabletLeaveProximityEvent *>(e));break;
#ifndef QT_NO_GESTUREScase QWindowSystemInterfacePrivate::Gesture:QGuiApplicationPrivate::processGestureEvent(static_cast<QWindowSystemInterfacePrivate::GestureEvent *>(e));break;
#endifcase QWindowSystemInterfacePrivate::PlatformPanel:QGuiApplicationPrivate::processPlatformPanelEvent(static_cast<QWindowSystemInterfacePrivate::PlatformPanelEvent *>(e));break;case QWindowSystemInterfacePrivate::FileOpen:QGuiApplicationPrivate::processFileOpenEvent(static_cast<QWindowSystemInterfacePrivate::FileOpenEvent *>(e));break;
#ifndef QT_NO_CONTEXTMENUcase QWindowSystemInterfacePrivate::ContextMenu:QGuiApplicationPrivate::processContextMenuEvent(static_cast<QWindowSystemInterfacePrivate::ContextMenuEvent *>(e));break;
#endifcase QWindowSystemInterfacePrivate::EnterWhatsThisMode:QGuiApplication::postEvent(QGuiApplication::instance(), new QEvent(QEvent::EnterWhatsThisMode));break;default:qWarning() << "Unknown user input event type:" << e->type;break;}
}void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{QEvent::Type type = QEvent::None;Qt::MouseButton button = Qt::NoButton;QWindow *window = e->window.data();bool positionChanged = QGuiApplicationPrivate::lastCursorPosition != e->globalPos;bool mouseMove = false;bool mousePress = false;if (e->enhancedMouseEvent()) {type = e->buttonType;button = e->button;if (type == QEvent::NonClientAreaMouseMove || type == QEvent::MouseMove)mouseMove = true;else if (type == QEvent::NonClientAreaMouseButtonPress || type == QEvent::MouseButtonPress)mousePress = true;if (!mouseMove && positionChanged) {QWindowSystemInterfacePrivate::MouseEvent moveEvent(window, e->timestamp,e->localPos, e->globalPos, e->buttons ^ button, e->modifiers, Qt::NoButton,e->nonClientArea ? QEvent::NonClientAreaMouseMove : QEvent::MouseMove,e->source, e->nonClientArea);if (e->synthetic())moveEvent.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;processMouseEvent(&moveEvent); // mouse move excluding state changeprocessMouseEvent(e); // the original mouse eventreturn;}} else {Qt::MouseButtons stateChange = e->buttons ^ mouse_buttons;if (positionChanged && (stateChange != Qt::NoButton)) {QWindowSystemInterfacePrivate::MouseEvent moveEvent(window, e->timestamp, e->localPos,e->globalPos, mouse_buttons, e->modifiers, Qt::NoButton, QEvent::None, e->source,e->nonClientArea);if (e->synthetic())moveEvent.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;processMouseEvent(&moveEvent); // mouse move excluding state changeprocessMouseEvent(e); // the original mouse eventreturn;}// In the compatibility path we deduce event type and button that caused the eventif (positionChanged) {mouseMove = true;type = e->nonClientArea ? QEvent::NonClientAreaMouseMove : QEvent::MouseMove;} else {// Check to see if a new button has been pressed/released.for (uint mask = Qt::LeftButton; mask <= Qt::MaxMouseButton; mask <<= 1) {if (stateChange & mask) {button = Qt::MouseButton(mask);break;}}if (button == Qt::NoButton) {// Ignore mouse events that don't change the current state. This shouldn't// really happen, getting here can only mean that the stored button state// is out of sync with the actual physical button state.return;}if (button & e->buttons) {mousePress = true;type = e->nonClientArea ? QEvent::NonClientAreaMouseButtonPress: QEvent::MouseButtonPress;} else {type = e->nonClientArea ? QEvent::NonClientAreaMouseButtonRelease: QEvent::MouseButtonRelease;}}}modifier_buttons = e->modifiers;QPointF localPoint = e->localPos;QPointF globalPoint = e->globalPos;bool doubleClick = false;if (mouseMove) {QGuiApplicationPrivate::lastCursorPosition = globalPoint;const auto doubleClickDistance = e->source == Qt::MouseEventNotSynthesized ?mouseDoubleClickDistance : touchDoubleTapDistance;if (qAbs(globalPoint.x() - mousePressX) > doubleClickDistance ||qAbs(globalPoint.y() - mousePressY) > doubleClickDistance)mousePressButton = Qt::NoButton;} else {mouse_buttons = e->buttons;if (mousePress) {ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval());doubleClick = e->timestamp - mousePressTime < doubleClickInterval && button == mousePressButton;mousePressTime = e->timestamp;mousePressButton = button;const QPoint point = QGuiApplicationPrivate::lastCursorPosition.toPoint();mousePressX = point.x();mousePressY = point.y();}}if (e->nullWindow()) {window = QGuiApplication::topLevelAt(globalPoint.toPoint());if (window) {// Moves and the release following a press must go to the same// window, even if the cursor has moved on over another window.if (e->buttons != Qt::NoButton) {if (!currentMousePressWindow)currentMousePressWindow = window;elsewindow = currentMousePressWindow;} else if (currentMousePressWindow) {window = currentMousePressWindow;currentMousePressWindow = 0;}QPointF delta = globalPoint - globalPoint.toPoint();localPoint = window->mapFromGlobal(globalPoint.toPoint()) + delta;}}if (!window)return;#ifndef QT_NO_CURSORif (!e->synthetic()) {if (const QScreen *screen = window->screen())if (QPlatformCursor *cursor = screen->handle()->cursor()) {const QPointF nativeLocalPoint = QHighDpi::toNativePixels(localPoint, screen);const QPointF nativeGlobalPoint = QHighDpi::toNativePixels(globalPoint, screen);QMouseEvent ev(type, nativeLocalPoint, nativeLocalPoint, nativeGlobalPoint,button, e->buttons, e->modifiers, e->source);ev.setTimestamp(e->timestamp);cursor->pointerEvent(ev);}}
#endifQMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source);ev.setTimestamp(e->timestamp);if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {// a modal window is blocking this window, don't allow mouse events throughreturn;}if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {// QtBUG-25831, used to suppress delivery in qwidgetwindow.cppsetMouseEventFlags(&ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);}QGuiApplication::sendSpontaneousEvent(window, &ev);e->eventAccepted = ev.isAccepted();if (!e->synthetic() && !ev.isAccepted()&& !e->nonClientArea&& qApp->testAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents)) {if (!m_fakeTouchDevice) {m_fakeTouchDevice = new QTouchDevice;QWindowSystemInterface::registerTouchDevice(m_fakeTouchDevice);}QList<QWindowSystemInterface::TouchPoint> points;QWindowSystemInterface::TouchPoint point;point.id = 1;point.area = QRectF(globalPoint.x() - 2, globalPoint.y() - 2, 4, 4);// only translate left button related events to// avoid strange touch event sequences when several// buttons are pressedif (type == QEvent::MouseButtonPress && button == Qt::LeftButton) {point.state = Qt::TouchPointPressed;} else if (type == QEvent::MouseButtonRelease && button == Qt::LeftButton) {point.state = Qt::TouchPointReleased;} else if (type == QEvent::MouseMove && (e->buttons & Qt::LeftButton)) {point.state = Qt::TouchPointMoved;} else {return;}points << point;QEvent::Type type;QList<QTouchEvent::TouchPoint> touchPoints =QWindowSystemInterfacePrivate::fromNativeTouchPoints(points, window, QTouchDevicePrivate::get(m_fakeTouchDevice)->id, &type);QWindowSystemInterfacePrivate::TouchEvent fake(window, e->timestamp, type, m_fakeTouchDevice, touchPoints, e->modifiers);fake.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;processTouchEvent(&fake);}if (doubleClick) {mousePressButton = Qt::NoButton;if (!e->window.isNull() || e->nullWindow()) { // QTBUG-36364, check if window closed in response to pressconst QEvent::Type doubleClickType = e->nonClientArea ? QEvent::NonClientAreaMouseButtonDblClick : QEvent::MouseButtonDblClick;QMouseEvent dblClickEvent(doubleClickType, localPoint, localPoint, globalPoint,button, e->buttons, e->modifiers, e->source);dblClickEvent.setTimestamp(e->timestamp);QGuiApplication::sendSpontaneousEvent(window, &dblClickEvent);}}
}/*!\internal
*/
bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{Q_TRACE(QCoreApplication_sendSpontaneousEvent, receiver, event, event->type());if (event)event->spont = true;return notifyInternal2(receiver, event);
}

/*!\internal
*/
bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{Q_TRACE(QCoreApplication_sendSpontaneousEvent, receiver, event, event->type());if (event)event->spont = true;return notifyInternal2(receiver, event);
}/*!\internal\since 5.6This function is here to make it possible for Qt extensions tohook into event notification without subclassing QApplication.
*/
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();if (!self && selfRequired)return false;// Make it possible for Qt Script to hook into events even// though QApplication is subclassed...bool result = false;void *cbdata[] = { receiver, event, &result };if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {return result;}// Qt enforces the rule that events can only be sent to objects in// the current thread, so receiver->d_func()->threadData is// equivalent to QThreadData::current(), just without the function// call overhead.QObjectPrivate *d = receiver->d_func();QThreadData *threadData = d->threadData;QScopedScopeLevelCounter scopeLevelCounter(threadData);if (!selfRequired)return doNotify(receiver, event);return self->notify(receiver, event);
}bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{// no events are delivered after ~QCoreApplication() has startedif (QCoreApplicationPrivate::is_app_closing)return true;return doNotify(receiver, event);
}static bool doNotify(QObject *receiver, QEvent *event)
{if (receiver == 0) {                        // serious errorqWarning("QCoreApplication::notify: Unexpected null receiver");return true;}#ifndef QT_NO_DEBUGQCoreApplicationPrivate::checkReceiverThread(receiver);
#endifreturn receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}Helper function called by QCoreApplicationPrivate::notify() and qapplication.cpp*/
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{// Note: when adjusting the tracepoints in here// consider adjusting QApplicationPrivate::notify_helper too.Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());bool consumed = false;bool filtered = false;Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);// send to all application event filters (only does anything in the main thread)if (QCoreApplication::self&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread()&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {filtered = true;return filtered;}// send to all receiver event filtersif (sendThroughObjectEventFilters(receiver, event)) {filtered = true;return filtered;}// deliver the eventconsumed = receiver->event(event);return consumed;
}receiver->event 指向QObject ;

可以看到,流程中涉及两个事件过滤器的调用:sendThroughApplicationEventFilters和sendThroughObjectEventFilters,事件过滤器调用完后,才是调用接收者的event函数。

QApplication的事件过滤器
上一小节提到的sendThroughApplicationEventFilters是处理app的事件过滤器。代码里会调用给app安装的所有事件过滤器(从代码中的注释看到,app的事件过滤器只能在主线程中被调用),我们给app安装的事件过滤器就是在这个阶段被执行的。

bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
{// We can't access the application event filters outside of the main thread (race conditions)Q_ASSERT(receiver->d_func()->threadData->thread.loadAcquire() == mainThread());if (extraData) {// application event filters are only called for objects in the GUI threadfor (int i = 0; i < extraData->eventFilters.size(); ++i) {QObject *obj = extraData->eventFilters.at(i);...if (obj->eventFilter(receiver, event))return true;}}return false;
}

QObject的事件过滤器
sendThroughObjectEventFilters是处理对象的事件过滤器。里面会调用给接收者安装的全部事件过滤器,我们通过installEventFilter给某个对象安装的事件过滤器就是在这个阶段执行的。

bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
{...for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);...if (obj->eventFilter(receiver, event))return true;}}return false;
}

对象的event方法
从QCoreApplicationPrivate::notify_helper代码可以看到,处理完事件接收者的事件过滤器后,就会调用接收者的event方法来处理事件。
每一个从QObject继承出来的子类都有event方法,都可以处理自己的事件。
我们简单看下QWidget的event方法:

bool QWidget::event(QEvent *event)
{...switch (event->type()) {...case QEvent::MouseMove:mouseMoveEvent((QMouseEvent*)event);break;case QEvent::MouseButtonPress:mousePressEvent((QMouseEvent*)event);break;case QEvent::MouseButtonRelease:mouseReleaseEvent((QMouseEvent*)event);break;case QEvent::MouseButtonDblClick:mouseDoubleClickEvent((QMouseEvent*)event);break;...
}到这里

是不是很熟悉,各个具体的事件处理函数就是在这个阶段被调用的。

相关文章:

QT 之wayland 事件处理分析基于qt5wayland5.14.2

1. Qt wayland 初始化 接收鼠标/案件&#xff0c;触摸屏等事件事件 QWaylandNativeInterface : public QPlatformNativeInterface 在QWaylandNativeInterface 继承qpa 接口类QPlatformNativeInterface; 1.1 初始化鼠标&#xff1a; void *QWaylandNativeInterface::nativeR…...

【this 和 super 的区别】

在 Java 中&#xff0c;this 和 super 都是关键字&#xff0c;表示当前对象和父类对象。 this 关键字可以用于以下几种情况&#xff1a; 引用当前对象的成员变量&#xff0c;方法和构造方法&#xff0c;用于区分局部变量和成员变量重名的情况&#xff1b; 调用当前类的另外一…...

K8s:Monokle Desktop 一个集Yaml资源编写、项目管理、集群管理的 K8s IDE

写在前面 Monokle Desktop 是 kubeshop 推出的一个开源的 K8s IDE相关项目还有 Monokle CLI 和 Monokle Cloud相比其他的工具&#xff0c;Monokle Desktop 功能较全面&#xff0c;涉及 k8s 管理的整个生命周期博文内容&#xff1a;Monokle Desktop 下载安装&#xff0c;项目管理…...

自动化测试实战篇(8),jmeter并发测试登录接口,模拟从100到1000个用户同时登录测试服务器压力

首先进行使用jmeter进行并发测试之前就需要搞清楚线程和进程的区别还需要理解什么是并发、高并发、并行。还需要理解高并发中的以及老生常谈的&#xff0c;TCP三次握手协议和TCP四次握手协议**TCP三次握手协议指&#xff1a;****TCP四次挥手协议&#xff1a;**进入Jmeter&#…...

ATTCK v12版本战术实战研究—持久化(二)

一、前言前几期文章中&#xff0c;我们介绍了ATT&CK中侦察、资源开发、初始访问、执行战术、持久化战术的知识。那么从前文中介绍的相关持久化子技术来开展测试&#xff0c;进行更深一步的分析。本文主要内容是介绍攻击者在运用持久化子技术时&#xff0c;在相关的资产服务…...

python函数式编程

1 callable内建函数判断一个名字是否为一个可调用函数 >>> import math >>> x 1 >>> y math.sqrt >>> callable(x) False >>> callable(y) True 2 记录函数&#xff08;文档字符串&#xff09; >>> def square(x): …...

3.linux下安装mysql

1.安装前的环境准备 查看是否安装过mysql 首先检测Linux操作系统中是否安装了MySQL&#xff1a; # rpm -qa | grep -i mysql 卸载安装包 如果有信息出现&#xff0c;则进行删除&#xff0c;命令如下&#xff1a; # rpm -e --nodeps 包名 删除老版本mysql的开发头文件和…...

17、MySQL分库分表,原理实战

MySQL分库分表,原理实战 1.MyCAT分布式架构入门及双主架构1.1 主从架构1.2 MyCAT安装1.3 启动和连接1.4 配置文件介绍2.MyCAT读写分离架构2.1 架构说明2.2 创建用户2.3 schema.xml2.4 连接说明2.5 读写测试2.6 当前是单节点3.MyCAT高可用读写分离架构3.1 架构说明3.3 schema.xm…...

【C++的OpenCV】第九课-OpenCV图像常用操作(六):图像形态学-阈值的概念、功能及操作(threshold()函数))

目录一、阈值&#xff08;thresh&#xff09;的概念二、阈值在图形学中的用途三、阈值的作用和操作3.1 在OpenCV中可以进行的阈值操作3.2 操作实例3.2.1 threshold()函数介绍3.2.2 实例3.2.3 结果上节课的内容&#xff08;作者还是鼓励各位同学按照顺序进行学习哦&#xff09;&…...

[Java代码审计]—MCMS

环境搭建 MCMS 5.2.4&#xff1a;https://gitee.com/mingSoft/MCMS/tree/5.2.4/利用 idea 打开项目 创建数据库 mcms&#xff0c;导入 doc/mcms-5.2.8.sql 修改 src/main/resources/application-dev.yml 中关于数据库设置参数 启动项目登录后台 http://localhost:8080/ms/l…...

《程序员面试金典(第6版)》面试题 01.08. 零矩阵

题目描述 编写代码&#xff0c;移除未排序链表中的重复节点。保留最开始出现的节点。 示例1: 输入&#xff1a;[1, 2, 3, 3, 2, 1] 输出&#xff1a;[1, 2, 3] -示例2: 输入&#xff1a;[1, 1, 1, 1, 2] 输出&#xff1a;[1, 2] 提示&#xff1a; 链表长度在[0, 20000]范…...

初识 Python

文章目录简介用途解释器命令行模式交互模式输入和输出简介 高级编程语言&#xff0c;解释型语言代码在执行时会逐行翻译成 CPU 能理解的机器码代码精简&#xff0c;但运行速度慢基础代码库丰富&#xff0c;还有大量第三方库代码不能加密 用途 网络应用工具软件包装其他语言开…...

常用sql语句分享

SELECT COUNT(DISTINCT money) FROM ac_association_course;#COUNT() 函数返回匹配指定条件的行数SELECT AVG(money) FROM ac_association_course;#AVG 函数返回数值列的平均值。NULL 值不包括在计算中SELECT id FROM ac_association_course order by id desc limit 1;#返回最大…...

极狐GitLab DevSecOps 为企业许可证安全合规保驾护航

本文来自&#xff1a; 小马哥 极狐(GitLab) 技术布道师 开源许可证是开源软件的法律武器&#xff0c;是第三方正确使用开源软件的安全合规依据。 根据 Linux 发布的 SBOM 报告显示&#xff0c;98% 的企业都在使用开源软件&#xff08;中文版报告详情&#xff09;。随着开源使用…...

后端程序员的前端基础-前端三剑客之HTML

文章目录1 HTML简介1.1 什么是HTML1.2 HTML能做什么1.3 HTML书写规范2 HTML基本标签2.1 结构标签2.2 排版标签2.3 块标签2.4 基本文字标签2.5 文本格式化标签2.6 标题标签2.7 列表标签(清单标签)2.8 图片标签2.9 链接标签2.10 表格标签3 HTML表单标签3.1 form元素常用属性3.2 i…...

VS2019加载解决方案时不能自动打开之前的文档(回忆消失)

✏️作者&#xff1a;枫霜剑客 &#x1f4cb;系列专栏&#xff1a;C实战宝典 &#x1f332;上一篇: 错误error c3861 :“_T“:找不到标识符 逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、问题描…...

ConcurrentHashMap-Java八股面试(五)

系列文章目录 第一章 ArrayList-Java八股面试(一) 第二章 HashMap-Java八股面试(二) 第三章 单例模式-Java八股面试(三) 第四章 线程池和Volatile关键字-Java八股面试(四) 提示&#xff1a;动态每日更新算法题&#xff0c;想要学习的可以关注一下 文章目录系列文章目录一、…...

互联网衰退期,测试工程师35岁的路该怎么走...

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…...

Windows Cannot Initialize Data Bindings 问题的解决方法

前言 拿到一个调试程序, 怎么折腾都打不开, 在客户那边, 尝试了几个系统版本, 发现Windows 10 21H2 版本可以正常运行。 尝试 系统篇 系统结果公司电脑 Windows 8有问题…下载安装 Windows10 22H2问题依旧下载安装 Windows10 21H2问题依旧家里的 笔记本Window 11正常 网上…...

Leetcode每日一题 1487. 保证文件名唯一

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…...

Linux常用命令——lsusb命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) lsusb 显示本机的USB设备列表信息 补充说明 lsusb命令用于显示本机的USB设备列表&#xff0c;以及USB设备的详细信息。 lsusb命令是一个学习USB驱动开发&#xff0c;认识USB设备的助手&#xff0c;推荐大家使用…...

Python——我愿称之为最简单的语言

Python——我愿称之为最简单的语言开发工具基础语法变量和数据类型列表和元组字典if语句while语句函数类文件与异常测试代码参考书籍&#xff1a;《python编程从入门到实践》 开发工具 python编程环境分为两个部分&#xff1a;python解释器和文本编辑器。运行.py文件时&#…...

java.io.IOException: Broken pipe

1、问题出现的场景 线上环境&#xff0c;拉取对账单&#xff0c;走的接口的形式&#xff0c;当天单量比较大&#xff0c;就出现了&#xff0c;拉取订单超时&#xff0c;报了个错java.io.IOException: Broken pipe。 2、解决方案 我们设置的超时时间是100S&#xff0c;由于当…...

Python——列表排序和赋值

&#xff08;1&#xff09;列表排序&#xff1a; 列表排序方法 ls.sort() 对列表ls 中的数据在原地进行排序 ls [13, 5, 73, 4, 9] ls.sort()ls.sort(reverseFalse) 默认升序&#xff0c;reverseTrue&#xff0c;降序 ls [13, 5, 73, 4, 9] ls.sort(reverseTrue)key指定排序时…...

python+pytest接口自动化(7)-cookie绕过登录(保持登录状态)

在编写接口自动化测试用例或其他脚本的过程中&#xff0c;经常会遇到需要绕过用户名/密码或验证码登录&#xff0c;去请求接口的情况&#xff0c;一是因为有时验证码会比较复杂&#xff0c;比如有些图形验证码&#xff0c;难以通过接口的方式去处理&#xff1b;再者&#xff0c…...

【连接池】什么是HikariCP?HikariCP 解决了哪些问题?为什么要使用 HikariCP?

文章目录什么是连接池什么是HikariCPHikariCP 解决了哪些问题&#xff1f;为什么要使用 HikariCP&#xff1f;HikariCP 的使用Maven支持数据库什么是连接池 数据库连接池负责分配、管理和释放数据库的连接。 数据库连接复用&#xff1a;重复使用现有的数据库长连接&#xff0…...

Tapdata Cloud 基础课:新功能详解之「微信告警」,更及时的告警通知渠道

【前言】作为中国的 “Fivetran/Airbyte”, Tapdata 是一个以低延迟数据移动为核心优势构建的现代数据平台&#xff0c;内置 60 数据连接器&#xff0c;拥有稳定的实时采集和传输能力、秒级响应的数据实时计算能力、稳定易用的数据实时服务能力&#xff0c;以及低代码可视化操作…...

【巨人的肩膀】JAVA面试总结(四)

&#x1f4aa;、JVM 目录&#x1f4aa;、JVM1、说一下JVM的主要组成部分及其作用2、什么是JVM内存结构&#xff08;谈谈对运行时数据区的理解&#xff09;3、堆和栈的区别是什么4、堆中存什么&#xff1f;栈中存什么&#xff1f;5、为什么不把基本类型放堆中呢&#xff1f;6、为…...

攒了一冬的甜,米易枇杷借力新电商走出川西大山

“绿暗初迎夏&#xff0c;红残不及春。魏花非老伴&#xff0c;卢橘是乡人。”苏轼文中的卢橘&#xff0c;就是枇杷&#xff0c;在苏轼看来&#xff0c;相较于姚黄魏紫&#xff0c;来自故乡四川的枇杷更为亲近。 四川省攀枝花市米易县是全国枇杷早熟产区之一&#xff0c;得益于…...

python-测试相关基础知识-补充

文章目录 1.面向对象1.1 基础概念1.2 面向对象关键字1.2.1 class关键字1.2.2 __init__初始化方法1.2.3 __del__销毁方法1.2.4 __str__输出字符串方法1.3 面向对象三大特点1.3.1 封装1.3.2 继承1.3.3 多态1.4 类属性和类方法1.5 静态方法2.文件操作2.1 文件基本操作2.2 按行读取…...

做网站采集内容/杭州网站seo外包

#!/bin/bash echo "wxc_dump.sh is running!" #抓包600秒保存一个包以时间命名 tcpdump -i ens160 -s0 -G 600 -Z root -w ./%Y%m%d_%H%M%S.pcap & #while条件括号里面不能少两个空格 #find当前路径所有后缀.pcap&#xff0c;文件内容最后修改时间比当前大于120分…...

台州路桥网站建设/常用的seo工具

之前遇到过"/",在esclipse中报错&#xff0c;只认识“//”&#xff0c;“\”符号&#xff0c;需要将string字符串“/”&#xff0c;转换成“//”或者“\”怎么转呢&#xff1f; 获取字符串是 String path "/root/data/image"; StringBuilder sb new Stri…...

建站城/站长素材官网免费

一. 硬件环境1.1. ubuntu 16.04 PC作为mysql服务器二. 安装mysql2.1. 依次输入这三条命令sudo apt-get install mysql-serversudo apt install mysql-clientsudo apt install libmysqlclient-dev2.2. 检验是否安装mysql成功sudo netstat -tap | grep mysql三. mysql配置3.1. 实…...

wordpress和discuz共存/曼联对利物浦新闻

# -*- coding: utf-8 -*- """ 图像模板匹配 模板匹配是在图像中寻找目标的方法之一 模板匹配的工作方式模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。假设我们有一张100x100…...

百度权重查询方法/东莞百度推广优化排名

为什么80%的码农都做不了架构师&#xff1f;>>> 运行一下 python manage.py sycdb 报错&#xff1a; ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb 运行&#xff1a; pip install mysql-python 报错&#xff1a;EnvironmentError…...

免费建站网站一级大录像不卡/百度一下百度主页官网

背景當我再搭建WSUS的時候&#xff0c;搭建Vcenter的時候&#xff0c;等等&#xff0c;如果提示你80端口被佔用&#xff0c;或者報端口佔用的error的時候。如何去查找哪個程序佔用了這個端口呢&#xff1f;命令&#xff1a;netstat -ano : 查看所有連接的PID 及端口號 1、netst…...