Convert Limelight to command line application

This commit is contained in:
Iwan Timmer
2014-01-07 01:40:51 +01:00
parent fa4f760cb3
commit 3f438c8e68
5 changed files with 57 additions and 1033 deletions

View File

@@ -4,14 +4,8 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import com.limelight.binding.LibraryHelper;
import com.limelight.binding.PlatformBinding;
import com.limelight.gui.MainFrame;
import com.limelight.gui.StreamFrame;
import com.limelight.input.gamepad.Gamepad;
import com.limelight.input.gamepad.GamepadListener;
import com.limelight.input.gamepad.NativeGamepad;
@@ -19,9 +13,14 @@ import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener;
import com.limelight.nvstream.StreamConfiguration;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.settings.PreferencesManager;
import com.limelight.settings.PreferencesManager.Preferences;
import com.limelight.settings.PreferencesManager.Preferences.Resolution;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import org.xmlpull.v1.XmlPullParserException;
/**
* Main class for Limelight-pc contains methods for starting the application as well
@@ -31,13 +30,10 @@ import com.limelight.settings.PreferencesManager.Preferences.Resolution;
*/
public class Limelight implements NvConnectionListener {
public static final double VERSION = 1.0;
public static boolean COMMAND_LINE_LAUNCH = false;
private String host;
private StreamFrame streamFrame;
private NvConnection conn;
private boolean connectionTerminating;
private static JFrame limeFrame;
/**
* Constructs a new instance based on the given host
@@ -51,11 +47,8 @@ public class Limelight implements NvConnectionListener {
* Creates a connection to the host and starts up the stream.
*/
private void startUp(StreamConfiguration streamConfig, boolean fullscreen) {
streamFrame = new StreamFrame();
conn = new NvConnection(host, this, streamConfig);
streamFrame.build(this, conn, streamConfig, fullscreen);
conn.start(PlatformBinding.getDeviceName(), streamFrame,
conn.start(PlatformBinding.getDeviceName(), null,
VideoDecoderRenderer.FLAG_PREFER_QUALITY,
PlatformBinding.getAudioRenderer(),
PlatformBinding.getVideoDecoderRenderer());
@@ -63,6 +56,46 @@ public class Limelight implements NvConnectionListener {
GamepadListener.getInstance().addDeviceListener(new Gamepad(conn));
}
private void pair() {
String macAddress;
try {
macAddress = NvConnection.getMacAddressString();
} catch (SocketException e) {
e.printStackTrace();
return;
}
if (macAddress == null) {
displayError("Pair", "Couldn't find a MAC address");
return;
}
NvHTTP httpConn;
try {
httpConn = new NvHTTP(InetAddress.getByName(host),
macAddress, PlatformBinding.getDeviceName());
try {
if (httpConn.getPairState()) {
displayError("Pair", "Already paired");
} else {
int session = httpConn.getSessionId();
if (session == 0) {
displayError("Pair", "Pairing was declined by the target");
} else {
displayMessage("Pairing was successful");
}
}
} catch (IOException e) {
displayError("Pair", e.getMessage());
} catch (XmlPullParserException e) {
displayError("Pair", e.getMessage());
}
} catch (UnknownHostException e1) {
displayError("Pair", "Failed to resolve host");
}
}
/*
* Creates a StreamConfiguration given a Resolution.
* Used to specify what kind of stream will be used.
@@ -82,15 +115,6 @@ public class Limelight implements NvConnectionListener {
return null;
}
}
/*
* Creates the main frame for the application.
*/
private static void createFrame() {
MainFrame main = new MainFrame();
main.build();
limeFrame = main.getLimeFrame();
}
/**
* Creates a new instance and starts the stream.
@@ -111,50 +135,16 @@ public class Limelight implements NvConnectionListener {
* @param args unused.
*/
public static void main(String args[]) {
// Redirect logging to a file if we're running from a JAR
if (LibraryHelper.isRunningFromJar()) {
try {
System.setErr(new PrintStream(new File("error.log")));
System.setOut(new PrintStream(new File("output.log")));
} catch (IOException e) {
}
}
//fix the menu bar if we are running in osx
if (System.getProperty("os.name").contains("Mac OS X")) {
// take the menu bar off the jframe
System.setProperty("apple.laf.useScreenMenuBar", "true");
// set the name of the application menu item
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Limelight");
} else {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception e) {
System.out.println("Unable to set cross platform look and feel.");
e.printStackTrace();
System.exit(2);
}
}
LibraryHelper.prepareNativeLibraries();
NativeGamepad.addListener(GamepadListener.getInstance());
NativeGamepad.start();
// launching with command line arguments
if (args.length > 0) {
parseCommandLine(args);
} else {
createFrame();
}
parseCommandLine(args);
}
//TODO: make this less jank
private static void parseCommandLine(String[] args) {
String host = null;
boolean fullscreen = false;
boolean pair = false;
int resolution = 720;
int refresh = 30;
@@ -167,6 +157,8 @@ public class Limelight implements NvConnectionListener {
System.out.println("Syntax error: hostname or ip address expected after -host");
System.exit(3);
}
} else if (args[i].equals("-pair")) {
pair = true;
} else if (args[i].equals("-fs")) {
fullscreen = true;
} else if (args[i].equals("-720")) {
@@ -187,7 +179,7 @@ public class Limelight implements NvConnectionListener {
System.exit(5);
}
Resolution streamRes = null;
Resolution streamRes = Resolution.RES_720_30;
if (resolution == 720 && refresh == 30) {
streamRes = Resolution.RES_720_30;
@@ -202,8 +194,10 @@ public class Limelight implements NvConnectionListener {
StreamConfiguration streamConfig = createConfiguration(streamRes);
Limelight limelight = new Limelight(host);
limelight.startUp(streamConfig, fullscreen);
COMMAND_LINE_LAUNCH = true;
if (!pair)
limelight.startUp(streamConfig, fullscreen);
else
limelight.pair();
}
@@ -219,7 +213,6 @@ public class Limelight implements NvConnectionListener {
@Override
public void stageStarting(Stage stage) {
System.out.println("Starting "+stage.getName());
streamFrame.showSpinner(stage);
}
/**
@@ -237,7 +230,6 @@ public class Limelight implements NvConnectionListener {
*/
@Override
public void stageFailed(Stage stage) {
streamFrame.dispose();
conn.stop();
displayError("Connection Error", "Starting " + stage.getName() + " failed");
}
@@ -247,7 +239,6 @@ public class Limelight implements NvConnectionListener {
*/
@Override
public void connectionStarted() {
streamFrame.hideSpinner();
}
/**
@@ -272,7 +263,6 @@ public class Limelight implements NvConnectionListener {
new Thread(new Runnable() {
@Override
public void run() {
streamFrame.dispose();
displayError("Connection Terminated", "The connection failed unexpectedly");
}
}).start();
@@ -285,7 +275,7 @@ public class Limelight implements NvConnectionListener {
*/
@Override
public void displayMessage(String message) {
JOptionPane.showMessageDialog(limeFrame, message, "Limelight", JOptionPane.INFORMATION_MESSAGE);
System.out.println(message);
}
/**
@@ -294,7 +284,7 @@ public class Limelight implements NvConnectionListener {
* @param message the message to show the user
*/
public void displayError(String title, String message) {
JOptionPane.showMessageDialog(limeFrame, message, title, JOptionPane.ERROR_MESSAGE);
System.err.println(title + " " + message);
}
}

View File

@@ -1,342 +0,0 @@
package com.limelight.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.HashMap;
import java.util.Map.Entry;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import com.limelight.input.Device;
import com.limelight.input.DeviceListener;
import com.limelight.input.gamepad.GamepadComponent;
import com.limelight.input.gamepad.GamepadListener;
import com.limelight.input.gamepad.GamepadMapping;
import com.limelight.input.gamepad.GamepadMapping.Mapping;
import com.limelight.input.gamepad.SourceComponent;
import com.limelight.settings.GamepadSettingsManager;
/**
* A frame used to configure the gamepad mappings.
* @author Diego Waxemberg
*/
public class GamepadConfigFrame extends JFrame {
private static final long serialVersionUID = 1L;
private boolean configChanged = false;
private MappingThread mappingThread;
private GamepadMapping config;
private HashMap<Box, Mapping> componentMap;
/**
* Constructs a new config frame. The frame is initially invisible and will <br>
* be made visible after all components are built by calling <code>build()</code>
*/
public GamepadConfigFrame() {
super("Gamepad Settings");
System.out.println("Creating Settings Frame");
this.setSize(850, 550);
this.setResizable(false);
this.setAlwaysOnTop(true);
config = GamepadSettingsManager.getSettings();
}
/**
* Builds all components of the config frame and sets the frame visible.
*/
public void build() {
componentMap = new HashMap<Box, Mapping>();
GridLayout layout = new GridLayout(GamepadComponent.values().length/2 + 1, 2);
layout.setHgap(60);
layout.setVgap(3);
JPanel mainPanel = new JPanel(layout);
GamepadComponent[] components = GamepadComponent.values();
for (int i = 0; i < components.length; i++) {
Mapping mapping = config.get(components[i]);
if (mapping == null) {
mapping = config.new Mapping(components[i], false, false);
config.insertMapping(mapping, null);
}
Box componentBox = createComponentBox(mapping);
mainPanel.add(componentBox);
}
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(dim.width/2-this.getSize().width/2, dim.height/2-this.getSize().height/2);
this.setLayout(new BorderLayout());
this.getContentPane().add(mainPanel, "Center");
this.getContentPane().add(Box.createVerticalStrut(20), "North");
this.getContentPane().add(Box.createVerticalStrut(20), "South");
this.getContentPane().add(Box.createHorizontalStrut(20), "East");
this.getContentPane().add(Box.createHorizontalStrut(20), "West");
this.addWindowListener(createWindowListener());
this.setVisible(true);
}
/*
* Creates the box that holds the button and checkboxes
*/
private Box createComponentBox(Mapping mapping) {
Box componentBox = Box.createHorizontalBox();
JButton mapButton = new JButton();
JCheckBox invertBox = new GamepadCheckBox("Invert", GamepadCheckBox.Type.INVERT);
JCheckBox triggerBox = new GamepadCheckBox("Trigger", GamepadCheckBox.Type.TRIGGER);
Dimension buttonSize = new Dimension(110, 24);
mapButton.setMaximumSize(buttonSize);
mapButton.setMinimumSize(buttonSize);
mapButton.setPreferredSize(buttonSize);
mapButton.addActionListener(createMapListener());
setButtonText(mapButton, config.getMapping(mapping.padComp));
invertBox.setSelected(mapping.invert);
invertBox.addActionListener(createCheckboxListener());
invertBox.setName(mapping.padComp.name());
triggerBox.setSelected(mapping.trigger);
triggerBox.addActionListener(createCheckboxListener());
triggerBox.setName(mapping.padComp.name());
triggerBox.setToolTipText("If this component should act as a trigger. (one-way axis)");
componentBox.add(Box.createHorizontalStrut(5));
componentBox.add(mapping.padComp.getLabel());
componentBox.add(Box.createHorizontalGlue());
componentBox.add(mapButton);
componentBox.add(invertBox);
componentBox.add(triggerBox);
componentBox.add(Box.createHorizontalStrut(5));
componentBox.setBorder(new LineBorder(Color.GRAY, 1, true));
componentMap.put(componentBox, mapping);
return componentBox;
}
/*
* Creates the listener for the checkbox
*/
private ActionListener createCheckboxListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox clicked = (JCheckBox)e.getSource();
GamepadComponent padComp = GamepadComponent.valueOf(clicked.getName());
Mapping currentMapping = config.get(padComp);
if (currentMapping == null) {
//this makes more semantic sense to me than using !=
clicked.setSelected(!(clicked.isSelected()));
} else {
((GamepadCheckBox)clicked).setValue(currentMapping, clicked.isSelected());
configChanged = true;
}
}
};
}
/*
* Creates the listener for the window.
* It will save configs on exit and restart controller threads
*/
private WindowListener createWindowListener() {
return new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (mappingThread != null && mappingThread.isAlive()) {
mappingThread.interrupt();
}
if (configChanged) {
updateConfigs();
}
dispose();
}
};
}
/*
* Creates the listener for the map button
*/
private ActionListener createMapListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Box toMap = (Box)((JButton)e.getSource()).getParent();
if (GamepadListener.getInstance().deviceCount() == 0) {
JOptionPane.showMessageDialog(GamepadConfigFrame.this, "No Gamepad Detected");
return;
}
map(toMap);
}
};
}
/*
* Maps a gamepad component to the clicked component
*/
private void map(final Box toMap) {
if (mappingThread == null || !mappingThread.isAlive()) {
//a little janky, could probably be fixed up a bit
final JButton buttonPressed = getButton(toMap);
final Mapping mappingToMap = componentMap.get(toMap);
buttonPressed.setSelected(true);
buttonPressed.setText("Select Input");
mappingThread = new MappingThread(buttonPressed, mappingToMap);
mappingThread.start();
GamepadListener.getInstance().addDeviceListener(mappingThread);
}
}
/*
* Helper method to get the box component that contains the given a mapping
*/
private Box getBox(Mapping mapping) {
for (Entry<Box, Mapping> entry : componentMap.entrySet()) {
if (entry.getValue() == mapping) {
return entry.getKey();
}
}
return null;
}
/*
* Helper method to get the button out of the box component
*/
private JButton getButton(Box componentBox) {
for (java.awt.Component comp : componentBox.getComponents()) {
if (comp instanceof JButton)
return (JButton)comp;
}
return null;
}
/*
* Writes the current cofig to the configs on disk.
*/
private void updateConfigs() {
GamepadSettingsManager.writeSettings(config);
}
private void setButtonText(JButton button, SourceComponent comp) {
if (comp == null) {
button.setText("");
} else {
button.setText(comp.getType().name() + " " + comp.getId());
}
}
private class MappingThread extends Thread implements DeviceListener {
private SourceComponent newMapping = null;
private JButton buttonPressed;
private Mapping mappingToMap;
public MappingThread(JButton buttonPressed, Mapping mappingToMap) {
super("Gamepad Mapping Thread");
this.buttonPressed = buttonPressed;
this.mappingToMap = mappingToMap;
}
@Override
public void run() {
while (newMapping == null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
setButtonText(buttonPressed, config.getMapping(mappingToMap.padComp));
GamepadListener.getInstance().removeListener(this);
return;
}
}
Mapping oldConfig = config.get(newMapping);
if (oldConfig != null) {
getButton(getBox(oldConfig)).setText("");
}
config.insertMapping(mappingToMap, newMapping);
setButtonText(buttonPressed, newMapping);
configChanged = true;
GamepadListener.getInstance().removeListener(this);
}
@Override
public void handleButton(Device device, int buttonId, boolean pressed) {
if (pressed) {
newMapping = new SourceComponent(SourceComponent.Type.BUTTON, buttonId);
}
}
@Override
public void handleAxis(Device device, int axisId, float newValue,
float lastValue) {
if (Math.abs(newValue) > 0.75) {
newMapping = new SourceComponent(SourceComponent.Type.AXIS, axisId);
}
}
}
private static class GamepadCheckBox extends JCheckBox {
private static final long serialVersionUID = 1L;
private enum Type { TRIGGER, INVERT }
private Type type;
public GamepadCheckBox(String text, Type type) {
super(text);
this.type = type;
}
public void setValue(Mapping mapping, boolean value) {
switch (type) {
case TRIGGER:
mapping.trigger = value;
break;
case INVERT:
mapping.invert = value;
break;
default:
System.out.println("You did something terrible and should feel terrible.");
System.out.println("Fix it or the checkbox gods will smite you!");
break;
}
}
}
}

