Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/f1x/openauto/autoapp/Projection/InputDevice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <QObject>
#include <QKeyEvent>
#include <QTouchEvent>
#include <map>
#include <f1x/openauto/autoapp/Projection/IInputDevice.hpp>
#include <f1x/openauto/autoapp/Configuration/IConfiguration.hpp>

Expand Down Expand Up @@ -51,13 +53,17 @@ class InputDevice: public QObject, public IInputDevice, boost::noncopyable
bool handleKeyEvent(QEvent* event, QKeyEvent* key);
void dispatchKeyEvent(ButtonEvent event);
bool handleTouchEvent(QEvent* event);
bool handleMultiTouchEvent(QTouchEvent* touchEvent);
void translateTouchPoint(const QTouchEvent::TouchPoint& qtPoint, TouchPoint& ourPoint);

QObject& parent_;
configuration::IConfiguration::Pointer configuration_;
QRect touchscreenGeometry_;
QRect displayGeometry_;
IInputDeviceEventHandler* eventHandler_;
std::mutex mutex_;
std::map<int, uint32_t> touchPointIdMap_; // Maps Qt touch IDs to our sequential IDs
uint32_t nextTouchPointId_;
};

}
Expand Down
11 changes: 9 additions & 2 deletions include/f1x/openauto/autoapp/Projection/InputEvent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#pragma once

#include <vector>
#include <aap_protobuf/service/media/sink/message/KeyCode.pb.h>
#include <aap_protobuf/service/inputsource/message/PointerAction.pb.h>
#include <aasdk/IO/Promise.hpp>
Expand Down Expand Up @@ -52,14 +53,20 @@ struct ButtonEvent
aap_protobuf::service::media::sink::message::KeyCode code;
};

struct TouchEvent
struct TouchPoint
{
aap_protobuf::service::inputsource::message::PointerAction type;
uint32_t x;
uint32_t y;
uint32_t pointerId;
};

struct TouchEvent
{
aap_protobuf::service::inputsource::message::PointerAction type;
std::vector<TouchPoint> pointers;
uint32_t actionIndex; // Index of the pointer that changed state
};

}
}
}
Expand Down
2 changes: 2 additions & 0 deletions include/f1x/openauto/autoapp/Projection/QtVideoOutput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class QtVideoOutput: public QObject, public VideoOutput, boost::noncopyable

public:
QtVideoOutput(configuration::IConfiguration::Pointer configuration);
~QtVideoOutput() override;
bool open() override;
bool init() override;
void write(uint64_t timestamp, const aasdk::common::DataConstBuffer& buffer) override;
Expand All @@ -58,6 +59,7 @@ protected slots:
void onError(QMediaPlayer::Error error);

private:
void cleanupPlayer();
SequentialBuffer videoBuffer_;
std::unique_ptr<QVideoWidget> videoWidget_;
std::unique_ptr<QMediaPlayer> mediaPlayer_;
Expand Down
161 changes: 160 additions & 1 deletion src/autoapp/Projection/InputDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ InputDevice::InputDevice(QObject& parent, configuration::IConfiguration::Pointer
, touchscreenGeometry_(touchscreenGeometry)
, displayGeometry_(displayGeometry)
, eventHandler_(nullptr)
, nextTouchPointId_(0)
{
this->moveToThread(parent.thread());
parent_.setAttribute(Qt::WA_AcceptTouchEvents, true);
}

void InputDevice::start(IInputDeviceEventHandler& eventHandler)
Expand Down Expand Up @@ -71,8 +73,16 @@ bool InputDevice::eventFilter(QObject* obj, QEvent* event)
return this->handleKeyEvent(event, key);
}
}
else if(event->type() == QEvent::TouchBegin ||
event->type() == QEvent::TouchUpdate ||
event->type() == QEvent::TouchEnd ||
event->type() == QEvent::TouchCancel)
{
return this->handleMultiTouchEvent(static_cast<QTouchEvent*>(event));
}
else if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseMove)
{
// Fallback to mouse events if touch events are not available
return this->handleTouchEvent(event);
}
}
Expand Down Expand Up @@ -215,7 +225,14 @@ bool InputDevice::handleTouchEvent(QEvent* event)
{
const uint32_t x = (static_cast<float>(mouse->pos().x()) / touchscreenGeometry_.width()) * displayGeometry_.width();
const uint32_t y = (static_cast<float>(mouse->pos().y()) / touchscreenGeometry_.height()) * displayGeometry_.height();
eventHandler_->onTouchEvent({type, x, y, 0});

// Create single-touch event for mouse fallback
TouchEvent event;
event.type = type;
event.actionIndex = 0;
event.pointers.push_back({x, y, 0});

eventHandler_->onTouchEvent(event);
}

return true;
Expand All @@ -236,6 +253,148 @@ IInputDevice::ButtonCodes InputDevice::getSupportedButtonCodes() const
return configuration_->getButtonCodes();
}

