Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

DesktopWindow.cxx 34KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
  3. *
  4. * This is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This software is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this software; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  17. * USA.
  18. */
  19. #ifdef HAVE_CONFIG_H
  20. #include <config.h>
  21. #endif
  22. #include <assert.h>
  23. #include <stdio.h>
  24. #include <string.h>
  25. #include <sys/time.h>
  26. #include <rfb/LogWriter.h>
  27. #include <rfb/CMsgWriter.h>
  28. #include "DesktopWindow.h"
  29. #include "OptionsDialog.h"
  30. #include "i18n.h"
  31. #include "parameters.h"
  32. #include "vncviewer.h"
  33. #include "CConn.h"
  34. #include "Surface.h"
  35. #include "Viewport.h"
  36. #include <FL/Fl.H>
  37. #include <FL/Fl_Image_Surface.H>
  38. #include <FL/Fl_Scrollbar.H>
  39. #include <FL/fl_draw.H>
  40. #include <FL/x.H>
  41. #ifdef WIN32
  42. #include "win32.h"
  43. #endif
  44. #ifdef __APPLE__
  45. #include "cocoa.h"
  46. #endif
  47. #define EDGE_SCROLL_SIZE 32
  48. #define EDGE_SCROLL_SPEED 20
  49. using namespace rfb;
  50. static rfb::LogWriter vlog("DesktopWindow");
  51. DesktopWindow::DesktopWindow(int w, int h, const char *name,
  52. const rfb::PixelFormat& serverPF,
  53. CConn* cc_)
  54. : Fl_Window(w, h), cc(cc_), offscreen(NULL), overlay(NULL),
  55. firstUpdate(true),
  56. delayedFullscreen(false), delayedDesktopSize(false),
  57. statsLastFrame(0), statsLastPixels(0), statsLastPosition(0),
  58. statsGraph(NULL)
  59. {
  60. Fl_Group* group;
  61. // Dummy group to prevent FLTK from moving our widgets around
  62. group = new Fl_Group(0, 0, w, h);
  63. group->resizable(NULL);
  64. resizable(group);
  65. viewport = new Viewport(w, h, serverPF, cc);
  66. // Position will be adjusted later
  67. hscroll = new Fl_Scrollbar(0, 0, 0, 0);
  68. vscroll = new Fl_Scrollbar(0, 0, 0, 0);
  69. hscroll->type(FL_HORIZONTAL);
  70. hscroll->callback(handleScroll, this);
  71. vscroll->callback(handleScroll, this);
  72. group->end();
  73. callback(handleClose, this);
  74. setName(name);
  75. OptionsDialog::addCallback(handleOptions, this);
  76. // Hack. See below...
  77. Fl::event_dispatch(&fltkHandle);
  78. // Support for -geometry option. Note that although we do support
  79. // negative coordinates, we do not support -XOFF-YOFF (ie
  80. // coordinates relative to the right edge / bottom edge) at this
  81. // time.
  82. int geom_x = 0, geom_y = 0;
  83. if (strcmp(geometry, "") != 0) {
  84. int matched;
  85. matched = sscanf(geometry.getValueStr(), "+%d+%d", &geom_x, &geom_y);
  86. if (matched == 2) {
  87. force_position(1);
  88. } else {
  89. int geom_w, geom_h;
  90. matched = sscanf(geometry.getValueStr(), "%dx%d+%d+%d", &geom_w, &geom_h, &geom_x, &geom_y);
  91. switch (matched) {
  92. case 4:
  93. force_position(1);
  94. /* fall through */
  95. case 2:
  96. w = geom_w;
  97. h = geom_h;
  98. break;
  99. default:
  100. geom_x = geom_y = 0;
  101. vlog.error(_("Invalid geometry specified!"));
  102. }
  103. }
  104. }
  105. #ifdef __APPLE__
  106. // On OS X we can do the maximize thing properly before the
  107. // window is showned. Other platforms handled further down...
  108. if (maximize) {
  109. int dummy;
  110. Fl::screen_work_area(dummy, dummy, w, h, geom_x, geom_y);
  111. }
  112. #endif
  113. if (force_position()) {
  114. resize(geom_x, geom_y, w, h);
  115. } else {
  116. size(w, h);
  117. }
  118. if (fullScreen) {
  119. // Hack: Window managers seem to be rather crappy at respecting
  120. // fullscreen hints on initial windows. So on X11 we'll have to
  121. // wait until after we've been mapped.
  122. #if defined(WIN32) || defined(__APPLE__)
  123. fullscreen_on();
  124. #else
  125. delayedFullscreen = true;
  126. #endif
  127. }
  128. show();
  129. // Full screen events are not sent out for a hidden window,
  130. // so send a fake one here to set up things properly.
  131. if (fullscreen_active())
  132. handle(FL_FULLSCREEN);
  133. // Unfortunately, current FLTK does not allow us to set the
  134. // maximized property on Windows and X11 before showing the window.
  135. // See STR #2083 and STR #2178
  136. #ifndef __APPLE__
  137. if (maximize) {
  138. maximizeWindow();
  139. }
  140. #endif
  141. // Adjust layout now that we're visible and know our final size
  142. repositionWidgets();
  143. if (delayedFullscreen) {
  144. // Hack: Fullscreen requests may be ignored, so we need a timeout for
  145. // when we should stop waiting. We also really need to wait for the
  146. // resize, which can come after the fullscreen event.
  147. Fl::add_timeout(0.5, handleFullscreenTimeout, this);
  148. fullscreen_on();
  149. }
  150. // Throughput graph for debugging
  151. if (vlog.getLevel() >= LogWriter::LEVEL_DEBUG) {
  152. memset(&stats, 0, sizeof(stats));
  153. Fl::add_timeout(0, handleStatsTimeout, this);
  154. }
  155. // Show hint about menu key
  156. Fl::add_timeout(0.5, menuOverlay, this);
  157. }
  158. DesktopWindow::~DesktopWindow()
  159. {
  160. // Unregister all timeouts in case they get a change tro trigger
  161. // again later when this object is already gone.
  162. Fl::remove_timeout(handleGrab, this);
  163. Fl::remove_timeout(handleResizeTimeout, this);
  164. Fl::remove_timeout(handleFullscreenTimeout, this);
  165. Fl::remove_timeout(handleEdgeScroll, this);
  166. Fl::remove_timeout(handleStatsTimeout, this);
  167. Fl::remove_timeout(menuOverlay, this);
  168. Fl::remove_timeout(updateOverlay, this);
  169. OptionsDialog::removeCallback(handleOptions);
  170. delete overlay;
  171. delete offscreen;
  172. delete statsGraph;
  173. // FLTK automatically deletes all child widgets, so we shouldn't touch
  174. // them ourselves here
  175. }
  176. const rfb::PixelFormat &DesktopWindow::getPreferredPF()
  177. {
  178. return viewport->getPreferredPF();
  179. }
  180. void DesktopWindow::setName(const char *name)
  181. {
  182. CharArray windowNameStr;
  183. windowNameStr.replaceBuf(new char[256]);
  184. snprintf(windowNameStr.buf, 256, "%.240s - TigerVNC", name);
  185. copy_label(windowNameStr.buf);
  186. }
  187. // Copy the areas of the framebuffer that have been changed (damaged)
  188. // to the displayed window.
  189. void DesktopWindow::updateWindow()
  190. {
  191. if (firstUpdate) {
  192. if (cc->cp.supportsSetDesktopSize) {
  193. // Hack: Wait until we're in the proper mode and position until
  194. // resizing things, otherwise we might send the wrong thing.
  195. if (delayedFullscreen)
  196. delayedDesktopSize = true;
  197. else
  198. handleDesktopSize();
  199. }
  200. firstUpdate = false;
  201. }
  202. viewport->updateWindow();
  203. }
  204. void DesktopWindow::resizeFramebuffer(int new_w, int new_h)
  205. {
  206. if ((new_w == viewport->w()) && (new_h == viewport->h()))
  207. return;
  208. // If we're letting the viewport match the window perfectly, then
  209. // keep things that way for the new size, otherwise just keep things
  210. // like they are.
  211. if (!fullscreen_active()) {
  212. if ((w() == viewport->w()) && (h() == viewport->h()))
  213. size(new_w, new_h);
  214. else {
  215. // Make sure the window isn't too big. We do this manually because
  216. // we have to disable the window size restriction (and it isn't
  217. // entirely trustworthy to begin with).
  218. if ((w() > new_w) || (h() > new_h))
  219. size(__rfbmin(w(), new_w), __rfbmin(h(), new_h));
  220. }
  221. }
  222. viewport->size(new_w, new_h);
  223. repositionWidgets();
  224. }
  225. void DesktopWindow::setCursor(int width, int height,
  226. const rfb::Point& hotspot,
  227. const rdr::U8* data)
  228. {
  229. viewport->setCursor(width, height, hotspot, data);
  230. }
  231. void DesktopWindow::draw()
  232. {
  233. bool redraw;
  234. int X, Y, W, H;
  235. // X11 needs an off screen buffer for compositing to avoid flicker,
  236. // and alpha blending doesn't work for windows on Win32
  237. #if !defined(__APPLE__)
  238. // Adjust offscreen surface dimensions
  239. if ((offscreen == NULL) ||
  240. (offscreen->width() != w()) || (offscreen->height() != h())) {
  241. delete offscreen;
  242. offscreen = new Surface(w(), h());
  243. }
  244. #endif
  245. // Active area inside scrollbars
  246. W = w() - (vscroll->visible() ? vscroll->w() : 0);
  247. H = h() - (hscroll->visible() ? hscroll->h() : 0);
  248. // Full redraw?
  249. redraw = (damage() & ~FL_DAMAGE_CHILD);
  250. // Simplify the clip region to a simple rectangle in order to
  251. // properly draw all the layers even if they only partially overlap
  252. if (redraw)
  253. X = Y = 0;
  254. else
  255. fl_clip_box(0, 0, W, H, X, Y, W, H);
  256. fl_push_no_clip();
  257. fl_push_clip(X, Y, W, H);
  258. // Redraw background only on full redraws
  259. if (redraw) {
  260. if (offscreen)
  261. offscreen->clear(40, 40, 40);
  262. else
  263. fl_rectf(0, 0, W, H, 40, 40, 40);
  264. }
  265. if (offscreen) {
  266. viewport->draw(offscreen);
  267. viewport->clear_damage();
  268. } else {
  269. if (redraw)
  270. draw_child(*viewport);
  271. else
  272. update_child(*viewport);
  273. }
  274. // Debug graph (if active)
  275. if (statsGraph) {
  276. int ox, oy, ow, oh;
  277. ox = X = w() - statsGraph->width() - 30;
  278. oy = Y = h() - statsGraph->height() - 30;
  279. ow = statsGraph->width();
  280. oh = statsGraph->height();
  281. fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);
  282. if (offscreen)
  283. statsGraph->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, 204);
  284. else
  285. statsGraph->blend(ox - X, oy - Y, ox, oy, ow, oh, 204);
  286. }
  287. // Overlay (if active)
  288. if (overlay) {
  289. int ox, oy, ow, oh;
  290. ox = X = (w() - overlay->width()) / 2;
  291. oy = Y = 50;
  292. ow = overlay->width();
  293. oh = overlay->height();
  294. fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);
  295. if (offscreen)
  296. overlay->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
  297. else
  298. overlay->blend(ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
  299. }
  300. // Flush offscreen surface to screen
  301. if (offscreen) {
  302. fl_clip_box(0, 0, w(), h(), X, Y, W, H);
  303. offscreen->draw(X, Y, X, Y, W, H);
  304. }
  305. fl_pop_clip();
  306. fl_pop_clip();
  307. // Finally the scrollbars
  308. if (redraw) {
  309. draw_child(*hscroll);
  310. draw_child(*vscroll);
  311. } else {
  312. update_child(*hscroll);
  313. update_child(*vscroll);
  314. }
  315. }
  316. void DesktopWindow::resize(int x, int y, int w, int h)
  317. {
  318. bool resizing;
  319. #if ! (defined(WIN32) || defined(__APPLE__))
  320. // X11 window managers will treat a resize to cover the entire
  321. // monitor as a request to go full screen. Make sure we avoid this.
  322. if (!fullscreen_active()) {
  323. bool resize_req;
  324. // If there is no X11 window, then this must be a resize request,
  325. // not a notification from the X server.
  326. if (!shown())
  327. resize_req = true;
  328. else {
  329. // Otherwise we need to get the real window coordinates to tell
  330. // the difference
  331. XWindowAttributes actual;
  332. Window cr;
  333. int wx, wy;
  334. XGetWindowAttributes(fl_display, fl_xid(this), &actual);
  335. XTranslateCoordinates(fl_display, fl_xid(this), actual.root,
  336. 0, 0, &wx, &wy, &cr);
  337. // Actual resize request?
  338. if ((wx != x) || (wy != y) ||
  339. (actual.width != w) || (actual.height != h))
  340. resize_req = true;
  341. else
  342. resize_req = false;
  343. }
  344. if (resize_req) {
  345. for (int i = 0;i < Fl::screen_count();i++) {
  346. int sx, sy, sw, sh;
  347. Fl::screen_xywh(sx, sy, sw, sh, i);
  348. if ((sx == x) && (sy == y) && (sw == w) && (sh == h)) {
  349. vlog.info(_("Adjusting window size to avoid accidental full screen request"));
  350. // Assume a panel of some form and adjust the height
  351. y += 20;
  352. h -= 40;
  353. }
  354. }
  355. }
  356. }
  357. #endif
  358. if ((this->w() != w) || (this->h() != h))
  359. resizing = true;
  360. else
  361. resizing = false;
  362. Fl_Window::resize(x, y, w, h);
  363. if (resizing) {
  364. // Try to get the remote size to match our window size, provided
  365. // the following conditions are true:
  366. //
  367. // a) The user has this feature turned on
  368. // b) The server supports it
  369. // c) We're not still waiting for a chance to handle DesktopSize
  370. // d) We're not still waiting for startup fullscreen to kick in
  371. //
  372. if (not firstUpdate and not delayedFullscreen and
  373. ::remoteResize and cc->cp.supportsSetDesktopSize) {
  374. // We delay updating the remote desktop as we tend to get a flood
  375. // of resize events as the user is dragging the window.
  376. Fl::remove_timeout(handleResizeTimeout, this);
  377. Fl::add_timeout(0.5, handleResizeTimeout, this);
  378. }
  379. repositionWidgets();
  380. }
  381. }
  382. void DesktopWindow::menuOverlay(void* data)
  383. {
  384. DesktopWindow *self;
  385. self = (DesktopWindow*)data;
  386. self->setOverlay(_("Press %s to open the context menu"),
  387. (const char*)menuKey);
  388. }
  389. void DesktopWindow::setOverlay(const char* text, ...)
  390. {
  391. va_list ap;
  392. char textbuf[1024];
  393. Fl_Image_Surface *surface;
  394. Fl_RGB_Image* imageText;
  395. Fl_RGB_Image* image;
  396. unsigned char* buffer;
  397. int x, y;
  398. int w, h;
  399. unsigned char* a;
  400. const unsigned char* b;
  401. delete overlay;
  402. Fl::remove_timeout(updateOverlay, this);
  403. va_start(ap, text);
  404. vsnprintf(textbuf, sizeof(textbuf), text, ap);
  405. textbuf[sizeof(textbuf)-1] = '\0';
  406. va_end(ap);
  407. #if !defined(WIN32) && !defined(__APPLE__)
  408. // FLTK < 1.3.5 crashes if fl_gc is unset
  409. if (!fl_gc)
  410. fl_gc = XDefaultGC(fl_display, 0);
  411. #endif
  412. fl_font(FL_HELVETICA, FL_NORMAL_SIZE * 2);
  413. w = 0;
  414. fl_measure(textbuf, w, h);
  415. // Margins
  416. w += 80;
  417. h += 40;
  418. surface = new Fl_Image_Surface(w, h);
  419. surface->set_current();
  420. fl_rectf(0, 0, w, h, 0, 0, 0);
  421. fl_font(FL_HELVETICA, FL_NORMAL_SIZE * 2);
  422. fl_color(FL_WHITE);
  423. fl_draw(textbuf, 40, 20 + fl_height() - fl_descent());
  424. imageText = surface->image();
  425. delete surface;
  426. Fl_Display_Device::display_device()->set_current();
  427. buffer = new unsigned char[w * h * 4];
  428. image = new Fl_RGB_Image(buffer, w, h, 4);
  429. a = buffer;
  430. for (x = 0;x < image->w() * image->h();x++) {
  431. a[0] = a[1] = a[2] = 0x40;
  432. a[3] = 0xcc;
  433. a += 4;
  434. }
  435. a = buffer;
  436. b = (const unsigned char*)imageText->data()[0];
  437. for (y = 0;y < h;y++) {
  438. for (x = 0;x < w;x++) {
  439. unsigned char alpha;
  440. alpha = *b;
  441. a[0] = (unsigned)a[0] * (255 - alpha) / 255 + alpha;
  442. a[1] = (unsigned)a[1] * (255 - alpha) / 255 + alpha;
  443. a[2] = (unsigned)a[2] * (255 - alpha) / 255 + alpha;
  444. a[3] = 255 - (255 - a[3]) * (255 - alpha) / 255;
  445. a += 4;
  446. b += imageText->d();
  447. }
  448. if (imageText->ld() != 0)
  449. b += imageText->ld() - w * imageText->d();
  450. }
  451. delete imageText;
  452. x = (this->w() - image->w()) / 2;
  453. y = 50;
  454. w = image->w();
  455. h = image->h();
  456. overlay = new Surface(image);
  457. overlayAlpha = 0;
  458. gettimeofday(&overlayStart, NULL);
  459. delete image;
  460. Fl::add_timeout(1.0/60, updateOverlay, this);
  461. }
  462. void DesktopWindow::updateOverlay(void *data)
  463. {
  464. DesktopWindow *self;
  465. unsigned elapsed;
  466. self = (DesktopWindow*)data;
  467. elapsed = msSince(&self->overlayStart);
  468. if (elapsed < 500) {
  469. self->overlayAlpha = (unsigned)255 * elapsed / 500;
  470. Fl::add_timeout(1.0/60, updateOverlay, self);
  471. } else if (elapsed < 3500) {
  472. self->overlayAlpha = 255;
  473. Fl::add_timeout(3.0, updateOverlay, self);
  474. } else if (elapsed < 4000) {
  475. self->overlayAlpha = (unsigned)255 * (4000 - elapsed) / 500;
  476. Fl::add_timeout(1.0/60, updateOverlay, self);
  477. } else {
  478. delete self->overlay;
  479. self->overlay = NULL;
  480. }
  481. self->damage(FL_DAMAGE_USER1);
  482. }
  483. int DesktopWindow::handle(int event)
  484. {
  485. switch (event) {
  486. case FL_FULLSCREEN:
  487. fullScreen.setParam(fullscreen_active());
  488. // Update scroll bars
  489. repositionWidgets();
  490. if (!fullscreenSystemKeys)
  491. break;
  492. if (fullscreen_active())
  493. grabKeyboard();
  494. else
  495. ungrabKeyboard();
  496. break;
  497. case FL_ENTER:
  498. case FL_LEAVE:
  499. case FL_DRAG:
  500. case FL_MOVE:
  501. if (fullscreen_active()) {
  502. if (((viewport->x() < 0) && (Fl::event_x() < EDGE_SCROLL_SIZE)) ||
  503. ((viewport->x() + viewport->w() > w()) && (Fl::event_x() > w() - EDGE_SCROLL_SIZE)) ||
  504. ((viewport->y() < 0) && (Fl::event_y() < EDGE_SCROLL_SIZE)) ||
  505. ((viewport->y() + viewport->h() > h()) && (Fl::event_y() > h() - EDGE_SCROLL_SIZE))) {
  506. if (!Fl::has_timeout(handleEdgeScroll, this))
  507. Fl::add_timeout(0.1, handleEdgeScroll, this);
  508. }
  509. }
  510. // Continue processing so that the viewport also gets mouse events
  511. break;
  512. }
  513. return Fl_Window::handle(event);
  514. }
  515. int DesktopWindow::fltkHandle(int event, Fl_Window *win)
  516. {
  517. int ret;
  518. ret = Fl::handle_(event, win);
  519. // This is hackish and the result of the dodgy focus handling in FLTK.
  520. // The basic problem is that FLTK's view of focus and the system's tend
  521. // to differ, and as a result we do not see all the FL_FOCUS events we
  522. // need. Fortunately we can grab them here...
  523. DesktopWindow *dw = dynamic_cast<DesktopWindow*>(win);
  524. if (dw && fullscreenSystemKeys) {
  525. switch (event) {
  526. case FL_FOCUS:
  527. // FIXME: We reassert the keyboard grabbing on focus as FLTK there are
  528. // some issues we need to work around:
  529. // a) Fl::grab(0) on X11 will release the keyboard grab for us.
  530. // b) Gaining focus on the system level causes FLTK to switch
  531. // window level on OS X.
  532. if (dw->fullscreen_active())
  533. dw->grabKeyboard();
  534. break;
  535. case FL_UNFOCUS:
  536. // FIXME: We need to relinquish control when the entire window loses
  537. // focus as it is very tied to this specific window on some
  538. // platforms and we want to be able to open subwindows.
  539. dw->ungrabKeyboard();
  540. break;
  541. }
  542. }
  543. return ret;
  544. }
  545. void DesktopWindow::fullscreen_on()
  546. {
  547. if (not fullScreenAllMonitors)
  548. fullscreen_screens(-1, -1, -1, -1);
  549. else {
  550. int top, bottom, left, right;
  551. int top_y, bottom_y, left_x, right_x;
  552. int sx, sy, sw, sh;
  553. top = bottom = left = right = 0;
  554. Fl::screen_xywh(sx, sy, sw, sh, 0);
  555. top_y = sy;
  556. bottom_y = sy + sh;
  557. left_x = sx;
  558. right_x = sx + sw;
  559. for (int i = 1;i < Fl::screen_count();i++) {
  560. Fl::screen_xywh(sx, sy, sw, sh, i);
  561. if (sy < top_y) {
  562. top = i;
  563. top_y = sy;
  564. }
  565. if ((sy + sh) > bottom_y) {
  566. bottom = i;
  567. bottom_y = sy + sh;
  568. }
  569. if (sx < left_x) {
  570. left = i;
  571. left_x = sx;
  572. }
  573. if ((sx + sw) > right_x) {
  574. right = i;
  575. right_x = sx + sw;
  576. }
  577. }
  578. fullscreen_screens(top, bottom, left, right);
  579. }
  580. fullscreen();
  581. }
  582. void DesktopWindow::grabKeyboard()
  583. {
  584. // Grabbing the keyboard is fairly safe as FLTK reroutes events to the
  585. // correct widget regardless of which low level window got the system
  586. // event.
  587. // FIXME: Push this stuff into FLTK.
  588. #if defined(WIN32)
  589. int ret;
  590. ret = win32_enable_lowlevel_keyboard(fl_xid(this));
  591. if (ret != 0)
  592. vlog.error(_("Failure grabbing keyboard"));
  593. #elif defined(__APPLE__)
  594. int ret;
  595. ret = cocoa_capture_display(this, fullScreenAllMonitors);
  596. if (ret != 0)
  597. vlog.error(_("Failure grabbing keyboard"));
  598. #else
  599. int ret;
  600. ret = XGrabKeyboard(fl_display, fl_xid(this), True,
  601. GrabModeAsync, GrabModeAsync, CurrentTime);
  602. if (ret) {
  603. if (ret == AlreadyGrabbed) {
  604. // It seems like we can race with the WM in some cases.
  605. // Try again in a bit.
  606. if (!Fl::has_timeout(handleGrab, this))
  607. Fl::add_timeout(0.500, handleGrab, this);
  608. } else {
  609. vlog.error(_("Failure grabbing keyboard"));
  610. }
  611. }
  612. // We also need to grab the pointer as some WMs like to grab buttons
  613. // combined with modifies (e.g. Alt+Button0 in metacity).
  614. ret = XGrabPointer(fl_display, fl_xid(this), True,
  615. ButtonPressMask|ButtonReleaseMask|
  616. ButtonMotionMask|PointerMotionMask,
  617. GrabModeAsync, GrabModeAsync,
  618. None, None, CurrentTime);
  619. if (ret)
  620. vlog.error(_("Failure grabbing mouse"));
  621. #endif
  622. }
  623. void DesktopWindow::ungrabKeyboard()
  624. {
  625. Fl::remove_timeout(handleGrab, this);
  626. #if defined(WIN32)
  627. win32_disable_lowlevel_keyboard(fl_xid(this));
  628. #elif defined(__APPLE__)
  629. cocoa_release_display(this);
  630. #else
  631. // FLTK has a grab so lets not mess with it
  632. if (Fl::grab())
  633. return;
  634. XUngrabPointer(fl_display, fl_event_time);
  635. XUngrabKeyboard(fl_display, fl_event_time);
  636. #endif
  637. }
  638. void DesktopWindow::handleGrab(void *data)
  639. {
  640. DesktopWindow *self = (DesktopWindow*)data;
  641. assert(self);
  642. if (!fullscreenSystemKeys)
  643. return;
  644. if (!self->fullscreen_active())
  645. return;
  646. self->grabKeyboard();
  647. }
  648. #define _NET_WM_STATE_ADD 1 /* add/set property */
  649. void DesktopWindow::maximizeWindow()
  650. {
  651. #if defined(WIN32)
  652. // We cannot use ShowWindow() in full screen mode as it will
  653. // resize things implicitly. Fortunately modifying the style
  654. // directly results in a maximized state once we leave full screen.
  655. if (fullscreen_active()) {
  656. WINDOWINFO wi;
  657. wi.cbSize = sizeof(WINDOWINFO);
  658. GetWindowInfo(fl_xid(this), &wi);
  659. SetWindowLongPtr(fl_xid(this), GWL_STYLE, wi.dwStyle | WS_MAXIMIZE);
  660. } else
  661. ShowWindow(fl_xid(this), SW_MAXIMIZE);
  662. #elif defined(__APPLE__)
  663. // OS X is somewhat strange and does not really have a concept of a
  664. // maximized window, so we can simply resize the window to the workarea.
  665. // Note that we shouldn't do this whilst in full screen as that will
  666. // incorrectly adjust things.
  667. if (fullscreen_active())
  668. return;
  669. int X, Y, W, H;
  670. Fl::screen_work_area(X, Y, W, H, this->x(), this->y());
  671. size(W, H);
  672. #else
  673. // X11
  674. fl_open_display();
  675. Atom net_wm_state = XInternAtom (fl_display, "_NET_WM_STATE", 0);
  676. Atom net_wm_state_maximized_vert = XInternAtom (fl_display, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
  677. Atom net_wm_state_maximized_horz = XInternAtom (fl_display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
  678. XEvent e;
  679. e.xany.type = ClientMessage;
  680. e.xany.window = fl_xid(this);
  681. e.xclient.message_type = net_wm_state;
  682. e.xclient.format = 32;
  683. e.xclient.data.l[0] = _NET_WM_STATE_ADD;
  684. e.xclient.data.l[1] = net_wm_state_maximized_vert;
  685. e.xclient.data.l[2] = net_wm_state_maximized_horz;
  686. e.xclient.data.l[3] = 0;
  687. e.xclient.data.l[4] = 0;
  688. XSendEvent(fl_display, RootWindow(fl_display, fl_screen), 0, SubstructureNotifyMask | SubstructureRedirectMask, &e);
  689. #endif
  690. }
  691. void DesktopWindow::handleDesktopSize()
  692. {
  693. if (strcmp(desktopSize, "") != 0) {
  694. int width, height;
  695. // An explicit size has been requested
  696. if (sscanf(desktopSize, "%dx%d", &width, &height) != 2)
  697. return;
  698. remoteResize(width, height);
  699. } else if (::remoteResize) {
  700. // No explicit size, but remote resizing is on so make sure it
  701. // matches whatever size the window ended up being
  702. remoteResize(w(), h());
  703. }
  704. }
  705. void DesktopWindow::handleResizeTimeout(void *data)
  706. {
  707. DesktopWindow *self = (DesktopWindow *)data;
  708. assert(self);
  709. self->remoteResize(self->w(), self->h());
  710. }
  711. void DesktopWindow::remoteResize(int width, int height)
  712. {
  713. ScreenSet layout;
  714. ScreenSet::iterator iter;
  715. if (!fullscreen_active() || (width > w()) || (height > h())) {
  716. // In windowed mode (or the framebuffer is so large that we need
  717. // to scroll) we just report a single virtual screen that covers
  718. // the entire framebuffer.
  719. layout = cc->cp.screenLayout;
  720. // Not sure why we have no screens, but adding a new one should be
  721. // safe as there is nothing to conflict with...
  722. if (layout.num_screens() == 0)
  723. layout.add_screen(rfb::Screen());
  724. else if (layout.num_screens() != 1) {
  725. // More than one screen. Remove all but the first (which we
  726. // assume is the "primary").
  727. while (true) {
  728. iter = layout.begin();
  729. ++iter;
  730. if (iter == layout.end())
  731. break;
  732. layout.remove_screen(iter->id);
  733. }
  734. }
  735. // Resize the remaining single screen to the complete framebuffer
  736. layout.begin()->dimensions.tl.x = 0;
  737. layout.begin()->dimensions.tl.y = 0;
  738. layout.begin()->dimensions.br.x = width;
  739. layout.begin()->dimensions.br.y = height;
  740. } else {
  741. int i;
  742. rdr::U32 id;
  743. int sx, sy, sw, sh;
  744. rfb::Rect viewport_rect, screen_rect;
  745. // In full screen we report all screens that are fully covered.
  746. viewport_rect.setXYWH(x() + (w() - width)/2, y() + (h() - height)/2,
  747. width, height);
  748. // If we can find a matching screen in the existing set, we use
  749. // that, otherwise we create a brand new screen.
  750. //
  751. // FIXME: We should really track screens better so we can handle
  752. // a resized one.
  753. //
  754. for (i = 0;i < Fl::screen_count();i++) {
  755. Fl::screen_xywh(sx, sy, sw, sh, i);
  756. // Check that the screen is fully inside the framebuffer
  757. screen_rect.setXYWH(sx, sy, sw, sh);
  758. if (!screen_rect.enclosed_by(viewport_rect))
  759. continue;
  760. // Adjust the coordinates so they are relative to our viewport
  761. sx -= viewport_rect.tl.x;
  762. sy -= viewport_rect.tl.y;
  763. // Look for perfectly matching existing screen...
  764. for (iter = cc->cp.screenLayout.begin();
  765. iter != cc->cp.screenLayout.end(); ++iter) {
  766. if ((iter->dimensions.tl.x == sx) &&
  767. (iter->dimensions.tl.y == sy) &&
  768. (iter->dimensions.width() == sw) &&
  769. (iter->dimensions.height() == sh))
  770. break;
  771. }
  772. // Found it?
  773. if (iter != cc->cp.screenLayout.end()) {
  774. layout.add_screen(*iter);
  775. continue;
  776. }
  777. // Need to add a new one, which means we need to find an unused id
  778. while (true) {
  779. id = rand();
  780. for (iter = cc->cp.screenLayout.begin();
  781. iter != cc->cp.screenLayout.end(); ++iter) {
  782. if (iter->id == id)
  783. break;
  784. }
  785. if (iter == cc->cp.screenLayout.end())
  786. break;
  787. }
  788. layout.add_screen(rfb::Screen(id, sx, sy, sw, sh, 0));
  789. }
  790. // If the viewport doesn't match a physical screen, then we might
  791. // end up with no screens in the layout. Add a fake one...
  792. if (layout.num_screens() == 0)
  793. layout.add_screen(rfb::Screen(0, 0, 0, width, height, 0));
  794. }
  795. // Do we actually change anything?
  796. if ((width == cc->cp.width) &&
  797. (height == cc->cp.height) &&
  798. (layout == cc->cp.screenLayout))
  799. return;
  800. char buffer[2048];
  801. vlog.debug("Requesting framebuffer resize from %dx%d to %dx%d",
  802. cc->cp.width, cc->cp.height, width, height);
  803. layout.print(buffer, sizeof(buffer));
  804. vlog.debug("%s", buffer);
  805. if (!layout.validate(width, height)) {
  806. vlog.error(_("Invalid screen layout computed for resize request!"));
  807. return;
  808. }
  809. cc->writer()->writeSetDesktopSize(width, height, layout);
  810. }
  811. void DesktopWindow::repositionWidgets()
  812. {
  813. int new_x, new_y;
  814. // Viewport position
  815. new_x = viewport->x();
  816. new_y = viewport->y();
  817. if (w() > viewport->w())
  818. new_x = (w() - viewport->w()) / 2;
  819. else {
  820. if (viewport->x() > 0)
  821. new_x = 0;
  822. else if (w() > (viewport->x() + viewport->w()))
  823. new_x = w() - viewport->w();
  824. }
  825. // Same thing for y axis
  826. if (h() > viewport->h())
  827. new_y = (h() - viewport->h()) / 2;
  828. else {
  829. if (viewport->y() > 0)
  830. new_y = 0;
  831. else if (h() > (viewport->y() + viewport->h()))
  832. new_y = h() - viewport->h();
  833. }
  834. if ((new_x != viewport->x()) || (new_y != viewport->y())) {
  835. viewport->position(new_x, new_y);
  836. damage(FL_DAMAGE_SCROLL);
  837. }
  838. // Scrollbars visbility
  839. if (!fullscreen_active() && (w() < viewport->w()))
  840. hscroll->show();
  841. else
  842. hscroll->hide();
  843. if (!fullscreen_active() && (h() < viewport->h()))
  844. vscroll->show();
  845. else
  846. vscroll->hide();
  847. // Scrollbars positions
  848. hscroll->resize(0, h() - Fl::scrollbar_size(),
  849. w() - (vscroll->visible() ? Fl::scrollbar_size() : 0),
  850. Fl::scrollbar_size());
  851. vscroll->resize(w() - Fl::scrollbar_size(), 0,
  852. Fl::scrollbar_size(),
  853. h() - (hscroll->visible() ? Fl::scrollbar_size() : 0));
  854. // Scrollbars range
  855. hscroll->value(-viewport->x(),
  856. w() - (vscroll->visible() ? vscroll->w() : 0),
  857. 0, viewport->w());
  858. vscroll->value(-viewport->y(),
  859. h() - (hscroll->visible() ? hscroll->h() : 0),
  860. 0, viewport->h());
  861. hscroll->value(hscroll->clamp(hscroll->value()));
  862. vscroll->value(vscroll->clamp(vscroll->value()));
  863. }
  864. void DesktopWindow::handleClose(Fl_Widget *wnd, void *data)
  865. {
  866. exit_vncviewer();
  867. }
  868. void DesktopWindow::handleOptions(void *data)
  869. {
  870. DesktopWindow *self = (DesktopWindow*)data;
  871. if (self->fullscreen_active() && fullscreenSystemKeys)
  872. self->grabKeyboard();
  873. else
  874. self->ungrabKeyboard();
  875. if (fullScreen && !self->fullscreen_active())
  876. self->fullscreen_on();
  877. else if (!fullScreen && self->fullscreen_active())
  878. self->fullscreen_off();
  879. }
  880. void DesktopWindow::handleFullscreenTimeout(void *data)
  881. {
  882. DesktopWindow *self = (DesktopWindow *)data;
  883. assert(self);
  884. self->delayedFullscreen = false;
  885. if (self->delayedDesktopSize) {
  886. self->handleDesktopSize();
  887. self->delayedDesktopSize = false;
  888. }
  889. }
  890. void DesktopWindow::scrollTo(int x, int y)
  891. {
  892. x = hscroll->clamp(x);
  893. y = vscroll->clamp(y);
  894. hscroll->value(x);
  895. vscroll->value(y);
  896. if (!hscroll->visible())
  897. x = -viewport->x();
  898. if (!vscroll->visible())
  899. y = -viewport->y();
  900. // Scrollbar position results in inverse movement of
  901. // the viewport widget
  902. x = -x;
  903. y = -y;
  904. if ((viewport->x() == x) && (viewport->y() == y))
  905. return;
  906. viewport->position(x, y);
  907. damage(FL_DAMAGE_SCROLL);
  908. }
  909. void DesktopWindow::handleScroll(Fl_Widget *widget, void *data)
  910. {
  911. DesktopWindow *self = (DesktopWindow *)data;
  912. self->scrollTo(self->hscroll->value(), self->vscroll->value());
  913. }
  914. void DesktopWindow::handleEdgeScroll(void *data)
  915. {
  916. DesktopWindow *self = (DesktopWindow *)data;
  917. int mx, my;
  918. int dx, dy;
  919. assert(self);
  920. if (!self->fullscreen_active())
  921. return;
  922. mx = Fl::event_x();
  923. my = Fl::event_y();
  924. dx = dy = 0;
  925. // Clamp mouse position in case it is outside the window
  926. if (mx < 0)
  927. mx = 0;
  928. if (mx > self->w())
  929. mx = self->w();
  930. if (my < 0)
  931. my = 0;
  932. if (my > self->h())
  933. my = self->h();
  934. if ((self->viewport->x() < 0) && (mx < EDGE_SCROLL_SIZE))
  935. dx = EDGE_SCROLL_SPEED -
  936. EDGE_SCROLL_SPEED * mx / EDGE_SCROLL_SIZE;
  937. if ((self->viewport->x() + self->viewport->w() > self->w()) &&
  938. (mx > self->w() - EDGE_SCROLL_SIZE))
  939. dx = EDGE_SCROLL_SPEED * (self->w() - mx) / EDGE_SCROLL_SIZE -
  940. EDGE_SCROLL_SPEED;
  941. if ((self->viewport->y() < 0) && (my < EDGE_SCROLL_SIZE))
  942. dy = EDGE_SCROLL_SPEED -
  943. EDGE_SCROLL_SPEED * my / EDGE_SCROLL_SIZE;
  944. if ((self->viewport->y() + self->viewport->h() > self->h()) &&
  945. (my > self->h() - EDGE_SCROLL_SIZE))
  946. dy = EDGE_SCROLL_SPEED * (self->h() - my) / EDGE_SCROLL_SIZE -
  947. EDGE_SCROLL_SPEED;
  948. if ((dx == 0) && (dy == 0))
  949. return;
  950. self->scrollTo(self->hscroll->value() + dx, self->vscroll->value() + dy);
  951. Fl::repeat_timeout(0.1, handleEdgeScroll, data);
  952. }
  953. void DesktopWindow::handleStatsTimeout(void *data)
  954. {
  955. DesktopWindow *self = (DesktopWindow*)data;
  956. const size_t statsCount = sizeof(stats)/sizeof(stats[0]);
  957. unsigned frame, pixels, pos;
  958. unsigned elapsed;
  959. const unsigned statsWidth = 200;
  960. const unsigned statsHeight = 100;
  961. const unsigned graphWidth = statsWidth - 10;
  962. const unsigned graphHeight = statsHeight - 25;
  963. Fl_Image_Surface *surface;
  964. Fl_RGB_Image *image;
  965. unsigned maxFPS, maxPPS, maxBPS;
  966. size_t i;
  967. char buffer[256];
  968. frame = self->cc->getFrameCount();
  969. pixels = self->cc->getPixelCount();
  970. pos = self->cc->getPosition();
  971. elapsed = msSince(&self->statsLastTime);
  972. if (elapsed < 1)
  973. elapsed = 1;
  974. memmove(&self->stats[0], &self->stats[1], sizeof(stats[0])*(statsCount-1));
  975. self->stats[statsCount-1].fps = (frame - self->statsLastFrame) * 1000 / elapsed;
  976. self->stats[statsCount-1].pps = (pixels - self->statsLastPixels) * 1000 / elapsed;
  977. self->stats[statsCount-1].bps = (pos - self->statsLastPosition) * 1000 / elapsed;
  978. gettimeofday(&self->statsLastTime, NULL);
  979. self->statsLastFrame = frame;
  980. self->statsLastPixels = pixels;
  981. self->statsLastPosition = pos;
  982. #if !defined(WIN32) && !defined(__APPLE__)
  983. // FLTK < 1.3.5 crashes if fl_gc is unset
  984. if (!fl_gc)
  985. fl_gc = XDefaultGC(fl_display, 0);
  986. #endif
  987. surface = new Fl_Image_Surface(statsWidth, statsHeight);
  988. surface->set_current();
  989. fl_rectf(0, 0, statsWidth, statsHeight, FL_BLACK);
  990. fl_rect(5, 5, graphWidth, graphHeight, FL_WHITE);
  991. maxFPS = maxPPS = maxBPS = 0;
  992. for (i = 0;i < statsCount;i++) {
  993. if (self->stats[i].fps > maxFPS)
  994. maxFPS = self->stats[i].fps;
  995. if (self->stats[i].pps > maxPPS)
  996. maxPPS = self->stats[i].pps;
  997. if (self->stats[i].bps > maxBPS)
  998. maxBPS = self->stats[i].bps;
  999. }
  1000. if (maxFPS != 0) {
  1001. fl_color(FL_GREEN);
  1002. for (i = 0;i < statsCount-1;i++) {
  1003. fl_line(5 + i * graphWidth / statsCount,
  1004. 5 + graphHeight - graphHeight * self->stats[i].fps / maxFPS,
  1005. 5 + (i+1) * graphWidth / statsCount,
  1006. 5 + graphHeight - graphHeight * self->stats[i+1].fps / maxFPS);
  1007. }
  1008. }
  1009. if (maxPPS != 0) {
  1010. fl_color(FL_YELLOW);
  1011. for (i = 0;i < statsCount-1;i++) {
  1012. fl_line(5 + i * graphWidth / statsCount,
  1013. 5 + graphHeight - graphHeight * self->stats[i].pps / maxPPS,
  1014. 5 + (i+1) * graphWidth / statsCount,
  1015. 5 + graphHeight - graphHeight * self->stats[i+1].pps / maxPPS);
  1016. }
  1017. }
  1018. if (maxBPS != 0) {
  1019. fl_color(FL_RED);
  1020. for (i = 0;i < statsCount-1;i++) {
  1021. fl_line(5 + i * graphWidth / statsCount,
  1022. 5 + graphHeight - graphHeight * self->stats[i].bps / maxBPS,
  1023. 5 + (i+1) * graphWidth / statsCount,
  1024. 5 + graphHeight - graphHeight * self->stats[i+1].bps / maxBPS);
  1025. }
  1026. }
  1027. fl_font(FL_HELVETICA, 10);
  1028. fl_color(FL_GREEN);
  1029. snprintf(buffer, sizeof(buffer), "%u fps", self->stats[statsCount-1].fps);
  1030. fl_draw(buffer, 5, statsHeight - 5);
  1031. fl_color(FL_YELLOW);
  1032. siPrefix(self->stats[statsCount-1].pps * 8, "pix/s",
  1033. buffer, sizeof(buffer), 3);
  1034. fl_draw(buffer, 5 + (statsWidth-10)/3, statsHeight - 5);
  1035. fl_color(FL_RED);
  1036. iecPrefix(self->stats[statsCount-1].bps * 8, "Bps",
  1037. buffer, sizeof(buffer), 3);
  1038. fl_draw(buffer, 5 + (statsWidth-10)*2/3, statsHeight - 5);
  1039. image = surface->image();
  1040. delete surface;
  1041. Fl_Display_Device::display_device()->set_current();
  1042. delete self->statsGraph;
  1043. self->statsGraph = new Surface(image);
  1044. delete image;
  1045. self->damage(FL_DAMAGE_CHILD, self->w() - statsWidth - 30,
  1046. self->h() - statsHeight - 30,
  1047. statsWidth, statsHeight);
  1048. Fl::repeat_timeout(0.5, handleStatsTimeout, data);
  1049. }