You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DesktopWindow.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright (C) 2011-2016 Brian P. Hinz
  3. * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved.
  4. *
  5. * This is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This software is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this software; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  18. * USA.
  19. */
  20. package com.tigervnc.vncviewer;
  21. import java.awt.*;
  22. import java.awt.event.*;
  23. import java.lang.reflect.*;
  24. import java.util.*;
  25. import javax.swing.*;
  26. import javax.swing.Timer;
  27. import javax.swing.border.*;
  28. import com.tigervnc.rfb.*;
  29. import com.tigervnc.rfb.Point;
  30. import java.lang.Exception;
  31. import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
  32. import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
  33. import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
  34. import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
  35. import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
  36. import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
  37. import static com.tigervnc.vncviewer.Parameters.*;
  38. public class DesktopWindow extends JFrame
  39. {
  40. static LogWriter vlog = new LogWriter("DesktopWindow");
  41. public DesktopWindow(int w, int h, String name,
  42. PixelFormat serverPF, CConn cc_)
  43. {
  44. cc = cc_;
  45. firstUpdate = true;
  46. delayedFullscreen = false; delayedDesktopSize = false;
  47. setFocusable(false);
  48. setFocusTraversalKeysEnabled(false);
  49. getToolkit().setDynamicLayout(false);
  50. if (!VncViewer.os.startsWith("mac os x"))
  51. setIconImage(VncViewer.frameIcon);
  52. UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
  53. new UIDefaults.LazyInputMap(new Object[]{}));
  54. scroll = new JScrollPane(new Viewport(w, h, serverPF, cc));
  55. viewport = (Viewport)scroll.getViewport().getView();
  56. scroll.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
  57. getContentPane().add(scroll);
  58. setName(name);
  59. lastScaleFactor = scalingFactor.getValue();
  60. if (VncViewer.os.startsWith("mac os x"))
  61. if (!noLionFS.getValue())
  62. enableLionFS();
  63. OptionsDialog.addCallback("handleOptions", this);
  64. addWindowFocusListener(new WindowAdapter() {
  65. public void windowGainedFocus(WindowEvent e) {
  66. if (isVisible())
  67. if (scroll.getViewport() != null)
  68. scroll.getViewport().getView().requestFocusInWindow();
  69. }
  70. public void windowLostFocus(WindowEvent e) {
  71. viewport.releaseDownKeys();
  72. }
  73. });
  74. addWindowListener(new WindowAdapter() {
  75. public void windowClosing(WindowEvent e) {
  76. cc.close();
  77. }
  78. public void windowDeiconified(WindowEvent e) {
  79. // ViewportBorder sometimes lost when window is shaded or de-iconified
  80. repositionViewport();
  81. }
  82. });
  83. addWindowStateListener(new WindowAdapter() {
  84. public void windowStateChanged(WindowEvent e) {
  85. int state = e.getNewState();
  86. if ((state & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
  87. Rectangle b = getGraphicsConfiguration().getBounds();
  88. if (!b.contains(getLocationOnScreen()))
  89. setLocation((int)b.getX(), (int)b.getY());
  90. }
  91. // ViewportBorder sometimes lost when restoring on Windows
  92. repositionViewport();
  93. }
  94. });
  95. // Window resize events
  96. timer = new Timer(500, new AbstractAction() {
  97. public void actionPerformed(ActionEvent e) {
  98. handleResizeTimeout();
  99. }
  100. });
  101. timer.setRepeats(false);
  102. addComponentListener(new ComponentAdapter() {
  103. public void componentResized(ComponentEvent e) {
  104. if (remoteResize.getValue()) {
  105. if (timer.isRunning())
  106. timer.restart();
  107. else
  108. // Try to get the remote size to match our window size, provided
  109. // the following conditions are true:
  110. //
  111. // a) The user has this feature turned on
  112. // b) The server supports it
  113. // c) We're not still waiting for a chance to handle DesktopSize
  114. // d) We're not still waiting for startup fullscreen to kick in
  115. if (!firstUpdate && !delayedFullscreen &&
  116. remoteResize.getValue() && cc.cp.supportsSetDesktopSize)
  117. timer.start();
  118. } else {
  119. String scaleString = scalingFactor.getValue();
  120. if (!scaleString.matches("^[0-9]+$")) {
  121. Dimension maxSize = getContentPane().getSize();
  122. if ((maxSize.width != viewport.scaledWidth) ||
  123. (maxSize.height != viewport.scaledHeight))
  124. viewport.setScaledSize(maxSize.width, maxSize.height);
  125. if (!scaleString.equals("Auto")) {
  126. if (!isMaximized() && !fullscreen_active()) {
  127. int dx = getInsets().left + getInsets().right;
  128. int dy = getInsets().top + getInsets().bottom;
  129. setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
  130. }
  131. }
  132. }
  133. repositionViewport();
  134. }
  135. }
  136. });
  137. }
  138. // Remove resize listener in order to prevent recursion when resizing
  139. @Override
  140. public void setSize(Dimension d)
  141. {
  142. ComponentListener[] listeners = getListeners(ComponentListener.class);
  143. for (ComponentListener l : listeners)
  144. removeComponentListener(l);
  145. super.setSize(d);
  146. for (ComponentListener l : listeners)
  147. addComponentListener(l);
  148. }
  149. @Override
  150. public void setSize(int width, int height)
  151. {
  152. ComponentListener[] listeners = getListeners(ComponentListener.class);
  153. for (ComponentListener l : listeners)
  154. removeComponentListener(l);
  155. super.setSize(width, height);
  156. for (ComponentListener l : listeners)
  157. addComponentListener(l);
  158. }
  159. @Override
  160. public void setBounds(Rectangle r)
  161. {
  162. ComponentListener[] listeners = getListeners(ComponentListener.class);
  163. for (ComponentListener l : listeners)
  164. removeComponentListener(l);
  165. super.setBounds(r);
  166. for (ComponentListener l : listeners)
  167. addComponentListener(l);
  168. }
  169. private void repositionViewport()
  170. {
  171. scroll.revalidate();
  172. Rectangle r = scroll.getViewportBorderBounds();
  173. int dx = r.width - viewport.scaledWidth;
  174. int dy = r.height - viewport.scaledHeight;
  175. int top = (int)Math.max(Math.floor(dy/2), 0);
  176. int left = (int)Math.max(Math.floor(dx/2), 0);
  177. int bottom = (int)Math.max(dy - top, 0);
  178. int right = (int)Math.max(dx - left, 0);
  179. Insets insets = new Insets(top, left, bottom, right);
  180. scroll.setViewportBorder(new MatteBorder(insets, Color.BLACK));
  181. scroll.revalidate();
  182. }
  183. public PixelFormat getPreferredPF()
  184. {
  185. return viewport.getPreferredPF();
  186. }
  187. public void setName(String name)
  188. {
  189. setTitle(name);
  190. }
  191. // Copy the areas of the framebuffer that have been changed (damaged)
  192. // to the displayed window.
  193. public void updateWindow()
  194. {
  195. if (firstUpdate) {
  196. pack();
  197. if (embed.getValue()) {
  198. scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
  199. scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
  200. VncViewer.setupEmbeddedFrame(scroll);
  201. } else {
  202. if (fullScreen.getValue())
  203. fullscreen_on();
  204. else
  205. setVisible(true);
  206. if (maximize.getValue())
  207. setExtendedState(JFrame.MAXIMIZED_BOTH);
  208. }
  209. if (cc.cp.supportsSetDesktopSize && !desktopSize.getValue().equals("")) {
  210. // Hack: Wait until we're in the proper mode and position until
  211. // resizing things, otherwise we might send the wrong thing.
  212. if (delayedFullscreen)
  213. delayedDesktopSize = true;
  214. else
  215. handleDesktopSize();
  216. }
  217. firstUpdate = false;
  218. }
  219. viewport.updateWindow();
  220. }
  221. public void resizeFramebuffer(int new_w, int new_h)
  222. {
  223. if ((new_w == viewport.scaledWidth) && (new_h == viewport.scaledHeight))
  224. return;
  225. // If we're letting the viewport match the window perfectly, then
  226. // keep things that way for the new size, otherwise just keep things
  227. // like they are.
  228. int dx = getInsets().left + getInsets().right;
  229. int dy = getInsets().top + getInsets().bottom;
  230. if (!fullscreen_active()) {
  231. if ((w() == viewport.scaledWidth) && (h() == viewport.scaledHeight))
  232. setSize(new_w+dx, new_h+dy);
  233. else {
  234. // Make sure the window isn't too big. We do this manually because
  235. // we have to disable the window size restriction (and it isn't
  236. // entirely trustworthy to begin with).
  237. if ((w() > new_w) || (h() > new_h))
  238. setSize(Math.min(w(), new_w)+dx, Math.min(h(), new_h)+dy);
  239. }
  240. }
  241. viewport.resize(0, 0, new_w, new_h);
  242. // We might not resize the main window, so we need to manually call this
  243. // to make sure the viewport is centered.
  244. repositionViewport();
  245. // repositionViewport() makes sure the scroll widget notices any changes
  246. // in position, but it might be just the size that changes so we also
  247. // need a poke here as well.
  248. validate();
  249. }
  250. public void setCursor(int width, int height, Point hotspot,
  251. byte[] data)
  252. {
  253. viewport.setCursor(width, height, hotspot, data);
  254. }
  255. public void fullscreen_on()
  256. {
  257. fullScreen.setParam(true);
  258. lastState = getExtendedState();
  259. lastBounds = getBounds();
  260. dispose();
  261. // Screen bounds calculation affected by maximized window?
  262. setExtendedState(JFrame.NORMAL);
  263. setUndecorated(true);
  264. setVisible(true);
  265. setBounds(getScreenBounds());
  266. }
  267. public void fullscreen_off()
  268. {
  269. fullScreen.setParam(false);
  270. dispose();
  271. setUndecorated(false);
  272. setExtendedState(lastState);
  273. setBounds(lastBounds);
  274. setVisible(true);
  275. }
  276. public boolean fullscreen_active()
  277. {
  278. return isUndecorated();
  279. }
  280. private void handleDesktopSize()
  281. {
  282. if (!desktopSize.getValue().equals("")) {
  283. int width, height;
  284. // An explicit size has been requested
  285. if (desktopSize.getValue().split("x").length != 2)
  286. return;
  287. width = Integer.parseInt(desktopSize.getValue().split("x")[0]);
  288. height = Integer.parseInt(desktopSize.getValue().split("x")[1]);
  289. remoteResize(width, height);
  290. } else if (remoteResize.getValue()) {
  291. // No explicit size, but remote resizing is on so make sure it
  292. // matches whatever size the window ended up being
  293. remoteResize(w(), h());
  294. }
  295. }
  296. public void handleResizeTimeout()
  297. {
  298. DesktopWindow self = (DesktopWindow)this;
  299. assert(self != null);
  300. self.remoteResize(self.w(), self.h());
  301. }
  302. private void remoteResize(int width, int height)
  303. {
  304. ScreenSet layout;
  305. ListIterator<Screen> iter;
  306. if (!fullscreen_active() || (width > w()) || (height > h())) {
  307. // In windowed mode (or the framebuffer is so large that we need
  308. // to scroll) we just report a single virtual screen that covers
  309. // the entire framebuffer.
  310. layout = cc.cp.screenLayout;
  311. // Not sure why we have no screens, but adding a new one should be
  312. // safe as there is nothing to conflict with...
  313. if (layout.num_screens() == 0)
  314. layout.add_screen(new Screen());
  315. else if (layout.num_screens() != 1) {
  316. // More than one screen. Remove all but the first (which we
  317. // assume is the "primary").
  318. while (true) {
  319. iter = layout.begin();
  320. Screen screen = iter.next();
  321. if (iter == layout.end())
  322. break;
  323. layout.remove_screen(screen.id);
  324. }
  325. }
  326. // Resize the remaining single screen to the complete framebuffer
  327. ((Screen)layout.begin().next()).dimensions.tl.x = 0;
  328. ((Screen)layout.begin().next()).dimensions.tl.y = 0;
  329. ((Screen)layout.begin().next()).dimensions.br.x = width;
  330. ((Screen)layout.begin().next()).dimensions.br.y = height;
  331. } else {
  332. layout = new ScreenSet();
  333. int id;
  334. int sx, sy, sw, sh;
  335. Rect viewport_rect = new Rect();
  336. Rect screen_rect = new Rect();
  337. // In full screen we report all screens that are fully covered.
  338. viewport_rect.setXYWH(x() + (w() - width)/2, y() + (h() - height)/2,
  339. width, height);
  340. // If we can find a matching screen in the existing set, we use
  341. // that, otherwise we create a brand new screen.
  342. //
  343. // FIXME: We should really track screens better so we can handle
  344. // a resized one.
  345. //
  346. GraphicsEnvironment ge =
  347. GraphicsEnvironment.getLocalGraphicsEnvironment();
  348. for (GraphicsDevice gd : ge.getScreenDevices()) {
  349. for (GraphicsConfiguration gc : gd.getConfigurations()) {
  350. Rectangle bounds = gc.getBounds();
  351. sx = bounds.x;
  352. sy = bounds.y;
  353. sw = bounds.width;
  354. sh = bounds.height;
  355. // Check that the screen is fully inside the framebuffer
  356. screen_rect.setXYWH(sx, sy, sw, sh);
  357. if (!screen_rect.enclosed_by(viewport_rect))
  358. continue;
  359. // Adjust the coordinates so they are relative to our viewport
  360. sx -= viewport_rect.tl.x;
  361. sy -= viewport_rect.tl.y;
  362. // Look for perfectly matching existing screen...
  363. for (iter = cc.cp.screenLayout.begin();
  364. iter != cc.cp.screenLayout.end(); iter.next()) {
  365. Screen screen = iter.next(); iter.previous();
  366. if ((screen.dimensions.tl.x == sx) &&
  367. (screen.dimensions.tl.y == sy) &&
  368. (screen.dimensions.width() == sw) &&
  369. (screen.dimensions.height() == sh))
  370. break;
  371. }
  372. // Found it?
  373. if (iter != cc.cp.screenLayout.end()) {
  374. layout.add_screen(iter.next());
  375. continue;
  376. }
  377. // Need to add a new one, which means we need to find an unused id
  378. Random rng = new Random();
  379. while (true) {
  380. id = rng.nextInt();
  381. for (iter = cc.cp.screenLayout.begin();
  382. iter != cc.cp.screenLayout.end(); iter.next()) {
  383. Screen screen = iter.next(); iter.previous();
  384. if (screen.id == id)
  385. break;
  386. }
  387. if (iter == cc.cp.screenLayout.end())
  388. break;
  389. }
  390. layout.add_screen(new Screen(id, sx, sy, sw, sh, 0));
  391. }
  392. // If the viewport doesn't match a physical screen, then we might
  393. // end up with no screens in the layout. Add a fake one...
  394. if (layout.num_screens() == 0)
  395. layout.add_screen(new Screen(0, 0, 0, width, height, 0));
  396. }
  397. }
  398. // Do we actually change anything?
  399. if ((width == cc.cp.width) &&
  400. (height == cc.cp.height) &&
  401. (layout == cc.cp.screenLayout))
  402. return;
  403. String buffer;
  404. vlog.debug(String.format("Requesting framebuffer resize from %dx%d to %dx%d",
  405. cc.cp.width, cc.cp.height, width, height));
  406. layout.debug_print();
  407. if (!layout.validate(width, height)) {
  408. vlog.error("Invalid screen layout computed for resize request!");
  409. return;
  410. }
  411. cc.writer().writeSetDesktopSize(width, height, layout);
  412. }
  413. boolean lionFSSupported() { return canDoLionFS; }
  414. private int x() { return getContentPane().getX(); }
  415. private int y() { return getContentPane().getY(); }
  416. private int w() { return getContentPane().getWidth(); }
  417. private int h() { return getContentPane().getHeight(); }
  418. void enableLionFS() {
  419. try {
  420. String version = System.getProperty("os.version");
  421. int firstDot = version.indexOf('.');
  422. int lastDot = version.lastIndexOf('.');
  423. if (lastDot > firstDot && lastDot >= 0) {
  424. version = version.substring(0, version.indexOf('.', firstDot + 1));
  425. }
  426. double v = Double.parseDouble(version);
  427. if (v < 10.7)
  428. throw new Exception("Operating system version is " + v);
  429. Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
  430. Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
  431. Method setWindowCanFullScreen =
  432. fsuClass.getMethod("setWindowCanFullScreen", argClasses);
  433. setWindowCanFullScreen.invoke(fsuClass, this, true);
  434. canDoLionFS = true;
  435. } catch (Exception e) {
  436. vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
  437. e.getMessage());
  438. }
  439. }
  440. public void toggleLionFS() {
  441. try {
  442. Class appClass = Class.forName("com.apple.eawt.Application");
  443. Method getApplication = appClass.getMethod("getApplication",
  444. (Class[])null);
  445. Object app = getApplication.invoke(appClass);
  446. Method requestToggleFullScreen =
  447. appClass.getMethod("requestToggleFullScreen", Window.class);
  448. requestToggleFullScreen.invoke(app, this);
  449. } catch (Exception e) {
  450. vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
  451. e.getMessage());
  452. }
  453. }
  454. public boolean isMaximized()
  455. {
  456. int state = getExtendedState();
  457. return ((state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
  458. }
  459. public Dimension getScreenSize() {
  460. return getScreenBounds().getSize();
  461. }
  462. public Rectangle getScreenBounds() {
  463. GraphicsEnvironment ge =
  464. GraphicsEnvironment.getLocalGraphicsEnvironment();
  465. Rectangle r = new Rectangle();
  466. if (fullScreenAllMonitors.getValue()) {
  467. for (GraphicsDevice gd : ge.getScreenDevices())
  468. for (GraphicsConfiguration gc : gd.getConfigurations())
  469. r = r.union(gc.getBounds());
  470. } else {
  471. GraphicsConfiguration gc = getGraphicsConfiguration();
  472. r = gc.getBounds();
  473. }
  474. return r;
  475. }
  476. public static Window getFullScreenWindow() {
  477. GraphicsEnvironment ge =
  478. GraphicsEnvironment.getLocalGraphicsEnvironment();
  479. for (GraphicsDevice gd : ge.getScreenDevices()) {
  480. Window fullScreenWindow = gd.getFullScreenWindow();
  481. if (fullScreenWindow != null)
  482. return fullScreenWindow;
  483. }
  484. return null;
  485. }
  486. public static void setFullScreenWindow(Window fullScreenWindow) {
  487. GraphicsEnvironment ge =
  488. GraphicsEnvironment.getLocalGraphicsEnvironment();
  489. if (fullScreenAllMonitors.getValue()) {
  490. for (GraphicsDevice gd : ge.getScreenDevices())
  491. gd.setFullScreenWindow(fullScreenWindow);
  492. } else {
  493. GraphicsDevice gd = ge.getDefaultScreenDevice();
  494. gd.setFullScreenWindow(fullScreenWindow);
  495. }
  496. }
  497. public void handleOptions()
  498. {
  499. if (fullScreen.getValue() && !fullscreen_active())
  500. fullscreen_on();
  501. else if (!fullScreen.getValue() && fullscreen_active())
  502. fullscreen_off();
  503. if (remoteResize.getValue()) {
  504. scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
  505. scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
  506. remoteResize(w(), h());
  507. } else {
  508. String scaleString = scalingFactor.getValue();
  509. if (!scaleString.equals(lastScaleFactor)) {
  510. if (scaleString.matches("^[0-9]+$")) {
  511. scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
  512. scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
  513. viewport.setScaledSize(cc.cp.width, cc.cp.height);
  514. } else {
  515. scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
  516. scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
  517. viewport.setScaledSize(w(), h());
  518. }
  519. if (isMaximized() || fullscreen_active()) {
  520. repositionViewport();
  521. } else {
  522. int dx = getInsets().left + getInsets().right;
  523. int dy = getInsets().top + getInsets().bottom;
  524. setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
  525. }
  526. repositionViewport();
  527. lastScaleFactor = scaleString;
  528. }
  529. }
  530. if (isVisible()) {
  531. toFront();
  532. requestFocus();
  533. }
  534. }
  535. public void handleFullscreenTimeout()
  536. {
  537. DesktopWindow self = (DesktopWindow)this;
  538. assert(self != null);
  539. self.delayedFullscreen = false;
  540. if (self.delayedDesktopSize) {
  541. self.handleDesktopSize();
  542. self.delayedDesktopSize = false;
  543. }
  544. }
  545. private CConn cc;
  546. private JScrollPane scroll;
  547. public Viewport viewport;
  548. private boolean firstUpdate;
  549. private boolean delayedFullscreen;
  550. private boolean delayedDesktopSize;
  551. private boolean canDoLionFS;
  552. private String lastScaleFactor;
  553. private Rectangle lastBounds;
  554. private int lastState;
  555. private Timer timer;
  556. }