blob: bffdfbe2ad6a6fab16435c36d7d15730378b5ee9 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18//
19// VNC server configuration utility
20//
21
22#include <string.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <sys/time.h>
26#include <sys/types.h>
27#include <unistd.h>
28#include <errno.h>
29
30#include <signal.h>
31#include <X11/X.h>
32#include <X11/Xlib.h>
33#include <X11/Xatom.h>
34#include <X11/Xutil.h>
35#include <X11/keysym.h>
36#include "vncExt.h"
37#include <rdr/Exception.h>
38#include <rfb/Configuration.h>
39#include <rfb/Logger_stdio.h>
40#include <rfb/LogWriter.h>
41#include <rfb/Timer.h>
42#include "TXWindow.h"
43#include "TXCheckbox.h"
44#include "TXLabel.h"
45#include "QueryConnectDialog.h"
46
47using namespace rfb;
48
49LogWriter vlog("vncconfig");
50
51StringParameter displayname("display", "The X display", "");
52BoolParameter noWindow("nowin", "Don't display a window", 0);
53BoolParameter iconic("iconic", "Start with window iconified", 0);
54BoolParameter sendPrimary("SendPrimary", "Send the PRIMARY as well as the "
55 "CLIPBOARD selection", true);
56IntParameter pollTime("poll",
57 "How often to poll for clipboard changes in ms", 0);
58
59inline const char* selectionName(Atom sel) {
60 if (sel == xaCLIPBOARD) return "CLIPBOARD";
61 if (sel == XA_PRIMARY) return "PRIMARY";
62 return "unknown";
63}
64
65#define ACCEPT_CUT_TEXT "AcceptCutText"
66#define SEND_CUT_TEXT "SendCutText"
67
68char* programName = 0;
69Display* dpy;
70int vncExtEventBase, vncExtErrorBase;
71
72static bool getBoolParam(Display* dpy, const char* param) {
73 char* data;
74 int len;
75 if (XVncExtGetParam(dpy, param, &data, &len)) {
76 if (strcmp(data,"1") == 0) return true;
77 }
78 return false;
79}
80
81class VncConfigWindow : public TXWindow, public TXEventHandler,
82 public TXDeleteWindowCallback,
83 public TXCheckboxCallback,
84 public rfb::Timer::Callback,
85 public QueryResultCallback {
86public:
87 VncConfigWindow(Display* dpy)
88 : TXWindow(dpy, 300, 100), cutText(0), cutTextLen(0),
89 acceptClipboard(dpy, "Accept clipboard from viewers", this, false, this),
90 sendClipboard(dpy, "Send clipboard to viewers", this, false, this),
91 sendPrimaryCB(dpy, "Send primary selection to viewers", this,false,this),
92 pollTimer(this),
93 queryConnectDialog(0)
94 {
95 selection[0] = selection[1] = 0;
96 selectionLen[0] = selectionLen[1] = 0;
97 int y = yPad;
98 acceptClipboard.move(xPad, y);
99 acceptClipboard.checked(getBoolParam(dpy, ACCEPT_CUT_TEXT));
100 y += acceptClipboard.height();
101 sendClipboard.move(xPad, y);
102 sendClipboard.checked(getBoolParam(dpy, SEND_CUT_TEXT));
103 y += sendClipboard.height();
104 sendPrimaryCB.move(xPad, y);
105 sendPrimaryCB.checked(sendPrimary);
106 sendPrimaryCB.disabled(!sendClipboard.checked());
107 y += sendPrimaryCB.height();
108 setEventHandler(this);
109 toplevel("VNC config", this, 0, 0, 0, iconic);
110 XVncExtSelectInput(dpy, win(),
111 VncExtClientCutTextMask|
112 VncExtSelectionChangeMask|
113 VncExtQueryConnectMask);
114 XConvertSelection(dpy, XA_PRIMARY, XA_STRING,
115 XA_PRIMARY, win(), CurrentTime);
116 XConvertSelection(dpy, xaCLIPBOARD, XA_STRING,
117 xaCLIPBOARD, win(), CurrentTime);
118 if (pollTime != 0)
119 pollTimer.start(pollTime);
120 }
121
122 // handleEvent(). If we get a ClientCutTextNotify event from Xvnc, set the
123 // primary and clipboard selections to the clientCutText. If we get a
124 // SelectionChangeNotify event from Xvnc, set the serverCutText to the value
125 // of the new selection.
126
127 virtual void handleEvent(TXWindow* w, XEvent* ev) {
128 if (acceptClipboard.checked()) {
129 if (ev->type == vncExtEventBase + VncExtClientCutTextNotify) {
130 XVncExtClientCutTextEvent* cutEv = (XVncExtClientCutTextEvent*)ev;
131 if (cutText)
132 XFree(cutText);
133 cutText = 0;
134 if (XVncExtGetClientCutText(dpy, &cutText, &cutTextLen)) {
135 vlog.debug("Got client cut text: '%.*s%s'",
136 cutTextLen<9?cutTextLen:8, cutText,
137 cutTextLen<9?"":"...");
138 XStoreBytes(dpy, cutText, cutTextLen);
139 ownSelection(XA_PRIMARY, cutEv->time);
140 ownSelection(xaCLIPBOARD, cutEv->time);
141 delete [] selection[0];
142 delete [] selection[1];
143 selection[0] = selection[1] = 0;
144 selectionLen[0] = selectionLen[1] = 0;
145 }
146 }
147 }
148 if (sendClipboard.checked()) {
149 if (ev->type == vncExtEventBase + VncExtSelectionChangeNotify) {
150 vlog.debug("selection change event");
151 XVncExtSelectionChangeEvent* selEv = (XVncExtSelectionChangeEvent*)ev;
152 if (selEv->selection == xaCLIPBOARD ||
153 (selEv->selection == XA_PRIMARY && sendPrimaryCB.checked())) {
154 if (!selectionOwner(selEv->selection))
155 XConvertSelection(dpy, selEv->selection, XA_STRING,
156 selEv->selection, win(), CurrentTime);
157 }
158 }
159 }
160 if (ev->type == vncExtEventBase + VncExtQueryConnectNotify) {
161 vlog.debug("query connection event");
162 if (queryConnectDialog)
163 delete queryConnectDialog;
164 queryConnectDialog = 0;
165 char* qcAddress;
166 char* qcUser;
167 int qcTimeout;
168 if (XVncExtGetQueryConnect(dpy, &qcAddress, &qcUser,
169 &qcTimeout, &queryConnectId)) {
170 if (qcTimeout)
171 queryConnectDialog = new QueryConnectDialog(dpy, qcAddress,
172 qcUser, qcTimeout,
173 this);
174 if (queryConnectDialog)
175 queryConnectDialog->map();
176 XFree(qcAddress);
177 XFree(qcUser);
178 }
179 }
180 }
181
182
183 // selectionRequest() is called when we are the selection owner and another X
184 // client has requested the selection. We simply put the server's cut text
185 // into the requested property. TXWindow will handle the rest.
186 bool selectionRequest(Window requestor, Atom selection, Atom property)
187 {
188 if (cutText)
189 XChangeProperty(dpy, requestor, property, XA_STRING, 8,
190 PropModeReplace, (unsigned char*)cutText,
191 cutTextLen);
192 return cutText;
193 }
194
195 // selectionNotify() is called when we have requested the selection from the
196 // selection owner.
197 void selectionNotify(XSelectionEvent* ev, Atom type, int format,
198 int nitems, void* data)
199 {
200 if (ev->requestor != win() || ev->target != XA_STRING)
201 return;
202
203 if (data && format == 8) {
204 int i = (ev->selection == XA_PRIMARY ? 0 : 1);
205 if (selectionLen[i] == nitems && memcmp(selection[i], data, nitems) == 0)
206 return;
207 delete [] selection[i];
208 selection[i] = new char[nitems];
209 memcpy(selection[i], data, nitems);
210 selectionLen[i] = nitems;
211 if (cutTextLen == nitems && memcmp(cutText, data, nitems) == 0) {
212 vlog.debug("ignoring duplicate cut text");
213 return;
214 }
215 if (cutText)
216 XFree(cutText);
217 cutText = (char*)malloc(nitems); // assuming XFree() same as free()
Pierre Ossmane83b14a2014-10-10 13:32:31 +0200218 if (!cutText) {
219 vlog.error("unable to allocate selection buffer");
220 return;
221 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000222 memcpy(cutText, data, nitems);
223 cutTextLen = nitems;
224 vlog.debug("sending %s selection as server cut text: '%.*s%s'",
225 selectionName(ev->selection),cutTextLen<9?cutTextLen:8,
226 cutText, cutTextLen<9?"":"...");
227 XVncExtSetServerCutText(dpy, cutText, cutTextLen);
228 }
229 }
230
231 // TXDeleteWindowCallback method
232 virtual void deleteWindow(TXWindow* w) {
233 exit(1);
234 }
235
236 // TXCheckboxCallback method
237 virtual void checkboxSelect(TXCheckbox* checkbox) {
238 if (checkbox == &acceptClipboard) {
239 XVncExtSetParam(dpy, (acceptClipboard.checked()
240 ? ACCEPT_CUT_TEXT "=1" : ACCEPT_CUT_TEXT "=0"));
241 } else if (checkbox == &sendClipboard) {
242 XVncExtSetParam(dpy, (sendClipboard.checked()
243 ? SEND_CUT_TEXT "=1" : SEND_CUT_TEXT "=0"));
244 sendPrimaryCB.disabled(!sendClipboard.checked());
245 }
246 }
247
248 // rfb::Timer::Callback interface
249 virtual bool handleTimeout(rfb::Timer* timer) {
250 if (sendPrimaryCB.checked() && !selectionOwner(XA_PRIMARY))
251 XConvertSelection(dpy, XA_PRIMARY, XA_STRING,
252 XA_PRIMARY, win(), CurrentTime);
253 if (!selectionOwner(xaCLIPBOARD))
254 XConvertSelection(dpy, xaCLIPBOARD, XA_STRING,
255 xaCLIPBOARD, win(), CurrentTime);
256 return true;
257 }
258
259 // QueryResultCallback interface
260 virtual void queryApproved() {
261 XVncExtApproveConnect(dpy, queryConnectId, 1);
262 }
263 virtual void queryRejected() {
264 XVncExtApproveConnect(dpy, queryConnectId, 0);
265 }
266
267private:
268 char* cutText;
269 int cutTextLen;
270 char* selection[2];
271 int selectionLen[2];
272 TXCheckbox acceptClipboard, sendClipboard, sendPrimaryCB;
273 rfb::Timer pollTimer;
274
275 QueryConnectDialog* queryConnectDialog;
276 void* queryConnectId;
277};
278
279static void usage()
280{
281 fprintf(stderr,"usage: %s [parameters]\n",
282 programName);
283 fprintf(stderr," %s [parameters] -connect <host>[:<port>]\n",
284 programName);
285 fprintf(stderr," %s [parameters] -disconnect\n", programName);
286 fprintf(stderr," %s [parameters] [-set] <Xvnc-param>=<value> ...\n",
287 programName);
288 fprintf(stderr," %s [parameters] -list\n", programName);
289 fprintf(stderr," %s [parameters] -get <param>\n", programName);
290 fprintf(stderr," %s [parameters] -desc <param>\n",programName);
291 fprintf(stderr,"\n"
292 "Parameters can be turned on with -<param> or off with -<param>=0\n"
293 "Parameters which take a value can be specified as "
294 "-<param> <value>\n"
295 "Other valid forms are <param>=<value> -<param>=<value> "
296 "--<param>=<value>\n"
297 "Parameter names are case-insensitive. The parameters are:\n\n");
298 Configuration::listParams(79, 14);
299 exit(1);
300}
301
302void removeArgs(int* argc, char** argv, int first, int n)
303{
304 if (first + n > *argc) return;
305 for (int i = first + n; i < *argc; i++)
306 argv[i-n] = argv[i];
307 *argc -= n;
308}
309
310int main(int argc, char** argv)
311{
312 programName = argv[0];
313 rfb::initStdIOLoggers();
314 rfb::LogWriter::setLogParams("*:stderr:30");
315
316 // Process vncconfig's own parameters first, then we process the
317 // other arguments when we have the X display.
318 int i;
319 for (i = 1; i < argc; i++) {
320 if (Configuration::setParam(argv[i]))
321 continue;
322
323 if (argv[i][0] == '-' && i+1 < argc &&
324 Configuration::setParam(&argv[i][1], argv[i+1])) {
325 i++;
326 continue;
327 }
328 break;
329 }
330
331 CharArray displaynameStr(displayname.getData());
332 if (!(dpy = XOpenDisplay(displaynameStr.buf))) {
333 fprintf(stderr,"%s: unable to open display \"%s\"\n",
334 programName, XDisplayName(displaynameStr.buf));
335 exit(1);
336 }
337
338 if (!XVncExtQueryExtension(dpy, &vncExtEventBase, &vncExtErrorBase)) {
339 fprintf(stderr,"No VNC extension on display %s\n",
340 XDisplayName(displaynameStr.buf));
341 exit(1);
342 }
343
344 if (i < argc) {
345 for (; i < argc; i++) {
346 if (strcmp(argv[i], "-connect") == 0) {
347 i++;
348 if (i >= argc) usage();
349 if (!XVncExtConnect(dpy, argv[i])) {
350 fprintf(stderr,"connecting to %s failed\n",argv[i]);
351 }
352 } else if (strcmp(argv[i], "-disconnect") == 0) {
353 if (!XVncExtConnect(dpy, "")) {
354 fprintf(stderr,"disconnecting all clients failed\n");
355 }
356 } else if (strcmp(argv[i], "-get") == 0) {
357 i++;
358 if (i >= argc) usage();
359 char* data;
360 int len;
361 if (XVncExtGetParam(dpy, argv[i], &data, &len)) {
362 printf("%.*s\n",len,data);
363 } else {
364 fprintf(stderr,"getting param %s failed\n",argv[i]);
365 }
366 XFree(data);
367 } else if (strcmp(argv[i], "-desc") == 0) {
368 i++;
369 if (i >= argc) usage();
370 char* desc = XVncExtGetParamDesc(dpy, argv[i]);
371 if (desc) {
372 printf("%s\n",desc);
373 } else {
374 fprintf(stderr,"getting description for param %s failed\n",argv[i]);
375 }
376 XFree(desc);
377 } else if (strcmp(argv[i], "-list") == 0) {
378 int nParams;
379 char** list = XVncExtListParams(dpy, &nParams);
380 for (int i = 0; i < nParams; i++) {
381 printf("%s\n",list[i]);
382 }
383 XVncExtFreeParamList(list);
384 } else if (strcmp(argv[i], "-set") == 0) {
385 i++;
386 if (i >= argc) usage();
387 if (!XVncExtSetParam(dpy, argv[i])) {
388 fprintf(stderr,"setting param %s failed\n",argv[i]);
389 }
390 } else if (XVncExtSetParam(dpy, argv[i])) {
391 fprintf(stderr,"set parameter %s\n",argv[i]);
392 } else {
393 usage();
394 }
395 }
396
397 return 0;
398 }
399
400 try {
401 TXWindow::init(dpy,"Vncconfig");
402
403 VncConfigWindow w(dpy);
404 if (!noWindow) w.map();
405
406 while (true) {
407 struct timeval tv;
408 struct timeval* tvp = 0;
409
410 // Process any incoming X events
411 TXWindow::handleXEvents(dpy);
412
413 // Process expired timers and get the time until the next one
414 int timeoutMs = Timer::checkTimeouts();
415 if (timeoutMs) {
416 tv.tv_sec = timeoutMs / 1000;
417 tv.tv_usec = (timeoutMs % 1000) * 1000;
418 tvp = &tv;
419 }
420
421 // If there are X requests pending then poll, don't wait!
422 if (XPending(dpy)) {
423 tv.tv_usec = tv.tv_sec = 0;
424 tvp = &tv;
425 }
426
427 // Wait for X events, VNC traffic, or the next timer expiry
428 fd_set rfds;
429 FD_ZERO(&rfds);
430 FD_SET(ConnectionNumber(dpy), &rfds);
431 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
432 if (n < 0) throw rdr::SystemException("select",errno);
433 }
434
435 XCloseDisplay(dpy);
436
437 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000438 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000439 }
440
441 return 0;
442}