bool InputDevice::handleMultiTouchEvent(QTouchEvent* touchEvent)
{
if(!configuration_->getTouchscreenEnabled())
{
return true;
}

OPENAUTO_LOG(debug) << "[InputDevice] handleMultiTouchEvent: type=" << touchEvent->type()
<< " touchPointCount=" << touchEvent->touchPoints().size();

TouchEvent event;
event.actionIndex = 0; // Will be updated based on which pointer changed state

// Determine the action type and which pointer triggered it
const auto& touchPoints = touchEvent->touchPoints();

if(touchPoints.isEmpty())
{
return true;
}

// Find the pointer that changed state (for ACTION_DOWN, ACTION_UP, ACTION_POINTER_DOWN, ACTION_POINTER_UP)
int changedPointIndex = -1;
Qt::TouchPointState changedState = Qt::TouchPointStationary;

for(int i = 0; i < touchPoints.size(); ++i)
{
const auto& point = touchPoints[i];
if(point.state() == Qt::TouchPointPressed || point.state() == Qt::TouchPointReleased)
{
changedPointIndex = i;
changedState = point.state();
break;
}
}

// Determine the action based on event type and pointer states
if(touchEvent->type() == QEvent::TouchBegin)
{
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_DOWN;
event.actionIndex = 0;
}
else if(touchEvent->type() == QEvent::TouchEnd)
{
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_UP;
// Find the released pointer index
for(int i = 0; i < touchPoints.size(); ++i)
{
if(touchPoints[i].state() == Qt::TouchPointReleased)
{
event.actionIndex = i;
break;
}
}
}
else if(touchEvent->type() == QEvent::TouchUpdate)
{
if(changedState == Qt::TouchPointPressed)
{
// Additional finger went down
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_POINTER_DOWN;
event.actionIndex = changedPointIndex;
}
else if(changedState == Qt::TouchPointReleased)
{
// One finger lifted while others remain
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_POINTER_UP;
event.actionIndex = changedPointIndex;
}
else
{
// Movement only
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_MOVED;
event.actionIndex = 0;
}
}
else if(touchEvent->type() == QEvent::TouchCancel)
{
event.type = aap_protobuf::service::inputsource::message::PointerAction::ACTION_CANCEL;
event.actionIndex = 0;
touchPointIdMap_.clear();
nextTouchPointId_ = 0;
}
else
{
return true;
}

// Translate all touch points
for(const auto& qtPoint : touchPoints)
{
// Skip released points except for UP actions
if(qtPoint.state() == Qt::TouchPointReleased &&
event.type != aap_protobuf::service::inputsource::message::PointerAction::ACTION_UP &&
event.type != aap_protobuf::service::inputsource::message::PointerAction::ACTION_POINTER_UP)
{
continue;
}

TouchPoint ourPoint;
translateTouchPoint(qtPoint, ourPoint);
event.pointers.push_back(ourPoint);

// Clean up released touch points from map
if(qtPoint.state() == Qt::TouchPointReleased)
{
touchPointIdMap_.erase(qtPoint.id());
}
}

if(!event.pointers.empty())
{
OPENAUTO_LOG(debug) << "[InputDevice] Sending touch event: action=" << event.type
<< " actionIndex=" << event.actionIndex
<< " pointerCount=" << event.pointers.size();
eventHandler_->onTouchEvent(event);
}

return true;
}

void InputDevice::translateTouchPoint(const QTouchEvent::TouchPoint& qtPoint, TouchPoint& ourPoint)
{
// Map Qt touch point ID to our sequential ID
int qtId = qtPoint.id();
if(touchPointIdMap_.find(qtId) == touchPointIdMap_.end())
{
touchPointIdMap_[qtId] = nextTouchPointId_++;
}
ourPoint.pointerId = touchPointIdMap_[qtId];

// Scale coordinates from touchscreen geometry to display geometry
QPointF pos = qtPoint.pos();
ourPoint.x = static_cast<uint32_t>((pos.x() / touchscreenGeometry_.width()) * displayGeometry_.width());
ourPoint.y = static_cast<uint32_t>((pos.y() / touchscreenGeometry_.height()) * displayGeometry_.height());

OPENAUTO_LOG(debug) << "[InputDevice] Touch point: qtId=" << qtId
<< " ourId=" << ourPoint.pointerId
<< " pos=(" << ourPoint.x << "," << ourPoint.y << ")"
<< " state=" << qtPoint.state();
}

}
}
}
Expand Down
58 changes: 44 additions & 14 deletions src/autoapp/Projection/QtVideoOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/

