Add RFB server class implementation
Change-Id: I3532b5ecdef87cb8f64327de6bd22a175b68f865
Signed-off-by: Eddie James <eajames@linux.ibm.com>
diff --git a/ikvm_server.cpp b/ikvm_server.cpp
index 679f47c..98d45ef 100644
--- a/ikvm_server.cpp
+++ b/ikvm_server.cpp
@@ -1,15 +1,209 @@
#include "ikvm_server.hpp"
+#include <rfb/rfbproto.h>
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
namespace ikvm
{
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
Server::Server(const Args& args, Input& i, Video& v) :
- input(i), video(v)
+ pendingResize(false), frameCounter(0), numClients(0), input(i), video(v)
{
+ std::string ip("localhost");
+ const Args::CommandLine& commandLine = args.getCommandLine();
+ int argc = commandLine.argc;
+
+ server = rfbGetScreen(&argc, commandLine.argv, video.getWidth(),
+ video.getHeight(), Video::bitsPerSample,
+ Video::samplesPerPixel, Video::bytesPerPixel);
+
+ if (!server)
+ {
+ log<level::ERR>("Failed to get VNC screen due to invalid arguments");
+ elog<InvalidArgument>(
+ xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_NAME(""),
+ xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_VALUE(""));
+ }
+
+ framebuffer.resize(
+ video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
+
+ server->screenData = this;
+ server->desktopName = "OpenBMC IKVM";
+ server->frameBuffer = framebuffer.data();
+ server->newClientHook = newClient;
+
+ rfbStringToAddr(&ip[0], &server->listenInterface);
+
+ rfbInitServer(server);
+
+ rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
+
+ server->kbdAddEvent = Input::keyEvent;
+ server->ptrAddEvent = Input::pointerEvent;
+
+ processTime = (1000000 / video.getFrameRate()) - 100;
}
Server::~Server()
{
+ rfbScreenCleanup(server);
+}
+
+void Server::resize()
+{
+ if (frameCounter > video.getFrameRate())
+ {
+ doResize();
+ }
+ else
+ {
+ pendingResize = true;
+ }
+}
+
+void Server::run()
+{
+ rfbProcessEvents(server, processTime);
+
+ if (server->clientHead)
+ {
+ input.sendReport();
+
+ frameCounter++;
+ if (pendingResize && frameCounter > video.getFrameRate())
+ {
+ doResize();
+ pendingResize = false;
+ }
+ }
+}
+
+void Server::sendFrame()
+{
+ char* data = video.getData();
+ rfbClientIteratorPtr it;
+ rfbClientPtr cl;
+
+ if (!data || pendingResize)
+ {
+ return;
+ }
+
+ it = rfbGetClientIterator(server);
+
+ while ((cl = rfbClientIteratorNext(it)))
+ {
+ ClientData* cd = (ClientData*)cl->clientData;
+ rfbFramebufferUpdateMsg* fu = (rfbFramebufferUpdateMsg*)cl->updateBuf;
+
+ if (!cd)
+ {
+ continue;
+ }
+
+ if (cd->skipFrame)
+ {
+ cd->skipFrame--;
+ continue;
+ }
+
+ if (cl->enableLastRectEncoding)
+ {
+ fu->nRects = 0xFFFF;
+ }
+ else
+ {
+ fu->nRects = Swap16IfLE(1);
+ }
+
+ fu->type = rfbFramebufferUpdate;
+ cl->ublen = sz_rfbFramebufferUpdateMsg;
+ rfbSendUpdateBuf(cl);
+
+ cl->tightEncoding = rfbEncodingTight;
+ rfbSendTightHeader(cl, 0, 0, video.getWidth(), video.getHeight());
+
+ cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4);
+ rfbSendCompressedDataTight(cl, data, video.getFrameSize());
+
+ if (cl->enableLastRectEncoding)
+ {
+ rfbSendLastRectMarker(cl);
+ }
+
+ rfbSendUpdateBuf(cl);
+ }
+
+ rfbReleaseClientIterator(it);
+}
+
+void Server::clientGone(rfbClientPtr cl)
+{
+ Server* server = (Server*)cl->screen->screenData;
+
+ delete (ClientData*)cl->clientData;
+
+ if (server->numClients-- == 1)
+ {
+ rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
+ server->video.getHeight());
+ }
+}
+
+enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
+{
+ Server* server = (Server*)cl->screen->screenData;
+
+ cl->clientData =
+ new ClientData(server->video.getFrameRate(), &server->input);
+ cl->clientGoneHook = clientGone;
+ if (!server->numClients++)
+ {
+ server->pendingResize = false;
+ server->frameCounter = 0;
+ server->video.start();
+ }
+
+ return RFB_CLIENT_ACCEPT;
+}
+
+void Server::doResize()
+{
+ rfbClientIteratorPtr it;
+ rfbClientPtr cl;
+
+ framebuffer.resize(
+ video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
+
+ rfbNewFramebuffer(server, framebuffer.data(), video.getWidth(),
+ video.getHeight(), Video::bitsPerSample,
+ Video::samplesPerPixel, Video::bytesPerPixel);
+ rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
+
+ it = rfbGetClientIterator(server);
+
+ while ((cl = rfbClientIteratorNext(it)))
+ {
+ ClientData* cd = (ClientData*)cl->clientData;
+
+ if (!cd)
+ {
+ continue;
+ }
+
+ // delay video updates to give the client time to resize
+ cd->skipFrame = video.getFrameRate();
+ }
+
+ rfbReleaseClientIterator(it);
}
} // namespace ikvm
diff --git a/ikvm_server.hpp b/ikvm_server.hpp
index 67602b0..ff51cfc 100644
--- a/ikvm_server.hpp
+++ b/ikvm_server.hpp
@@ -4,6 +4,10 @@
#include "ikvm_input.hpp"
#include "ikvm_video.hpp"
+#include <rfb/rfb.h>
+
+#include <vector>
+
namespace ikvm
{
@@ -53,6 +57,22 @@
Server(Server&&) = default;
Server& operator=(Server&&) = default;
+ /* @brief Resizes the RFB framebuffer */
+ void resize();
+ /* @brief Executes any pending RFB updates and client input */
+ void run();
+ /* @brief Sends pending video frame to clients */
+ void sendFrame();
+
+ /*
+ * @brief Indicates whether or not video data is desired
+ *
+ * @return Boolean to indicate whether any clients need a video frame
+ */
+ inline bool wantsFrame() const
+ {
+ return server->clientHead;
+ }
/*
* @brief Get the Video object
*
@@ -64,10 +84,38 @@
}
private:
+ /*
+ * @brief Handler for a client disconnecting
+ *
+ * @param[in] cl - Handle to the client object
+ */
+ static void clientGone(rfbClientPtr cl);
+ /*
+ * @brief Handler for client connecting
+ *
+ * @param[in] cl - Handle to the client object
+ */
+ static enum rfbNewClientAction newClient(rfbClientPtr cl);
+
+ /* @brief Performs the resize operation on the framebuffer */
+ void doResize();
+
+ /* @brief Boolean to indicate if a resize operation is on-going */
+ bool pendingResize;
+ /* @brief Number of frames handled since a client connected */
+ int frameCounter;
+ /* @brief Number of connected clients */
+ int numClients;
+ /* @brief Microseconds to process RFB events every frame */
+ long int processTime;
+ /* @brief Handle to the RFB server object */
+ rfbScreenInfoPtr server;
/* @brief Reference to the Input object */
Input& input;
/* @brief Reference to the Video object */
Video& video;
+ /* @brief Default framebuffer storage */
+ std::vector<char> framebuffer;
};
} // namespace ikvm