123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright (C) 2011-2016 Brian P. Hinz
- * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
- package com.tigervnc.vncviewer;
-
- import java.awt.*;
- import java.awt.event.*;
- import java.lang.reflect.*;
- import java.util.*;
- import javax.swing.*;
- import javax.swing.Timer;
- import javax.swing.border.*;
-
- import com.tigervnc.rfb.*;
- import com.tigervnc.rfb.Point;
- import java.lang.Exception;
-
- import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
- import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
- import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
- import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
- import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
- import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
-
- import static com.tigervnc.vncviewer.Parameters.*;
-
- public class DesktopWindow extends JFrame
- {
-
- static LogWriter vlog = new LogWriter("DesktopWindow");
-
- public DesktopWindow(int w, int h, String name,
- PixelFormat serverPF, CConn cc_)
- {
- cc = cc_;
- firstUpdate = true;
- delayedFullscreen = false; delayedDesktopSize = false;
-
- setFocusable(false);
- setFocusTraversalKeysEnabled(false);
- getToolkit().setDynamicLayout(false);
- if (!VncViewer.os.startsWith("mac os x"))
- setIconImage(VncViewer.frameIcon);
- UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
- new UIDefaults.LazyInputMap(new Object[]{}));
- scroll = new JScrollPane(new Viewport(w, h, serverPF, cc));
- viewport = (Viewport)scroll.getViewport().getView();
- scroll.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
- getContentPane().add(scroll);
-
- setName(name);
-
- lastScaleFactor = scalingFactor.getValue();
- if (VncViewer.os.startsWith("mac os x"))
- if (!noLionFS.getValue())
- enableLionFS();
-
- OptionsDialog.addCallback("handleOptions", this);
-
- addWindowFocusListener(new WindowAdapter() {
- public void windowGainedFocus(WindowEvent e) {
- if (isVisible())
- if (scroll.getViewport() != null)
- scroll.getViewport().getView().requestFocusInWindow();
- }
- public void windowLostFocus(WindowEvent e) {
- viewport.releaseDownKeys();
- }
- });
-
- addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- cc.close();
- }
- public void windowDeiconified(WindowEvent e) {
- // ViewportBorder sometimes lost when window is shaded or de-iconified
- repositionViewport();
- }
- });
-
- addWindowStateListener(new WindowAdapter() {
- public void windowStateChanged(WindowEvent e) {
- int state = e.getNewState();
- if ((state & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
- Rectangle b = getGraphicsConfiguration().getBounds();
- if (!b.contains(getLocationOnScreen()))
- setLocation((int)b.getX(), (int)b.getY());
- }
- // ViewportBorder sometimes lost when restoring on Windows
- repositionViewport();
- }
- });
-
- // Window resize events
- timer = new Timer(500, new AbstractAction() {
- public void actionPerformed(ActionEvent e) {
- handleResizeTimeout();
- }
- });
- timer.setRepeats(false);
- addComponentListener(new ComponentAdapter() {
- public void componentResized(ComponentEvent e) {
- if (remoteResize.getValue()) {
- if (timer.isRunning())
- timer.restart();
- else
- // Try to get the remote size to match our window size, provided
- // the following conditions are true:
- //
- // a) The user has this feature turned on
- // b) The server supports it
- // c) We're not still waiting for a chance to handle DesktopSize
- // d) We're not still waiting for startup fullscreen to kick in
- if (!firstUpdate && !delayedFullscreen &&
- remoteResize.getValue() && cc.cp.supportsSetDesktopSize)
- timer.start();
- } else {
- String scaleString = scalingFactor.getValue();
- if (!scaleString.matches("^[0-9]+$")) {
- Dimension maxSize = getContentPane().getSize();
- if ((maxSize.width != viewport.scaledWidth) ||
- (maxSize.height != viewport.scaledHeight))
- viewport.setScaledSize(maxSize.width, maxSize.height);
- if (!scaleString.equals("Auto")) {
- if (!isMaximized() && !fullscreen_active()) {
- int dx = getInsets().left + getInsets().right;
- int dy = getInsets().top + getInsets().bottom;
- setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
- }
- }
- }
- repositionViewport();
- }
- }
- });
-
- }
-
- // Remove resize listener in order to prevent recursion when resizing
- @Override
- public void setSize(Dimension d)
- {
- ComponentListener[] listeners = getListeners(ComponentListener.class);
- for (ComponentListener l : listeners)
- removeComponentListener(l);
- super.setSize(d);
- for (ComponentListener l : listeners)
- addComponentListener(l);
- }
-
- @Override
- public void setSize(int width, int height)
- {
- ComponentListener[] listeners = getListeners(ComponentListener.class);
- for (ComponentListener l : listeners)
- removeComponentListener(l);
- super.setSize(width, height);
- for (ComponentListener l : listeners)
- addComponentListener(l);
- }
-
- @Override
- public void setBounds(Rectangle r)
- {
- ComponentListener[] listeners = getListeners(ComponentListener.class);
- for (ComponentListener l : listeners)
- removeComponentListener(l);
- super.setBounds(r);
- for (ComponentListener l : listeners)
- addComponentListener(l);
- }
-
- private void repositionViewport()
- {
- scroll.revalidate();
- Rectangle r = scroll.getViewportBorderBounds();
- int dx = r.width - viewport.scaledWidth;
- int dy = r.height - viewport.scaledHeight;
- int top = (int)Math.max(Math.floor(dy/2), 0);
- int left = (int)Math.max(Math.floor(dx/2), 0);
- int bottom = (int)Math.max(dy - top, 0);
- int right = (int)Math.max(dx - left, 0);
- Insets insets = new Insets(top, left, bottom, right);
- scroll.setViewportBorder(new MatteBorder(insets, Color.BLACK));
- scroll.revalidate();
- }
-
- public PixelFormat getPreferredPF()
- {
- return viewport.getPreferredPF();
- }
-
- public void setName(String name)
- {
- setTitle(name);
- }
-
- // Copy the areas of the framebuffer that have been changed (damaged)
- // to the displayed window.
-
- public void updateWindow()
- {
- if (firstUpdate) {
- pack();
- if (embed.getValue()) {
- scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
- scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
- VncViewer.setupEmbeddedFrame(scroll);
- } else {
- if (fullScreen.getValue())
- fullscreen_on();
- else
- setVisible(true);
-
- if (maximize.getValue())
- setExtendedState(JFrame.MAXIMIZED_BOTH);
- }
-
- if (cc.cp.supportsSetDesktopSize && !desktopSize.getValue().equals("")) {
- // Hack: Wait until we're in the proper mode and position until
- // resizing things, otherwise we might send the wrong thing.
- if (delayedFullscreen)
- delayedDesktopSize = true;
- else
- handleDesktopSize();
- }
- firstUpdate = false;
- }
-
- viewport.updateWindow();
- }
-
- public void resizeFramebuffer(int new_w, int new_h)
- {
- if ((new_w == viewport.scaledWidth) && (new_h == viewport.scaledHeight))
- return;
-
- // If we're letting the viewport match the window perfectly, then
- // keep things that way for the new size, otherwise just keep things
- // like they are.
- int dx = getInsets().left + getInsets().right;
- int dy = getInsets().top + getInsets().bottom;
- if (!fullscreen_active()) {
- if ((w() == viewport.scaledWidth) && (h() == viewport.scaledHeight))
- setSize(new_w+dx, new_h+dy);
- else {
- // Make sure the window isn't too big. We do this manually because
- // we have to disable the window size restriction (and it isn't
- // entirely trustworthy to begin with).
- if ((w() > new_w) || (h() > new_h))
- setSize(Math.min(w(), new_w)+dx, Math.min(h(), new_h)+dy);
- }
- }
-
- viewport.resize(0, 0, new_w, new_h);
-
- // We might not resize the main window, so we need to manually call this
- // to make sure the viewport is centered.
- repositionViewport();
-
- // repositionViewport() makes sure the scroll widget notices any changes
- // in position, but it might be just the size that changes so we also
- // need a poke here as well.
- validate();
- }
-
- public void setCursor(int width, int height, Point hotspot,
- byte[] data)
- {
- viewport.setCursor(width, height, hotspot, data);
- }
-
- public void fullscreen_on()
- {
- fullScreen.setParam(true);
- lastState = getExtendedState();
- lastBounds = getBounds();
- dispose();
- // Screen bounds calculation affected by maximized window?
- setExtendedState(JFrame.NORMAL);
- setUndecorated(true);
- setVisible(true);
- setBounds(getScreenBounds());
- }
-
- public void fullscreen_off()
- {
- fullScreen.setParam(false);
- dispose();
- setUndecorated(false);
- setExtendedState(lastState);
- setBounds(lastBounds);
- setVisible(true);
- }
-
- public boolean fullscreen_active()
- {
- return isUndecorated();
- }
-
- private void handleDesktopSize()
- {
- if (!desktopSize.getValue().equals("")) {
- int width, height;
-
- // An explicit size has been requested
-
- if (desktopSize.getValue().split("x").length != 2)
- return;
-
- width = Integer.parseInt(desktopSize.getValue().split("x")[0]);
- height = Integer.parseInt(desktopSize.getValue().split("x")[1]);
- remoteResize(width, height);
- } else if (remoteResize.getValue()) {
- // No explicit size, but remote resizing is on so make sure it
- // matches whatever size the window ended up being
- remoteResize(w(), h());
- }
- }
-
- public void handleResizeTimeout()
- {
- DesktopWindow self = (DesktopWindow)this;
-
- assert(self != null);
-
- self.remoteResize(self.w(), self.h());
- }
-
- private void remoteResize(int width, int height)
- {
- ScreenSet layout;
- ListIterator<Screen> iter;
-
- if (!fullscreen_active() || (width > w()) || (height > h())) {
- // In windowed mode (or the framebuffer is so large that we need
- // to scroll) we just report a single virtual screen that covers
- // the entire framebuffer.
-
- layout = cc.cp.screenLayout;
-
- // Not sure why we have no screens, but adding a new one should be
- // safe as there is nothing to conflict with...
- if (layout.num_screens() == 0)
- layout.add_screen(new Screen());
- else if (layout.num_screens() != 1) {
- // More than one screen. Remove all but the first (which we
- // assume is the "primary").
-
- while (true) {
- iter = layout.begin();
- Screen screen = iter.next();
-
- if (iter == layout.end())
- break;
-
- layout.remove_screen(screen.id);
- }
- }
-
- // Resize the remaining single screen to the complete framebuffer
- ((Screen)layout.begin().next()).dimensions.tl.x = 0;
- ((Screen)layout.begin().next()).dimensions.tl.y = 0;
- ((Screen)layout.begin().next()).dimensions.br.x = width;
- ((Screen)layout.begin().next()).dimensions.br.y = height;
- } else {
- layout = new ScreenSet();
- int id;
- int sx, sy, sw, sh;
- Rect viewport_rect = new Rect();
- Rect screen_rect = new Rect();
-
- // In full screen we report all screens that are fully covered.
-
- viewport_rect.setXYWH(x() + (w() - width)/2, y() + (h() - height)/2,
- width, height);
-
- // If we can find a matching screen in the existing set, we use
- // that, otherwise we create a brand new screen.
- //
- // FIXME: We should really track screens better so we can handle
- // a resized one.
- //
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- for (GraphicsDevice gd : ge.getScreenDevices()) {
- for (GraphicsConfiguration gc : gd.getConfigurations()) {
- Rectangle bounds = gc.getBounds();
- sx = bounds.x;
- sy = bounds.y;
- sw = bounds.width;
- sh = bounds.height;
-
- // Check that the screen is fully inside the framebuffer
- screen_rect.setXYWH(sx, sy, sw, sh);
- if (!screen_rect.enclosed_by(viewport_rect))
- continue;
-
- // Adjust the coordinates so they are relative to our viewport
- sx -= viewport_rect.tl.x;
- sy -= viewport_rect.tl.y;
-
- // Look for perfectly matching existing screen...
- for (iter = cc.cp.screenLayout.begin();
- iter != cc.cp.screenLayout.end(); iter.next()) {
- Screen screen = iter.next(); iter.previous();
- if ((screen.dimensions.tl.x == sx) &&
- (screen.dimensions.tl.y == sy) &&
- (screen.dimensions.width() == sw) &&
- (screen.dimensions.height() == sh))
- break;
- }
-
- // Found it?
- if (iter != cc.cp.screenLayout.end()) {
- layout.add_screen(iter.next());
- continue;
- }
-
- // Need to add a new one, which means we need to find an unused id
- Random rng = new Random();
- while (true) {
- id = rng.nextInt();
- for (iter = cc.cp.screenLayout.begin();
- iter != cc.cp.screenLayout.end(); iter.next()) {
- Screen screen = iter.next(); iter.previous();
- if (screen.id == id)
- break;
- }
-
- if (iter == cc.cp.screenLayout.end())
- break;
- }
-
- layout.add_screen(new Screen(id, sx, sy, sw, sh, 0));
- }
-
- // If the viewport doesn't match a physical screen, then we might
- // end up with no screens in the layout. Add a fake one...
- if (layout.num_screens() == 0)
- layout.add_screen(new Screen(0, 0, 0, width, height, 0));
- }
- }
-
- // Do we actually change anything?
- if ((width == cc.cp.width) &&
- (height == cc.cp.height) &&
- (layout == cc.cp.screenLayout))
- return;
-
- String buffer;
- vlog.debug(String.format("Requesting framebuffer resize from %dx%d to %dx%d",
- cc.cp.width, cc.cp.height, width, height));
- layout.debug_print();
-
- if (!layout.validate(width, height)) {
- vlog.error("Invalid screen layout computed for resize request!");
- return;
- }
-
- cc.writer().writeSetDesktopSize(width, height, layout);
- }
-
- boolean lionFSSupported() { return canDoLionFS; }
-
- private int x() { return getContentPane().getX(); }
- private int y() { return getContentPane().getY(); }
- private int w() { return getContentPane().getWidth(); }
- private int h() { return getContentPane().getHeight(); }
-
- void enableLionFS() {
- try {
- String version = System.getProperty("os.version");
- int firstDot = version.indexOf('.');
- int lastDot = version.lastIndexOf('.');
- if (lastDot > firstDot && lastDot >= 0) {
- version = version.substring(0, version.indexOf('.', firstDot + 1));
- }
- double v = Double.parseDouble(version);
- if (v < 10.7)
- throw new Exception("Operating system version is " + v);
-
- Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
- Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
- Method setWindowCanFullScreen =
- fsuClass.getMethod("setWindowCanFullScreen", argClasses);
- setWindowCanFullScreen.invoke(fsuClass, this, true);
-
- canDoLionFS = true;
- } catch (Exception e) {
- vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
- e.getMessage());
- }
- }
-
- public void toggleLionFS() {
- try {
- Class appClass = Class.forName("com.apple.eawt.Application");
- Method getApplication = appClass.getMethod("getApplication",
- (Class[])null);
- Object app = getApplication.invoke(appClass);
- Method requestToggleFullScreen =
- appClass.getMethod("requestToggleFullScreen", Window.class);
- requestToggleFullScreen.invoke(app, this);
- } catch (Exception e) {
- vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
- e.getMessage());
- }
- }
-
-
- public boolean isMaximized()
- {
- int state = getExtendedState();
- return ((state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
- }
-
- public Dimension getScreenSize() {
- return getScreenBounds().getSize();
- }
-
- public Rectangle getScreenBounds() {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- Rectangle r = new Rectangle();
- if (fullScreenAllMonitors.getValue()) {
- for (GraphicsDevice gd : ge.getScreenDevices())
- for (GraphicsConfiguration gc : gd.getConfigurations())
- r = r.union(gc.getBounds());
- } else {
- GraphicsConfiguration gc = getGraphicsConfiguration();
- r = gc.getBounds();
- }
- return r;
- }
-
- public static Window getFullScreenWindow() {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- for (GraphicsDevice gd : ge.getScreenDevices()) {
- Window fullScreenWindow = gd.getFullScreenWindow();
- if (fullScreenWindow != null)
- return fullScreenWindow;
- }
- return null;
- }
-
- public static void setFullScreenWindow(Window fullScreenWindow) {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- if (fullScreenAllMonitors.getValue()) {
- for (GraphicsDevice gd : ge.getScreenDevices())
- gd.setFullScreenWindow(fullScreenWindow);
- } else {
- GraphicsDevice gd = ge.getDefaultScreenDevice();
- gd.setFullScreenWindow(fullScreenWindow);
- }
- }
-
- public void handleOptions()
- {
-
- if (fullScreen.getValue() && !fullscreen_active())
- fullscreen_on();
- else if (!fullScreen.getValue() && fullscreen_active())
- fullscreen_off();
-
- if (remoteResize.getValue()) {
- scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
- scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
- remoteResize(w(), h());
- } else {
- String scaleString = scalingFactor.getValue();
- if (!scaleString.equals(lastScaleFactor)) {
- if (scaleString.matches("^[0-9]+$")) {
- scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
- scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
- viewport.setScaledSize(cc.cp.width, cc.cp.height);
- } else {
- scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
- scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
- viewport.setScaledSize(w(), h());
- }
-
- if (isMaximized() || fullscreen_active()) {
- repositionViewport();
- } else {
- int dx = getInsets().left + getInsets().right;
- int dy = getInsets().top + getInsets().bottom;
- setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
- }
-
- repositionViewport();
- lastScaleFactor = scaleString;
- }
- }
-
- if (isVisible()) {
- toFront();
- requestFocus();
- }
- }
-
- public void handleFullscreenTimeout()
- {
- DesktopWindow self = (DesktopWindow)this;
-
- assert(self != null);
-
- self.delayedFullscreen = false;
-
- if (self.delayedDesktopSize) {
- self.handleDesktopSize();
- self.delayedDesktopSize = false;
- }
- }
-
- private CConn cc;
- private JScrollPane scroll;
- public Viewport viewport;
-
- private boolean firstUpdate;
- private boolean delayedFullscreen;
- private boolean delayedDesktopSize;
- private boolean canDoLionFS;
- private String lastScaleFactor;
- private Rectangle lastBounds;
- private int lastState;
- private Timer timer;
- }
|