#include <QApplication>
#include <QGuiApplication>
#include <QScreen>
#include <mutex>
#include <f1x/openauto/autoapp/Projection/QtVideoOutput.hpp>
#include <f1x/openauto/Common/Log.hpp>
Expand All @@ -38,11 +40,19 @@ QtVideoOutput::QtVideoOutput(configuration::IConfiguration::Pointer configuratio
{
this->moveToThread(QApplication::instance()->thread());
connect(this, &QtVideoOutput::startPlayback, this, &QtVideoOutput::onStartPlayback, Qt::BlockingQueuedConnection);
// Use QueuedConnection (non-blocking) for stop to avoid deadlocks if Qt event loop is blocked
connect(this, &QtVideoOutput::stopPlayback, this, &QtVideoOutput::onStopPlayback, Qt::QueuedConnection);
connect(this, &QtVideoOutput::stopPlayback, this, &QtVideoOutput::onStopPlayback, Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(this, "createVideoOutput", Qt::BlockingQueuedConnection);
}

QtVideoOutput::~QtVideoOutput()
{
OPENAUTO_LOG(info) << "[QtVideoOutput] Destructor called, ensuring cleanup";
// Force synchronous cleanup if not already stopped
if (playerReady_ || mediaPlayer_) {
cleanupPlayer();
}
}

void QtVideoOutput::createVideoOutput()
{
OPENAUTO_LOG(info) << "[QtVideoOutput] createVideoOutput()";
Expand Down Expand Up @@ -95,11 +105,25 @@ void QtVideoOutput::onStartPlayback()
videoWidget_->setAttribute(Qt::WA_OpaquePaintEvent, true);
videoWidget_->setAttribute(Qt::WA_NoSystemBackground, true);
videoWidget_->setAspectRatioMode(Qt::IgnoreAspectRatio);
videoWidget_->setFocus();
videoWidget_->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);

// Get the physical screen geometry and set widget to exactly match it
QScreen *screen = QGuiApplication::primaryScreen();
if (screen != nullptr) {
QRect screenGeometry = screen->geometry();
videoWidget_->setGeometry(screenGeometry);
OPENAUTO_LOG(info) << "[QtVideoOutput] Set video widget geometry to: "
<< screenGeometry.width() << "x" << screenGeometry.height()
<< " at (" << screenGeometry.x() << "," << screenGeometry.y() << ")";
} else {
// Fallback to fullscreen if screen detection fails
videoWidget_->setFullScreen(true);
OPENAUTO_LOG(warning) << "[QtVideoOutput] Could not detect screen, using setFullScreen()";
}

videoWidget_->raise();
videoWidget_->setFullScreen(true);
videoWidget_->show();
videoWidget_->setFocus();
videoWidget_->activateWindow();

// Connect state change signals to track when player is ready
Expand All @@ -122,26 +146,32 @@ void QtVideoOutput::onStartPlayback()
OPENAUTO_LOG(debug) << "[QtVideoOutput] Player error state -> " << mediaPlayer_->errorString().toStdString();
}

void QtVideoOutput::onStopPlayback()
void QtVideoOutput::cleanupPlayer()
{
OPENAUTO_LOG(info) << "[QtVideoOutput] onStopPlayback()";

std::lock_guard<std::mutex> lock(writeMutex_);
playerReady_ = false;
initialBufferingDone_ = false;
bytesWritten_ = 0;

// Stop the player first (this can block briefly but should complete quickly)
// Stop the player with timeout protection
if (mediaPlayer_) {
OPENAUTO_LOG(debug) << "[QtVideoOutput] Stopping media player";
mediaPlayer_->stop();
mediaPlayer_->setMedia(QMediaContent());
}

// Hide video widget without blocking
// Hide video widget
if (videoWidget_) {
videoWidget_->hide();
videoWidget_->clearFocus();
}
}

void QtVideoOutput::onStopPlayback()
{
OPENAUTO_LOG(info) << "[QtVideoOutput] onStopPlayback()";

std::lock_guard<std::mutex> lock(writeMutex_);
playerReady_ = false;
initialBufferingDone_ = false;
bytesWritten_ = 0;

cleanupPlayer();

OPENAUTO_LOG(info) << "[QtVideoOutput] onStopPlayback() complete";
}
Expand Down
7 changes: 6 additions & 1 deletion src/autoapp/Service/Bluetooth/BluetoothService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ namespace f1x::openauto::autoapp::service::bluetooth {
}

void BluetoothService::onChannelError(const aasdk::error::Error &e) {
OPENAUTO_LOG(error) << "[BluetoothService] onChannelError(): " << e.what();
// OPERATION_ABORTED is expected during shutdown when messenger stops
if (e.getCode() == aasdk::error::ErrorCode::OPERATION_ABORTED) {
OPENAUTO_LOG(debug) << "[BluetoothService] onChannelError(): " << e.what() << " (expected during stop)";
} else {
OPENAUTO_LOG(error) << "[BluetoothService] onChannelError(): " << e.what();
}
}
}

Expand Down
Loading
Loading