Pool Video Switch v2
Software video switch for distributed remote display in a lecture environment
vncwindow.cpp
Go to the documentation of this file.
1 /*
2  # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of
3  # Freiburg
4  #
5  # This program is free software distributed under the GPL version 2.
6  # See http://openslx.org/COPYING
7  #
8  # If you have any feedback please consult http://openslx.org/feedback and
9  # send your suggestions, praise, or complaints to [email protected]
10  #
11  # General information about OpenSLX can be found at http://openslx.org/
12  # -----------------------------------------------------------------------------
13  # clientVNCViewer.cpp
14  # - connetct to vnc server and show remote screen (window/full)
15  # -----------------------------------------------------------------------------
16  */
17 
18 #include "vncwindow.h"
19 #include "vncthread.h"
20 #include "../clientapp/clientapp.h"
21 
22 #include <QGuiApplication>
23 #include <QTimer>
24 #include <QPainter>
25 #include <QScreen>
26 #include <QKeyEvent>
27 
35 static int gcd(int a, int b)
36 {
37  if (b == 0)
38  return a;
39  return gcd(b, a % b);
40 }
41 
43  QWidget(parent), _srcStepX(1), _srcStepY(1), _dstStepX(1), _dstStepY(1),
44  _vncWorker(nullptr), _viewOnly(true), _multiScreen(false), _clientId(0), _redrawTimer(0), _tcpTimeoutTimer(0)
45 {
46  auto *upper = new QTimer(this);
47  connect(upper, &QTimer::timeout, this, &VncWindow::timer_moveToTop);
48  upper->start(1111);
49 }
50 
51 VncWindow::~VncWindow() = default;
52 
54 // Private
55 
64 {
65  if (_vncWorker == nullptr)
66  return;
67 
71  _vncWorker->stop();
72  _vncWorker = nullptr;
73  if (_redrawTimer != 0) {
74  killTimer(_redrawTimer);
75  _redrawTimer = 0;
76  }
77  if (_tcpTimeoutTimer != 0) {
78  killTimer(_tcpTimeoutTimer);
79  _tcpTimeoutTimer = 0;
80  }
81 }
82 
84 {
85  qDebug() << "Deleting thread" << QObject::sender();
86  delete QObject::sender();
87 }
88 
97 void VncWindow::draw(const int x, const int y, const int w, const int h)
98 {
99  if (_vncWorker == nullptr)
100  return;
101  QSharedPointer<QImage> buffer = _vncWorker->getFrameBuffer();
102  if (buffer.isNull())
103  return;
104  this->calcScaling(buffer.data());
105 
106  QPainter painter(this);
107  if (_dstStepX > 1 || _dstStepY > 1) {
108  // Scaling is required as vnc server and client are using different resolutions
109  // Calc section offsets first
110  const int startX = x / _dstStepX;
111  const int startY = y / _dstStepY;
112  const int endX = (x + w - 1) / _dstStepX + 1;
113  const int endY = (y + h - 1) / _dstStepY + 1;
114  // Now pixel offsets for source
115  const int dstX = startX * _dstStepX;
116  const int dstY = startY * _dstStepY;
117  const int dstW = endX * _dstStepX - dstX;
118  const int dstH = endY * _dstStepY - dstY;
119  // Pixel offsets for destination
120  const int srcX = startX * _srcStepX;
121  const int srcY = startY * _srcStepY;
122  const int srcW = endX * _srcStepX - srcX;
123  const int srcH = endY * _srcStepY - srcY;
124  // Rescale
125  QImage scaled(
126  buffer->copy(srcX, srcY, srcW, srcH).scaled(dstW, dstH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
127  painter.drawImage(dstX, dstY, scaled, 0, 0, dstW, dstH);
128  } else {
129  // Same resolution, nothing to do
130  painter.drawImage(x, y, *buffer, x, y, w, h);
131  }
132  //painter.drawRect(x,y,w,h); // for debugging updated area
133 }
134 
147 bool VncWindow::calcScaling(const QImage *remote)
148 {
149  if (this->size() == _desiredSize &&
150  (remote == nullptr || remote->size() == _remoteSize))
151  return false;
152  const QSize mySize = this->size();
153  const QSize remoteSize = remote == nullptr ? _remoteSize : remote->size();
154  if (mySize.isEmpty())
155  return false;
156  if (remoteSize.isEmpty())
157  return false;
158  _remoteSize = remoteSize;
159  _desiredSize = mySize;
160  const int gcdX = gcd(_desiredSize.width(), _remoteSize.width());
161  const int gcdY = gcd(_desiredSize.height(), _remoteSize.height());
162  _srcStepX = _remoteSize.width() / gcdX;
163  _srcStepY = _remoteSize.height() / gcdY;
164  _dstStepX = _desiredSize.width() / gcdX;
165  _dstStepY = _desiredSize.height() / gcdY;
166  qDebug() << "Scaling updated to" << _remoteSize << "->" << _desiredSize;
167  return true;
168 }
169 
171 // Public
172 
186 void VncWindow::open(const QString& host, int port, const QString& passwd, bool /* ro */ , bool fullscreen, const QString& caption, const int clientId, const QByteArray& rawThumb)
187 {
188  this->terminateVncThread();
189  _clientId = clientId;
190  _vncWorker = new VncThread(host, port, passwd, 1);
191  connect(_vncWorker, &VncThread::projectionStopped, this, &VncWindow::onProjectionStopped, Qt::QueuedConnection);
192  connect(_vncWorker, &VncThread::projectionStarted, this, &VncWindow::onProjectionStarted, Qt::QueuedConnection);
193  connect(_vncWorker, &VncThread::imageUpdated, this, &VncWindow::onUpdateImage, Qt::QueuedConnection);
194  connect(_vncWorker, &VncThread::finished, this, &VncWindow::deleteVncThread, Qt::QueuedConnection);
195 
196  setWindowTitle(caption);
197 
198  setAttribute(Qt::WA_OpaquePaintEvent);
199 
200  _remoteThumb.loadFromData(rawThumb);
201 
202  if (fullscreen) {
203  setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); // | Qt::X11BypassWindowManagerHint); <- better, but window won't get any keyboard input
204  // Show projection on rightmost screen
205  QRect best;
206  for (auto *screen : QGuiApplication::screens()) {
207  QRect r = screen->geometry();
208  if (best.isNull() || r.left() > best.left()) {
209  best = r;
210  }
211  }
212  _multiScreen = QGuiApplication::screens().size() > 1;
213  qDebug() << "Spawning VNC viewer at" << best;
214  show();
215  setGeometry(best);
216  raise();
217  if (!_multiScreen) {
218  activateWindow();
219  }
220  move(best.topLeft());
221  } else {
222  setWindowFlags(Qt::Tool | Qt::WindowMaximizeButtonHint);
223  resize(800, 600);
224  showNormal();
225  }
226 
227  this->update();
228 
229  _tcpTimeoutTimer = startTimer(10000);
230  _vncWorker->start(QThread::LowPriority);
231 }
232 
242 void VncWindow::closeEvent(QCloseEvent * /* e */ )
243 {
244  qDebug("Closing VNC viewer window.");
245  this->terminateVncThread();
246  emit running(false, _clientId);
247 }
248 
250 // Slots
251 
264 void VncWindow::onUpdateImage(const int x, const int y, const int w, const int h)
265 {
266  if (_vncWorker == nullptr)
267  return;
268  if (!this->calcScaling(_vncWorker->getFrameBuffer().data()) && !_remoteSize.isEmpty()) {
269  if (_srcStepX > 1 || _srcStepY > 1) {
270  this->update((x * _desiredSize.width()) / _remoteSize.width() - 2,
271  (y * _desiredSize.height()) / _remoteSize.height() - 2,
272  (w * _desiredSize.width()) / _remoteSize.width() + 4,
273  (h * _desiredSize.height()) / _remoteSize.height() + 4);
274  } else {
275  this->update(x, y, w, h);
276  }
277  } else {
278  // Changed, redraw everything
279  this->update();
280  }
281 }
282 
289 {
290  _remoteThumb = QPixmap();
291  if (_tcpTimeoutTimer != 0) {
292  killTimer(_tcpTimeoutTimer);
293  _tcpTimeoutTimer = 0;
294  }
295  emit running(true, _clientId);
296  _redrawTimer = startTimer(500);
297 }
298 
306 {
307  this->terminateVncThread();
308  this->close();
309 }
310 
312 {
313  if (this->isHidden())
314  return;
315  if (!_multiScreen) {
316  activateWindow();
317  }
318  raise();
319 }
320 
322 // Protected
332 void VncWindow::timerEvent(QTimerEvent *event)
333 {
334  if (event->timerId() == _redrawTimer) {
335  killTimer(_redrawTimer);
336  _redrawTimer = 0;
337  this->update();
338  } else if (event->timerId() == _tcpTimeoutTimer) {
339  killTimer(_tcpTimeoutTimer);
340  _tcpTimeoutTimer = 0;
341  if (_vncWorker != nullptr && !_vncWorker->isConnected()) {
342  this->close();
343  }
344  } else
345  killTimer(event->timerId());
346 }
347 
356 void VncWindow::paintEvent(QPaintEvent *event)
357 {
358  if (_vncWorker == nullptr || !_vncWorker->isConnected()) {
359  QPainter painter(this);
360  if (!_remoteThumb.isNull() && _remoteThumb.height() > 0) {
361  painter.drawPixmap(0, 0, this->width(), this->height(), _remoteThumb);
362  } else {
363  painter.fillRect(event->rect(), QColor(60, 63, 66));
364  }
365  QFontInfo fi = painter.fontInfo();
366  painter.setPen(QColor(200, 100, 10));
367  painter.setFont(QFont(fi.family(), 28, fi.weight(), fi.italic()));
368  painter.drawText(this->contentsRect(), Qt::AlignCenter, tr("Connecting..."));
369  } else {
370  const QRect &r = event->rect();
371  this->draw(r.left(), r.top(), r.width(), r.height());
372  }
373  event->accept();
374 }
375 
379 void VncWindow::keyReleaseEvent(QKeyEvent* event)
380 {
381  if (event->modifiers() == 0 && clientApp->isConnectedToLocalManager()) {
382  this->close();
383  } else {
384  QWidget::keyReleaseEvent(event);
385  }
386 }
387 
void draw(const int x, const int y, const int w, const int h)
Draws given part of the current VNC frame buffer to the window.
Definition: vncwindow.cpp:97
void timerEvent(QTimerEvent *event)
Called when a Qt timer fires.
Definition: vncwindow.cpp:332
void projectionStopped()
int _dstStepX
Definition: vncwindow.h:54
bool _multiScreen
Definition: vncwindow.h:57
const QSharedPointer< QImage > & getFrameBuffer()
Definition: vncthread.h:71
virtual ~VncWindow()
void projectionStarted()
void closeEvent(QCloseEvent *e)
Called by Qt if the window is requested to be closed.
Definition: vncwindow.cpp:242
void onProjectionStarted()
Triggered by _vncWorker after successfully connecting to the VNC server.
Definition: vncwindow.cpp:288
VncThread - communicate with VNC server, scale image if necessary.
Definition: vncthread.h:36
int _dstStepY
Definition: vncwindow.h:54
bool calcScaling(const QImage *remote)
When using image scaling, calc matching pixel borders for both resolutions to prevent artifacts throu...
Definition: vncwindow.cpp:147
void terminateVncThread()
Terminates the vnc worker thread and stops all related timers.
Definition: vncwindow.cpp:63
static int gcd(int a, int b)
Calc greatest common divisor.
Definition: vncwindow.cpp:35
int _srcStepY
Definition: vncwindow.h:54
VncThread * _vncWorker
Definition: vncwindow.h:55
int _srcStepX
Definition: vncwindow.h:54
void imageUpdated(const int x, const int y, const int w, const int h)
int _tcpTimeoutTimer
Definition: vncwindow.h:60
int _clientId
Definition: vncwindow.h:58
bool isConnected() const
Definition: vncthread.h:69
void running(const bool isRunning, const int clientId)
void paintEvent(QPaintEvent *event)
Called by Qt when a part of the window should be redrawn.
Definition: vncwindow.cpp:356
QSize _remoteSize
Definition: vncwindow.h:62
void stop()
Definition: vncthread.h:70
void keyReleaseEvent(QKeyEvent *event)
Called when user releases a pressed key and the window has focus.
Definition: vncwindow.cpp:379
void deleteVncThread()
Definition: vncwindow.cpp:83
#define clientApp
Definition: clientapp.h:12
VncWindow(QWidget *parent=0)
Definition: vncwindow.cpp:42
QSize _desiredSize
Definition: vncwindow.h:63
QPixmap _remoteThumb
Definition: vncwindow.h:61
void onUpdateImage(const int x, const int y, const int w, const int h)
Triggered by imageUpdate signal from the _vncWorker thread telling that the given region of the image...
Definition: vncwindow.cpp:264
void onProjectionStopped()
Triggered by _vncWorker when the connection to the VNC server is lost.
Definition: vncwindow.cpp:305
int _redrawTimer
Definition: vncwindow.h:59
void open(const QString &host, int port, const QString &passwd, bool ro, bool fullscreen, const QString &caption, const int clientId, const QByteArray &rawThumb)
Show the VNC client window and connect to the given VNC server.
Definition: vncwindow.cpp:186
void timer_moveToTop()
Definition: vncwindow.cpp:311