View File

@@ -1,226 +0,0 @@
package com.limelight.gui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.xmlpull.v1.XmlPullParserException;
import com.limelight.Limelight;
import com.limelight.binding.PlatformBinding;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.settings.PreferencesManager;
import com.limelight.settings.PreferencesManager.Preferences;
/**
* The main frame of Limelight that allows the user to specify the host and begin the stream.
* @author Diego Waxemberg
* <br>Cameron Gutman
*/
public class MainFrame {
private JTextField hostField;
private JButton pair;
private JButton stream;
private JFrame limeFrame;
/**
* Gets the actual JFrame this class creates
* @return the JFrame that is the main frame
*/
public JFrame getLimeFrame() {
return limeFrame;
}
/**
* Builds all components of the frame, including the frame itself and displays it to the user.
*/
public void build() {
limeFrame = new JFrame("Limelight");
limeFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container mainPane = limeFrame.getContentPane();
mainPane.setLayout(new BorderLayout());
JPanel centerPane = new JPanel();
centerPane.setLayout(new BoxLayout(centerPane, BoxLayout.Y_AXIS));
Preferences prefs = PreferencesManager.getPreferences();
hostField = new JTextField();
hostField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 24));
hostField.setToolTipText("Enter host name or IP address");
hostField.setText(prefs.getHost());
hostField.setSelectionStart(0);
hostField.setSelectionEnd(hostField.getText().length());
stream = new JButton("Start Streaming");
stream.addActionListener(createStreamButtonListener());
stream.setToolTipText("Start the GeForce stream");
pair = new JButton("Pair");
pair.addActionListener(createPairButtonListener());
pair.setToolTipText("Send pair request to GeForce PC");
Box streamBox = Box.createHorizontalBox();
streamBox.add(Box.createHorizontalGlue());
streamBox.add(stream);
streamBox.add(Box.createHorizontalGlue());
Box pairBox = Box.createHorizontalBox();
pairBox.add(Box.createHorizontalGlue());
pairBox.add(pair);
pairBox.add(Box.createHorizontalGlue());
Box hostBox = Box.createHorizontalBox();
hostBox.add(Box.createHorizontalStrut(20));
hostBox.add(hostField);
hostBox.add(Box.createHorizontalStrut(20));
Box contentBox = Box.createVerticalBox();
contentBox.add(Box.createVerticalStrut(20));
contentBox.add(hostBox);
contentBox.add(Box.createVerticalStrut(5));
contentBox.add(streamBox);
contentBox.add(Box.createVerticalStrut(10));
contentBox.add(pairBox);
contentBox.add(Box.createVerticalGlue());
centerPane.add(contentBox);
mainPane.add(centerPane, "Center");
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
limeFrame.setJMenuBar(createMenuBar());
limeFrame.getRootPane().setDefaultButton(stream);
limeFrame.setSize(300, 200);
limeFrame.setLocation(dim.width/2-limeFrame.getSize().width/2, dim.height/2-limeFrame.getSize().height/2);
limeFrame.setResizable(false);
limeFrame.setVisible(true);
}
/*
* Creates the menu bar for the user to go to preferences, mappings, etc.
*/
private JMenuBar createMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu optionsMenu = new JMenu("Options");
JMenuItem gamepadSettings = new JMenuItem("Gamepad Settings");
JMenuItem generalSettings = new JMenuItem("Preferences");
gamepadSettings.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new GamepadConfigFrame().build();
}
});
generalSettings.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new PreferencesFrame().build();
}
});
optionsMenu.add(gamepadSettings);
optionsMenu.add(generalSettings);
menuBar.add(optionsMenu);
return menuBar;
}
/*
* Creates the listener for the stream button- starts the stream process
*/
private ActionListener createStreamButtonListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String host = hostField.getText();
Preferences prefs = PreferencesManager.getPreferences();
if (!host.equals(prefs.getHost())) {
prefs.setHost(host);
PreferencesManager.writePreferences(prefs);
}
Limelight.createInstance(host);
}
};
}
/*
* Creates the listener for the pair button- requests a pairing with the specified host
*/
private ActionListener createPairButtonListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new Thread(new Runnable() {
@Override
public void run() {
String macAddress;
try {
macAddress = NvConnection.getMacAddressString();
} catch (SocketException e) {
e.printStackTrace();
return;
}
if (macAddress == null) {
System.out.println("Couldn't find a MAC address");
return;
}
NvHTTP httpConn;
String message;
try {
httpConn = new NvHTTP(InetAddress.getByName(hostField.getText()),
macAddress, PlatformBinding.getDeviceName());
try {
if (httpConn.getPairState()) {
message = "Already paired";
}
else {
int session = httpConn.getSessionId();
if (session == 0) {
message = "Pairing was declined by the target";
}
else {
message = "Pairing was successful";
}
}
} catch (IOException e) {
message = e.getMessage();
} catch (XmlPullParserException e) {
message = e.getMessage();
}
} catch (UnknownHostException e1) {
message = "Failed to resolve host";
}
JOptionPane.showMessageDialog(limeFrame, message, "Limelight", JOptionPane.INFORMATION_MESSAGE);
}
}).start();
}
};
}
}

