1 #include "ikvm_server.hpp"
2
3 #include <linux/videodev2.h>
4 #include <rfb/rfbproto.h>
5
6 #include <boost/crc.hpp>
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <phosphor-logging/elog.hpp>
9 #include <phosphor-logging/log.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11
12 namespace ikvm
13 {
14
15 using namespace phosphor::logging;
16 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
17
Server(const Args & args,Input & i,Video & v)18 Server::Server(const Args& args, Input& i, Video& v) :
19 pendingResize(false), frameCounter(0), numClients(0), input(i), video(v)
20 {
21 std::string ip("localhost");
22 const Args::CommandLine& commandLine = args.getCommandLine();
23 int argc = commandLine.argc;
24
25 server = rfbGetScreen(&argc, commandLine.argv, video.getWidth(),
26 video.getHeight(), Video::bitsPerSample,
27 Video::samplesPerPixel, Video::bytesPerPixel);
28
29 if (!server)
30 {
31 log<level::ERR>("Failed to get VNC screen due to invalid arguments");
32 elog<InvalidArgument>(
33 xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_NAME(""),
34 xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_VALUE(""));
35 }
36
37 framebuffer.resize(
38 video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
39
40 server->screenData = this;
41 server->desktopName = "OpenBMC IKVM";
42 server->frameBuffer = framebuffer.data();
43 server->newClientHook = newClient;
44 server->cursor = rfbMakeXCursor(cursorWidth, cursorHeight, (char*)cursor,
45 (char*)cursorMask);
46 server->cursor->xhot = 1;
47 server->cursor->yhot = 1;
48
49 rfbStringToAddr(&ip[0], &server->listenInterface);
50
51 rfbInitServer(server);
52
53 rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
54
55 server->kbdAddEvent = Input::keyEvent;
56 server->ptrAddEvent = Input::pointerEvent;
57
58 processTime = (1000000 / video.getFrameRate()) - 100;
59
60 calcFrameCRC = args.getCalcFrameCRC();
61 }
62
~Server()63 Server::~Server()
64 {
65 rfbScreenCleanup(server);
66 }
67
resize()68 void Server::resize()
69 {
70 if (frameCounter > video.getFrameRate())
71 {
72 doResize();
73 }
74 else
75 {
76 pendingResize = true;
77 }
78 }
79
run()80 void Server::run()
81 {
82 rfbProcessEvents(server, processTime);
83
84 if (server->clientHead)
85 {
86 frameCounter++;
87 if (pendingResize && frameCounter > video.getFrameRate())
88 {
89 doResize();
90 pendingResize = false;
91 }
92 }
93 }
94
sendFrame()95 void Server::sendFrame()
96 {
97 char* data = video.getData();
98 rfbClientIteratorPtr it;
99 rfbClientPtr cl;
100 int64_t frame_crc = -1;
101
102 if (!data || pendingResize)
103 {
104 return;
105 }
106
107 it = rfbGetClientIterator(server);
108
109 while ((cl = rfbClientIteratorNext(it)))
110 {
111 ClientData* cd = (ClientData*)cl->clientData;
112 rfbFramebufferUpdateMsg* fu = (rfbFramebufferUpdateMsg*)cl->updateBuf;
113
114 if (!cd)
115 {
116 continue;
117 }
118
119 if (cd->skipFrame)
120 {
121 cd->skipFrame--;
122 continue;
123 }
124
125 if (!cd->needUpdate)
126 {
127 continue;
128 }
129
130 if (calcFrameCRC)
131 {
132 if (frame_crc == -1)
133 {
134 /* JFIF header contains some varying data so skip it for
135 * checksum calculation */
136 frame_crc =
137 boost::crc<32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, true,
138 true>(data + 0x30, video.getFrameSize() - 0x30);
139 }
140
141 if (cd->last_crc == frame_crc)
142 {
143 continue;
144 }
145
146 cd->last_crc = frame_crc;
147 }
148
149 cd->needUpdate = false;
150
151 if (cl->enableLastRectEncoding)
152 {
153 fu->nRects = 0xFFFF;
154 }
155 else
156 {
157 fu->nRects = Swap16IfLE(1);
158 }
159
160 switch (video.getPixelformat())
161 {
162 case V4L2_PIX_FMT_RGB24:
163 framebuffer.assign(data, data + video.getFrameSize());
164 rfbMarkRectAsModified(server, 0, 0, video.getWidth(),
165 video.getHeight());
166 break;
167
168 case V4L2_PIX_FMT_JPEG:
169 fu->type = rfbFramebufferUpdate;
170 cl->ublen = sz_rfbFramebufferUpdateMsg;
171 rfbSendUpdateBuf(cl);
172 cl->tightEncoding = rfbEncodingTight;
173 rfbSendTightHeader(cl, 0, 0, video.getWidth(),
174 video.getHeight());
175 cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4);
176 rfbSendCompressedDataTight(cl, data, video.getFrameSize());
177 if (cl->enableLastRectEncoding)
178 {
179 rfbSendLastRectMarker(cl);
180 }
181 rfbSendUpdateBuf(cl);
182 break;
183
184 default:
185 break;
186 }
187 }
188
189 rfbReleaseClientIterator(it);
190 }
191
clientFramebufferUpdateRequest(rfbClientPtr cl,rfbFramebufferUpdateRequestMsg * furMsg)192 void Server::clientFramebufferUpdateRequest(
193 rfbClientPtr cl, rfbFramebufferUpdateRequestMsg* furMsg)
194 {
195 ClientData* cd = (ClientData*)cl->clientData;
196
197 if (!cd)
198 return;
199
200 // Ignore the furMsg info. This service uses full frame update always.
201 (void)furMsg;
202
203 cd->needUpdate = true;
204 }
205
clientGone(rfbClientPtr cl)206 void Server::clientGone(rfbClientPtr cl)
207 {
208 Server* server = (Server*)cl->screen->screenData;
209
210 delete (ClientData*)cl->clientData;
211 cl->clientData = nullptr;
212
213 if (server->numClients-- == 1)
214 {
215 server->input.disconnect();
216 rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
217 server->video.getHeight());
218 }
219 }
220
newClient(rfbClientPtr cl)221 enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
222 {
223 Server* server = (Server*)cl->screen->screenData;
224
225 cl->clientData =
226 new ClientData(server->video.getFrameRate(), &server->input);
227 cl->clientGoneHook = clientGone;
228 cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;
229 if (!server->numClients++)
230 {
231 server->input.connect();
232 server->pendingResize = false;
233 server->frameCounter = 0;
234 }
235
236 return RFB_CLIENT_ACCEPT;
237 }
238
doResize()239 void Server::doResize()
240 {
241 rfbClientIteratorPtr it;
242 rfbClientPtr cl;
243
244 framebuffer.resize(
245 video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
246
247 rfbNewFramebuffer(server, framebuffer.data(), video.getWidth(),
248 video.getHeight(), Video::bitsPerSample,
249 Video::samplesPerPixel, Video::bytesPerPixel);
250 rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
251
252 it = rfbGetClientIterator(server);
253
254 while ((cl = rfbClientIteratorNext(it)))
255 {
256 ClientData* cd = (ClientData*)cl->clientData;
257
258 if (!cd)
259 {
260 continue;
261 }
262
263 // delay video updates to give the client time to resize
264 cd->skipFrame = video.getFrameRate();
265 }
266
267 rfbReleaseClientIterator(it);
268 }
269
270 } // namespace ikvm
271