/* * Copyright (c) 2002-2010 LWJGL Project All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * Neither the name of 'LWJGL' nor the names * of its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.lwjgl.opengl; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.lwjgl.LWJGLUtil; /** * Utility for working with the xrandr commmand-line utility. Assumes * xrandr v1.2 or higher. * * @author ryanm */ public class XRandR { private static Screen[] current; private static Map<String, Screen[]> screens; private static void populate() { if( screens == null ) { screens = new HashMap<String, Screen[]>(); // ProcessBuilder pb = new ProcessBuilder( "xrandr", "-q" ); // pb.redirectErrorStream(); try { // Process p= pb.start(); Process p = Runtime.getRuntime().exec( new String[] { "xrandr", "-q" } ); List<Screen> currentList = new ArrayList<Screen>(); List<Screen> possibles = new ArrayList<Screen>(); String name = null; BufferedReader br = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); String line; while( ( line = br.readLine() ) != null ) { line = line.trim(); String[] sa = line.split( "\\s+" ); 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 ]; // record the current config parseScreen( currentList, name, sa[ 2 ] ); } else if( Pattern.matches( "\\d*x\\d*", sa[ 0 ] ) ) { // found a new mode line parseScreen( possibles, name, sa[ 0 ] ); } } screens.put( name, possibles.toArray( new Screen[ possibles.size() ] ) ); current = currentList.toArray(new Screen[currentList.size()]); } catch( Throwable e ) { LWJGLUtil.log( "Exception in XRandR.populate(): " + e.getMessage() ); screens.clear(); current = new Screen[ 0 ]; } } } /** * @return The current screen configuration, or an empty array if * xrandr is not supported */ public static Screen[] getConfiguration() { populate(); return current.clone(); } /** * @param screens * The desired screen set, may not be <code>null</code> * @throws IllegalArgumentException * if no screens are specified */ public static void setConfiguration(Screen... screens) { if( screens.length == 0 ) throw new IllegalArgumentException( "Must specify at least one screen" ); List<String> cmd = new ArrayList<String>(); cmd.add( "xrandr" ); // 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 ( !found ) { 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() ] ) ); // no output is expected, but check anyway BufferedReader br = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); String 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() ); } } /** * @return the name of connected screens, or an empty array if * xrandr is not supported */ public static String[] getScreenNames() { populate(); return screens.keySet().toArray( new String[ screens.size() ] ); } /** * @param name * @return the possible resolutions of the named screen, or * <code>null</code> if there is no such screen */ 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_PATTERN1 = Pattern.compile( "^(\\d+)x(\\d+)\\+(\\d+)\\+(\\d+)$" ); private static final Pattern SCREEN_PATTERN2 = Pattern.compile( "^(\\d+)x(\\d+)$" ); /** * Parses a screen configuration and adds it to the list if it's * valid. * * @param list * the list to add the Screen to if it's valid * @param name * the name of this screen * @param what * config string, format either widthxheight or * widthxheight+xPos+yPos */ private static void parseScreen( List<Screen> list, String name, String what ) { Matcher m = SCREEN_PATTERN1.matcher( what ); if( !m.matches() ) { m = SCREEN_PATTERN2.matcher( what ); if( !m.matches() ) { LWJGLUtil.log( "Did not match: " + what ); return; } } int width = Integer.parseInt( m.group( 1 ) ); int height = Integer.parseInt( m.group( 2 ) ); int xpos, ypos; if( m.groupCount() > 3 ) { xpos = Integer.parseInt( m.group( 3 ) ); ypos = Integer.parseInt( m.group( 4 ) ); } else { xpos = 0; ypos = 0; } list.add( new Screen( name, width, height, xpos, ypos ) ); } /** * Encapsulates the configuration of a monitor. Resolution is * fixed, position is mutable * * @author ryanm */ public static class Screen implements Cloneable { /** * Name for this output */ public final String name; /** * Width in pixels */ public final int width; /** * Height in pixels */ public final int height; /** * Position on the x-axis, in pixels */ public int xPos; /** * Position on the y-axis, in pixels */ public int yPos; private Screen( String name, int width, int height, int xPos, int yPos ) { this.name = name; this.width = width; this.height = height; this.xPos = xPos; this.yPos = yPos; } private void getArgs( List<String> argList ) { argList.add( "--output" ); argList.add( name ); argList.add( "--mode" ); argList.add( width + "x" + height ); argList.add( "--pos" ); argList.add( xPos + "x" + yPos ); } //@Override public String toString() { return name + " " + width + "x" + height + " @ " + xPos + "x" + yPos; } } }