View File

@@ -1,111 +0,0 @@
package com.limelight.gui;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.limelight.settings.PreferencesManager;
import com.limelight.settings.PreferencesManager.Preferences;
import com.limelight.settings.PreferencesManager.Preferences.Resolution;
/**
* A frame that holds user preferences such as streaming resolution
* @author Diego Waxemberg
*/
public class PreferencesFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JComboBox resolution;
private JCheckBox fullscreen;
private Preferences prefs;
/**
* Construcs a new frame and loads the saved preferences.
* <br>The frame is not made visible until a call to <br>build()</br> is made.
*/
public PreferencesFrame() {
super("Preferences");
this.setSize(200, 100);
this.setResizable(false);
this.setAlwaysOnTop(true);
prefs = PreferencesManager.getPreferences();
}
/**
* Constructs all components of the frame and makes the frame visible to the user.
*/
public void build() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
resolution = new JComboBox();
for (Resolution res : Resolution.values()) {
resolution.addItem(res);
}
resolution.setSelectedItem(prefs.getResolution());
fullscreen = new JCheckBox("Fullscreen");
fullscreen.setSelected(prefs.getFullscreen());
Box resolutionBox = Box.createHorizontalBox();
resolutionBox.add(Box.createHorizontalGlue());
resolutionBox.add(resolution);
resolutionBox.add(Box.createHorizontalGlue());
Box fullscreenBox = Box.createHorizontalBox();
fullscreenBox.add(Box.createHorizontalGlue());
fullscreenBox.add(fullscreen);
fullscreenBox.add(Box.createHorizontalGlue());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(resolutionBox);
mainPanel.add(Box.createVerticalStrut(5));
mainPanel.add(fullscreenBox);
mainPanel.add(Box.createVerticalGlue());
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
if (prefsChanged()) {
writePreferences();
}
}
});
this.getContentPane().add(mainPanel);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
//center on screen
this.setLocation((int)dim.getWidth()/2-this.getWidth()/2, (int)dim.getHeight()/2-this.getHeight()/2);
this.setVisible(true);
}
/*
* Checks if the preferences have changed from the cached preferences.
*/
private boolean prefsChanged() {
return (prefs.getResolution() != resolution.getSelectedItem()) ||
(prefs.getFullscreen() != fullscreen.isSelected());
}
/*
* Writes the preferences to the disk.
*/
private void writePreferences() {
prefs.setFullscreen(fullscreen.isSelected());
prefs.setResolution((Resolution)resolution.getSelectedItem());
PreferencesManager.writePreferences(prefs);
}
}

