mirror of
https://github.com/moonlight-stream/moonlight-embedded.git
synced 2026-02-16 02:20:42 +00:00
Convert Limelight to command line application
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user