diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index d61369109d66..5693b9256a08 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -28,6 +28,7 @@ MainWindow::MainWindow(const OptionsParser::Options &options)
 
 	title_ = "QCam " + QString::fromStdString(libcamera::version);
 	setWindowTitle(title_);
+	connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle()));
 
 	viewfinder_ = new ViewFinder(this);
 	setCentralWidget(viewfinder_);
@@ -54,6 +55,19 @@ MainWindow::~MainWindow()
 	CameraManager::instance()->stop();
 }
 
+void MainWindow::updateTitle()
+{
+	unsigned int duration = frameRateInterval_.elapsed();
+	unsigned int frames = framesCaptured_ - previousFrames_;
+	double fps = frames * 1000.0 / duration;
+
+	/* Restart counters */
+	frameRateInterval_.start();
+	previousFrames_ = framesCaptured_;
+
+	setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
+}
+
 int MainWindow::openCamera()
 {
 	CameraManager *cm = CameraManager::instance();
@@ -148,6 +162,10 @@ int MainWindow::startCapture()
 		requests.push_back(request);
 	}
 
+	titleTimer_.start(2000);
+	frameRateInterval_.start();
+	previousFrames_ = 0;
+	framesCaptured_ = 0;
 	lastBufferTime_ = 0;
 
 	ret = camera_->start();
@@ -188,6 +206,9 @@ void MainWindow::stopCapture()
 	isCapturing_ = false;
 
 	config_.reset();
+
+	titleTimer_.stop();
+	setWindowTitle(title_);
 }
 
 void MainWindow::requestComplete(Request *request,
@@ -196,6 +217,8 @@ void MainWindow::requestComplete(Request *request,
 	if (request->status() == Request::RequestCancelled)
 		return;
 
+	framesCaptured_++;
+
 	Buffer *buffer = buffers.begin()->second;
 
 	double fps = buffer->timestamp() - lastBufferTime_;
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index 46a494a9d783..e97d92402f1e 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -10,7 +10,11 @@
 #include <map>
 #include <memory>
 
+
+#include <QElapsedTimer>
 #include <QMainWindow>
+#include <QObject>
+#include <QTimer>
 
 #include <libcamera/camera.h>
 #include <libcamera/stream.h>
@@ -28,10 +32,15 @@ enum {
 
 class MainWindow : public QMainWindow
 {
+	Q_OBJECT
+
 public:
 	MainWindow(const OptionsParser::Options &options);
 	~MainWindow();
 
+public Q_SLOTS:
+	void updateTitle();
+
 private:
 	int openCamera();
 
@@ -43,6 +52,8 @@ private:
 	int display(Buffer *buffer);
 
 	QString title_;
+	QTimer titleTimer_;
+
 	const OptionsParser::Options &options_;
 
 	std::shared_ptr<Camera> camera_;
@@ -51,6 +62,10 @@ private:
 
 	uint64_t lastBufferTime_;
 
+	QElapsedTimer frameRateInterval_;
+	uint32_t previousFrames_;
+	uint32_t framesCaptured_;
+
 	ViewFinder *viewfinder_;
 };
 
diff --git a/src/qcam/meson.build b/src/qcam/meson.build
index 9f1fa75f9813..21f91f25cec0 100644
--- a/src/qcam/meson.build
+++ b/src/qcam/meson.build
@@ -7,14 +7,21 @@ qcam_sources = files([
     'viewfinder.cpp',
 ])
 
-import('qt5')
+qcam_moc_headers = files([
+    'main_window.h',
+])
+
+qt5 = import('qt5')
 qt5_dep = dependency('qt5',
                      method : 'pkg-config',
                      modules : ['Core', 'Gui', 'Widgets'],
                      required : false)
 
 if qt5_dep.found()
-    qcam  = executable('qcam', qcam_sources,
+    moc_files = qt5.preprocess(moc_headers: qcam_moc_headers,
+                               dependencies: qt5_dep)
+
+    qcam  = executable('qcam', qcam_sources, moc_files,
                        install : true,
                        dependencies : [libcamera_dep, qt5_dep],
                        cpp_args : '-DQT_NO_KEYWORDS')