View File

@@ -1,287 +0,0 @@
package com.limelight.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import com.limelight.Limelight;
import com.limelight.input.KeyboardHandler;
import com.limelight.input.MouseHandler;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener.Stage;
import com.limelight.nvstream.StreamConfiguration;
/**
* The frame to which the video is rendered
* @author Diego Waxemberg
* <br>Cameron Gutman
*
*/
public class StreamFrame extends JFrame {
private static final long serialVersionUID = 1L;
private static final double DESIRED_ASPECT_RATIO = 16.0/9.0;
private static final double ALTERNATE_ASPECT_RATIO = 16.0/10.0;
private KeyboardHandler keyboard;
private MouseHandler mouse;
private JProgressBar spinner;
private JLabel spinnerLabel;
private Cursor noCursor;
private Limelight limelight;
/**
* Frees the mouse ie. makes it visible and allowed to move outside the frame.
*/
public void freeMouse() {
mouse.free();
showCursor();
}
/**
* Captures the mouse ie. makes it invisible and not allowed to leave the frame
*/
public void captureMouse() {
mouse.capture();
hideCursor();
}
/**
* Builds the components of this frame with the specified configurations.
* @param conn the connection this frame belongs to
* @param streamConfig the configurations for this frame
* @param fullscreen if the frame should be made fullscreen
*/
public void build(Limelight limelight, NvConnection conn, StreamConfiguration streamConfig, boolean fullscreen) {
this.limelight = limelight;
keyboard = new KeyboardHandler(conn, this);
mouse = new MouseHandler(conn, this);
this.addKeyListener(keyboard);
this.addMouseListener(mouse);
this.addMouseMotionListener(mouse);
this.setFocusTraversalKeysEnabled(false);
this.setSize(streamConfig.getWidth(), streamConfig.getHeight());
this.setBackground(Color.BLACK);
this.getContentPane().setBackground(Color.BLACK);
this.getRootPane().setBackground(Color.BLACK);
this.addWindowListener(createWindowListener());
if (fullscreen) {
makeFullScreen(streamConfig);
}
hideCursor();
this.setVisible(true);
}
private ArrayList<DisplayMode> getDisplayModesByAspectRatio(DisplayMode[] configs, double aspectRatio) {
ArrayList<DisplayMode> matchingConfigs = new ArrayList<DisplayMode>();
for (DisplayMode config : configs) {
if ((double)config.getWidth()/(double)config.getHeight() == aspectRatio) {
matchingConfigs.add(config);
}
}
return matchingConfigs;
}
private DisplayMode getBestDisplay(StreamConfiguration targetConfig, DisplayMode[] configs) {
int targetDisplaySize = targetConfig.getWidth()*targetConfig.getHeight();
// Try to match the target aspect ratio
ArrayList<DisplayMode> aspectMatchingConfigs = getDisplayModesByAspectRatio(configs, DESIRED_ASPECT_RATIO);
if (aspectMatchingConfigs.size() == 0) {
// No matches for the target, so try the alternate
aspectMatchingConfigs = getDisplayModesByAspectRatio(configs, ALTERNATE_ASPECT_RATIO);
if (aspectMatchingConfigs.size() == 0) {
// No matches for either, so just use all of them
aspectMatchingConfigs = new ArrayList<DisplayMode>(Arrays.asList(configs));
}
}
// Sort by display size
Collections.sort(aspectMatchingConfigs, new Comparator<DisplayMode>() {
@Override
public int compare(DisplayMode o1, DisplayMode o2) {
if (o1.getWidth()*o1.getHeight() > o2.getWidth()*o2.getHeight()) {
return -1;
} else if (o2.getWidth()*o2.getHeight() > o1.getWidth()*o1.getHeight()) {
return 1;
} else {
return 0;
}
}
});
// Find the aspect-matching config with the closest matching display size
DisplayMode bestConfig = null;
for (DisplayMode config : aspectMatchingConfigs) {
if (config.getWidth()*config.getHeight() >= targetDisplaySize) {
bestConfig = config;
}
}
if (bestConfig != null) {
System.out.println("Using full-screen display mode "+bestConfig.getWidth()+"x"+bestConfig.getHeight()+
" for "+targetConfig.getWidth()+"x"+targetConfig.getHeight()+" stream");
}
return bestConfig;
}
private void makeFullScreen(StreamConfiguration streamConfig) {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
if (gd.isFullScreenSupported()) {
this.setUndecorated(true);
gd.setFullScreenWindow(this);
if (gd.isDisplayChangeSupported()) {
DisplayMode config = getBestDisplay(streamConfig, gd.getDisplayModes());
if (config != null) {
gd.setDisplayMode(config);
}
} else {
JOptionPane.showMessageDialog(
this,
"Unable to change display resolution. \nThis may not be the correct resolution",
"Display Resolution",
JOptionPane.ERROR_MESSAGE);
}
} else {
JOptionPane.showMessageDialog(
this,
"Your operating system does not support fullscreen.",
"Fullscreen Unsupported",
JOptionPane.ERROR_MESSAGE);
}
}
/**
* Makes the mouse cursor invisible
*/
public void hideCursor() {
if (noCursor == null) {
// Transparent 16 x 16 pixel cursor image.
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
noCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
}
// Set the blank cursor to the JFrame.
this.setCursor(noCursor);
this.getContentPane().setCursor(noCursor);
}
/**
* Makes the mouse cursor visible
*/
public void showCursor() {
this.setCursor(Cursor.getDefaultCursor());
this.getContentPane().setCursor(Cursor.getDefaultCursor());
}
/**
* Shows a progress bar with a label underneath that tells the user what
* loading stage the stream is at.
* @param stage the currently loading stage
*/
public void showSpinner(Stage stage) {
if (spinner == null) {
Container c = this.getContentPane();
JPanel panel = new JPanel();
panel.setBackground(Color.BLACK);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
spinner = new JProgressBar();
spinner.setIndeterminate(true);
spinner.setMaximumSize(new Dimension(150, 30));
spinnerLabel = new JLabel();
spinnerLabel.setForeground(Color.white);
Box spinBox = Box.createHorizontalBox();
spinBox.add(Box.createHorizontalGlue());
spinBox.add(spinner);
spinBox.add(Box.createHorizontalGlue());
Box lblBox = Box.createHorizontalBox();
lblBox.add(Box.createHorizontalGlue());
lblBox.add(spinnerLabel);
lblBox.add(Box.createHorizontalGlue());
panel.add(Box.createVerticalGlue());
panel.add(spinBox);
panel.add(Box.createVerticalStrut(10));
panel.add(lblBox);
panel.add(Box.createVerticalGlue());
c.setLayout(new BorderLayout());
c.add(panel, "Center");
}
spinnerLabel.setText("Starting " + stage.getName() + "...");
}
/**
* Creates the listener for the window.
* It terminates the connection when the window is closed
*/
private WindowListener createWindowListener() {
return new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
close();
}
};
}
/**
* Hides the spinner and the label
*/
public void hideSpinner() {
spinner.setVisible(false);
spinnerLabel.setVisible(false);
}
/**
* Stops the stream and destroys the frame
*/
public void close() {
limelight.stop();
dispose();
if (Limelight.COMMAND_LINE_LAUNCH) {
System.exit(0);
}
}
}