diff --git a/src/java/org/lwjgl/opengl/XRandR.java b/src/java/org/lwjgl/opengl/XRandR.java index 1f7fbf8d..2dfcf197 100644 --- a/src/java/org/lwjgl/opengl/XRandR.java +++ b/src/java/org/lwjgl/opengl/XRandR.java @@ -27,17 +27,14 @@ package org.lwjgl.opengl; +import org.lwjgl.LWJGLUtil; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.lwjgl.LWJGLUtil; /** * Utility for working with the xrandr commmand-line utility. Assumes @@ -45,384 +42,337 @@ import org.lwjgl.LWJGLUtil; * * @author ryanm */ -public class XRandR -{ +public class XRandR { + private static Screen[] current; - /** - * Either the screen marked as "primary" (if it is turned on) - * or the one with the largest (current) resolution. - */ - private static String primaryScreenIdentifier; + /** + * Either the screen marked as "primary" (if it is turned on) + * or the one with the largest (current) resolution. + */ + private static String primaryScreenIdentifier; - /** - * Used to save the configuration of all output devices to - * restore it on exit or in case of crash. - */ - private static Screen[] savedConfiguration; + /** + * Used to save the configuration of all output devices to + * restore it on exit or in case of crash. + */ + private static Screen[] savedConfiguration; private static Map screens; - private static void populate() - { - if( screens == null ) - { - screens = new HashMap(); + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - // ProcessBuilder pb = new ProcessBuilder( "xrandr", "-q" ); - // pb.redirectErrorStream(); - try - { - // Process p= pb.start(); - Process p = Runtime.getRuntime().exec( new String[] { "xrandr", "-q" } ); + private static void populate() { + if ( screens != null ) + return; - List currentList = new ArrayList(); - List possibles = new ArrayList(); - String name = null; - // saves the position of the current screen. this is specified in the header of the screen block, - // but required later when parsing the screen modelines - int[] currentScreenPosition = new int[2]; + screens = new HashMap(); - BufferedReader br = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); - String line; - while( ( line = br.readLine() ) != null ) - { - line = line.trim(); - String[] sa = line.split( "\\s+" ); + try { + Process p = Runtime.getRuntime().exec(new String[] { "xrandr", "-q" }); - if( "connected".equals(sa[1]) ) - { - // found a new screen block - if( name != null ) - { - screens.put( name, possibles.toArray( new Screen[ possibles.size() ] ) ); - possibles.clear(); - } - name = sa[ 0 ]; + List currentList = new ArrayList(); + List possibles = new ArrayList(); + String name = null; + // saves the position of the current screen. this is specified in the header of the screen block, + // but required later when parsing the screen modelines + int[] currentScreenPosition = new int[2]; - // save position of this screen, will be used later when current modeline is parsed - if ("primary".equals(sa[ 2 ])) { - parseScreenHeader(currentScreenPosition, sa[ 3 ]); - // save primary - primaryScreenIdentifier = name; - } else { - parseScreenHeader(currentScreenPosition, sa[ 2 ]); - } + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ( (line = br.readLine()) != null ) { + line = line.trim(); + String[] sa = WHITESPACE_PATTERN.split(line); + + if ( "connected".equals(sa[1]) ) { + // found a new screen block + if ( name != null ) { + screens.put(name, possibles.toArray(new Screen[possibles.size()])); + possibles.clear(); } - else if( Pattern.matches( "\\d*x\\d*", sa[ 0 ] ) ) - { + name = sa[0]; + + // save position of this screen, will be used later when current modeline is parsed + if ( "primary".equals(sa[2]) ) { + parseScreenHeader(currentScreenPosition, sa[3]); + // save primary + primaryScreenIdentifier = name; + } else { + parseScreenHeader(currentScreenPosition, sa[2]); + } + } else { + Matcher m = SCREEN_MODELINE_PATTERN.matcher(sa[0]); + if ( m.matches() ) { // found a new mode line - parseScreenModeline( possibles, currentList, name, sa[ 0 ], Arrays.copyOfRange(sa, 1, sa.length), currentScreenPosition); + parseScreenModeline( + possibles, currentList, name, + Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), + sa, currentScreenPosition + ); } } - - screens.put( name, possibles.toArray( new Screen[ possibles.size() ] ) ); - - current = currentList.toArray(new Screen[currentList.size()]); - - // set primary to largest screen if not set yet - if (primaryScreenIdentifier == null) { - long totalPixels = Long.MIN_VALUE; - for (Screen screen : current) { - if (1l * screen.width * screen.height > totalPixels) { - primaryScreenIdentifier = screen.name; - totalPixels = 1l * screen.width * screen.height; - } - } - } } - catch( Throwable e ) - { - LWJGLUtil.log( "Exception in XRandR.populate(): " + e.getMessage() ); - screens.clear(); - current = new Screen[ 0 ]; + + screens.put(name, possibles.toArray(new Screen[possibles.size()])); + + current = currentList.toArray(new Screen[currentList.size()]); + + // set primary to largest screen if not set yet + if ( primaryScreenIdentifier == null ) { + long totalPixels = Long.MIN_VALUE; + for ( Screen screen : current ) { + if ( 1l * screen.width * screen.height > totalPixels ) { + primaryScreenIdentifier = screen.name; + totalPixels = 1l * screen.width * screen.height; + } + } } + } catch (Throwable e) { + LWJGLUtil.log("Exception in XRandR.populate(): " + e.getMessage()); + screens.clear(); + current = new Screen[0]; } } /** * @return The current screen configuration of the primary device, - * or an empty array if xrandr is not supported + * or an empty array if xrandr is not supported */ - public static Screen[] getConfiguration() - { + public static Screen[] getConfiguration() { populate(); - // find and return primary - for (Screen screen : current) { - if (screen.name.equals(primaryScreenIdentifier)) { - return new Screen[]{screen}; - } - } + // find and return primary + for ( Screen screen : current ) { + if ( screen.name.equals(primaryScreenIdentifier) ) { + return new Screen[] { screen }; + } + } - // problem with primary device, fall back to old behaviour + // problem with primary device, fall back to old behaviour return current.clone(); } /** - * @param disableOthers - * if screens not included in screens should be turned off (true) or left alone (false) - * @param screens - * The desired screen set, may not be null - * @throws IllegalArgumentException - * if no screens are specified + * @param disableOthers if screens not included in screens should be turned off (true) or left alone (false) + * @param screens The desired screen set, may not be null + * + * @throws IllegalArgumentException if no screens are specified */ - public static void setConfiguration(boolean disableOthers, Screen... screens) - { - if( screens.length == 0 ) - throw new IllegalArgumentException( "Must specify at least one screen" ); + public static void setConfiguration(boolean disableOthers, Screen... screens) { + if ( screens.length == 0 ) + throw new IllegalArgumentException("Must specify at least one screen"); List cmd = new ArrayList(); - cmd.add( "xrandr" ); + cmd.add("xrandr"); - if (disableOthers) { - // switch off those in the current set not in the new set - for ( Screen screen : current ) { - boolean found = false; - for ( Screen screen1 : screens ) { - if ( screen1.name.equals(screen.name) ) { - found = true; - break; - } - } + if ( disableOthers ) { + // switch off those in the current set not in the new set + for ( Screen screen : current ) { + boolean disable = true; + for ( Screen screen1 : screens ) { + if ( screen1.name.equals(screen.name) ) { + disable = false; + break; + } + } - if ( !found ) { - cmd.add("--output"); - cmd.add(screen.name); - cmd.add("--off"); - } - } - } + if ( disable ) { + cmd.add("--output"); + cmd.add(screen.name); + cmd.add("--off"); + } + } + } // set up new set for ( Screen screen : screens ) screen.getArgs(cmd); - try - { - // ProcessBuilder pb = new ProcessBuilder( cmd ); - // pb.redirectErrorStream(); - // Process p = pb.start(); - Process p = - Runtime.getRuntime().exec( cmd.toArray( new String[ cmd.size() ] ) ); + try { + Process p = Runtime.getRuntime().exec(cmd.toArray(new String[cmd.size()])); // no output is expected, but check anyway - BufferedReader br = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; - while( ( line = br.readLine() ) != null ) - { - LWJGLUtil.log( "Unexpected output from xrandr process: " + line ); + while ( (line = br.readLine()) != null ) { + LWJGLUtil.log("Unexpected output from xrandr process: " + line); } current = screens; - } - catch( IOException e ) - { - LWJGLUtil.log( "XRandR exception in setConfiguration(): " + e.getMessage() ); + } catch (IOException e) { + LWJGLUtil.log("XRandR exception in setConfiguration(): " + e.getMessage()); } } - /** - * Saves the current configuration for all connected display devices. - * This configuration can be restored on exit/crash by calling - * restoreConfiguration() - */ - public static void saveConfiguration() { - populate(); - savedConfiguration = current.clone(); - } + /** + * Saves the current configuration for all connected display devices. + * This configuration can be restored on exit/crash by calling + * restoreConfiguration() + */ + public static void saveConfiguration() { + populate(); + savedConfiguration = current.clone(); + } - /** - * Restores the configuration for all connected display devices. - * Used on exit or in case of a crash to reset all devices. - */ - public static void restoreConfiguration() { - if (savedConfiguration != null) { - setConfiguration(true, savedConfiguration); - } - } + /** + * Restores the configuration for all connected display devices. + * Used on exit or in case of a crash to reset all devices. + */ + public static void restoreConfiguration() { + if ( savedConfiguration != null ) { + setConfiguration(true, savedConfiguration); + } + } /** * @return the name of connected screens, or an empty array if - * xrandr is not supported + * xrandr is not supported */ - public static String[] getScreenNames() - { + public static String[] getScreenNames() { populate(); - return screens.keySet().toArray( new String[ screens.size() ] ); + return screens.keySet().toArray(new String[screens.size()]); } /** * @param name + * * @return the possible resolutions of the named screen, or - * null if there is no such screen + * null if there is no such screen */ - public static Screen[] getResolutions( String name ) - { + public static Screen[] getResolutions(String name) { populate(); // clone the array to prevent held copies being altered return screens.get(name).clone(); } - private static final Pattern SCREEN_HEADER_PATTERN = - Pattern.compile( "^(\\d+)x(\\d+)\\+(\\d+)\\+(\\d+)$" ); + private static final Pattern SCREEN_HEADER_PATTERN = Pattern.compile("^(\\d+)x(\\d+)[+](\\d+)[+](\\d+)$"); + private static final Pattern SCREEN_MODELINE_PATTERN = Pattern.compile("^(\\d+)x(\\d+)$"); + private static final Pattern FREQ_PATTERN = Pattern.compile("^(\\d+)[.](\\d+)(?:\\s*[*])?(?:\\s*[+])?$"); - private static final Pattern SCREEN_MODELINE_PATTERN = Pattern.compile( "^(\\d+)x(\\d+)$" ); - - private static final Pattern FREQ_PATTERN = Pattern.compile("^(\\d+).(\\d+)[\\*,\\s]?\\+?$"); - - /** + /** * Parses a screen configuration and adds it to one of the lists if valid. * - * @param allModes - * the list to add the Screen to if it's valid - * @param current - * the list to add the current screen config to - * @param name - * the name of this screen - * @param res - * config string, format widthxheight - * @param freqs - * config strings, frequency as float, with optional * and + - * @param screenPosition - * position of this screen, null defaults to 0,0 + * @param allModes the list to add the Screen to if it's valid + * @param current the list to add the current screen config to + * @param name the name of this screen + * @param modeLine config string + * @param screenPosition position of this screen */ - private static void parseScreenModeline( List allModes, List current, String name, String res, String[] freqs, int[] screenPosition) - { - Matcher m = SCREEN_MODELINE_PATTERN.matcher( res ); - if( !m.matches() ) - { - LWJGLUtil.log( "Did not match: " + res ); - return; - } - int width = Integer.parseInt( m.group( 1 ) ); - int height = Integer.parseInt( m.group( 2 ) ); - int xpos = screenPosition[0]; - int ypos = screenPosition[1]; + private static void parseScreenModeline(List allModes, List current, String name, int width, int height, String[] modeLine, int[] screenPosition) { + for ( int i = 1; i < modeLine.length; i++ ) { + String freqS = modeLine[i]; + if ( "+".equals(freqS) ) { + // previous rate was the "preferred" refresh rate + // no way to get this info to the application, so ignore it + continue; + } - for (String freqS : freqs) { - if ("+".equals(freqS)) { - // previous rate was the "preferred" refresh rate - // no way to get this info to the application, so ignore it - continue; - } - m = FREQ_PATTERN.matcher(freqS); - if( !m.matches() ) - { - LWJGLUtil.log( "Did not match: " + res ); - return; - } - int freq = Integer.parseInt(m.group(1)); - if (freqS.contains("*")) { - // current mode, save to current list with screen position - current.add( new Screen( name, width, height, freq, xpos, ypos ) ); - } - // always add to List of all modes without screen position - allModes.add( new Screen( name, width, height, freq, 0, 0 ) ); - } + Matcher m = FREQ_PATTERN.matcher(freqS); + if ( !m.matches() ) { + LWJGLUtil.log("Frequency match failed: " + Arrays.toString(modeLine)); + return; + } + + int freq = Integer.parseInt(m.group(1)); + + Screen s = new Screen(name, width, height, freq, 0, 0); + if ( freqS.contains("*") ) { + // current mode, save to current list with screen position + current.add(new Screen(name, width, height, freq, screenPosition[0], screenPosition[1])); + // make sure the current mode is always first + allModes.add(0, s); + } else { + // always add to List of all modes without screen position + allModes.add(s); + } + } } - /** - * Parses a screen configuration header and extracts information about the position of the screen. - * - * @param screenPosition the int-array to write the position into - * @param resPos String containing resolution and position, from xrandr - */ - private static void parseScreenHeader(int[] screenPosition, String resPos) { - Matcher m = SCREEN_HEADER_PATTERN.matcher(resPos); - if (!m.matches()) { - // screen not active! - screenPosition[0] = 0; - screenPosition[1] = 0; - return; - } - screenPosition[0] = Integer.parseInt(m.group(3)); - screenPosition[1] = Integer.parseInt(m.group(4)); - } + /** + * Parses a screen configuration header and extracts information about the position of the screen. + * + * @param screenPosition the int-array to write the position into + * @param resPos String containing resolution and position, from xrandr + */ + private static void parseScreenHeader(int[] screenPosition, String resPos) { + Matcher m = SCREEN_HEADER_PATTERN.matcher(resPos); + if ( !m.matches() ) { + // screen not active! + screenPosition[0] = 0; + screenPosition[1] = 0; + return; + } + screenPosition[0] = Integer.parseInt(m.group(3)); + screenPosition[1] = Integer.parseInt(m.group(4)); + } - static Screen DisplayModetoScreen(DisplayMode mode) { - populate(); - Screen primary = findPrimary(current); - return new Screen(primary.name, mode.getWidth(), mode.getHeight(), mode.getFrequency(), primary.xPos, primary.yPos); - } + static Screen DisplayModetoScreen(DisplayMode mode) { + populate(); + Screen primary = findPrimary(current); + return new Screen(primary.name, mode.getWidth(), mode.getHeight(), mode.getFrequency(), primary.xPos, primary.yPos); + } - static DisplayMode ScreentoDisplayMode(Screen... screens) { - populate(); - Screen primary = findPrimary(screens); - return new DisplayMode(primary.width, primary.height, 24, primary.freq); - } + static DisplayMode ScreentoDisplayMode(Screen... screens) { + populate(); + Screen primary = findPrimary(screens); + return new DisplayMode(primary.width, primary.height, 24, primary.freq); + } - private static Screen findPrimary(Screen... screens) { - for (Screen screen : screens) { - if (screen.name.equals(primaryScreenIdentifier)) { - return screen; - } - } - // fallback - return screens[0]; - } + private static Screen findPrimary(Screen... screens) { + for ( Screen screen : screens ) { + if ( screen.name.equals(primaryScreenIdentifier) ) { + return screen; + } + } + // fallback + return screens[0]; + } /** * Encapsulates the configuration of a monitor. - * Resolution and freq are fixed, position is mutable + * Resolution and freq are fixed, position is mutable * * @author ryanm */ - public static class Screen implements Cloneable - { - /** - * Name for this output - */ + public static class Screen implements Cloneable { + /** Name for this output */ public final String name; - /** - * Width in pixels - */ + /** Width in pixels */ public final int width; - /** - * Height in pixels - */ + /** Height in pixels */ public final int height; - /** - * Frequency in Hz - */ - public final int freq; + /** Frequency in Hz */ + public final int freq; - /** - * Position on the x-axis, in pixels - */ + /** Position on the x-axis, in pixels */ public int xPos; - /** - * Position on the y-axis, in pixels - */ + /** Position on the y-axis, in pixels */ public int yPos; - Screen( String name, int width, int height, int freq, int xPos, int yPos ) - { + Screen(String name, int width, int height, int freq, int xPos, int yPos) { this.name = name; this.width = width; this.height = height; - this.freq = freq; + this.freq = freq; this.xPos = xPos; this.yPos = yPos; } - private void getArgs( List argList ) - { - argList.add( "--output" ); - argList.add( name ); - argList.add( "--mode" ); - argList.add( width + "x" + height ); - argList.add( "--rate" ); - argList.add( freq + "");//"" autoboxes freq as String - argList.add( "--pos" ); - argList.add( xPos + "x" + yPos ); + private void getArgs(List argList) { + argList.add("--output"); + argList.add(name); + argList.add("--mode"); + argList.add(width + "x" + height); + argList.add("--rate"); + argList.add(Integer.toString(freq)); + argList.add("--pos"); + argList.add(xPos + "x" + yPos); } //@Override - public String toString() - { + public String toString() { return name + " " + width + "x" + height + " @ " + xPos + "x" + yPos + " with " + freq + "Hz"; } }