Javaで表示するスクリーンを選ぶ

大きい方のディスプレイに表示させるには

諸事情でメインのディスプレイをセカンダリに、小さなディスプレイをプライマリに設定して使うことにした。

久しぶりに画像を表示する自作のJavaプログラムを起動すると、小さなディスプレイではうまく動かない。この不具合にも対策が必要だが、どちらにしても、大きい方のディスプレイを使うようにプログラムしたい。

環境を再び替えることもあろうから、セカンダリと決めてかかるのはよくない。今回の問題は高さだったので、縦の解像度の大きい方ということにした。

概要は

  1. Graphics­Environment#get­Screen­Devices()でGraphics­Deviceの配列を得る。
  2. Graphics­Device#get­Default­Configuration()で、各DeviceでデフォルトのGraphics­Configurationを得る
  3. 適当な方法でGraphics­Configurationを選択する
  4. JFrame frame = new JFrame(Graphics­Configuration gc);でフレームを生成する

以上についてプログラムを紹介する

追記

JFrame#setLocationByPlatform(true);を指定しておくことで、javaプログラムの起動端末の表示されているスクリーンが自動的に選択されるらしいとわかった。デフォルトのスクリーン内でのおまかせ配置だと思っていた。ずっと昔にOSによっては動かなかったり、配置が気に入らなかったりしてずっと使っていなかったので、あまり期待をしていなかったというので発見が遅れた。きっかけとなったプログラムにこれが書いてあったら、今回の調査に至らなかったが、やれることが広がったので良しとする。

デバイスを得る

GraphicsEnvironment#getScreenDevices()でGraphicsDeviceの配列を得る。

GraphicsEnvironment#get­Default­Screen­Device()でデフォルトのGraphicsDeviceを得る。

ScreenTest00.java
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
public class ScreenTest00{
    public static void main (String [] args) {
        //GG java.awt.GraphicsDevice
        GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        System.out.println("Env:"+ genv.toString());
        GraphicsDevice[] gdevs = genv.getScreenDevices();
        System.out.println(gdevs.length + " ScreenDevices found.");
        for(GraphicsDevice gdev: gdevs){
            System.out.println(gdev.toString());
          }
        GraphicsDevice ddev = genv.getDefaultScreenDevice();
        System.out.println("Default Device is : "+ ddev.toString() );
    }
}

GraphicsDeviceは、画面、プリンタ、またはイメージ・バッファの場合があると書いてあるが、画面だけ、つまり、Screen0,1が得られる。

デフォルトは0である。

adachi@debian64:/media/adachi/S1T/m06/jpgseldlg$ java ScreenTest00
Env:sun.awt.X11GraphicsEnvironment@7ba4f24f
2 ScreenDevices found.
X11GraphicsDevice[screen=0]
X11GraphicsDevice[screen=1]
Default Device is : X11GraphicsDevice[screen=0]

デバイスからGraphicsConfiguration

各GraphicsDeviceは、それに関連したいくつかのGraphicsConfigurationオブジェクトを持つということなので、調べる。

GraphicsDevice#getConfigurations()でGraphicsConfigurationの配列を得る

GraphicsDevice#getDefaultConfiguration()でデフォルトのGraphicsConfigurationを得る

ScreenTest01.java
import java.awt.Point;
import java.awt.Rectangle;
import java.util.TreeMap;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
public class ScreenTest01 {
    public static void main (String [] args) {
        GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gdevs = genv.getScreenDevices();
        System.out.println(gdevs.length + " ScreenDevices found.");

        TreeMap gdevmap = new TreeMap<>();

        Rectangle virtualBounds = new Rectangle();
        for(GraphicsDevice gdev: gdevs){
            System.out.println(gdev.toString());
            GraphicsConfiguration[] gcnfs = gdev.getConfigurations();
            System.out.println(gcnfs.length + " Configurations");
            int ct = 0;
            for(GraphicsConfiguration gcnf: gcnfs){
                ct++;
                Rectangle rectn = gcnf.getBounds();
                System.out.println("Config:"+ct+":"+gcnf.toString() );
                System.out.println("  bounds:"+ rectn.toString() );
                if (ct>4) {
                    System.out.println("....");
                    break;
                }//if
            }//for
            GraphicsConfiguration dcnf = gdev.getDefaultConfiguration();
            Rectangle drectn = dcnf.getBounds();
            System.out.println("Config:D:"+ dcnf.toString() );
            System.out.println("  bBounds:"+ drectn.toString() );
            virtualBounds = virtualBounds.union(drectn);
            gdevmap.put(drectn.getHeight(),dcnf);
        }
        System.out.println("VBounds:"+ virtualBounds.toString() );
        System.out.println("lastConf:"+ gdevmap.lastEntry().getValue());

        Point p = genv.getCenterPoint();
        Rectangle rec = genv.getMaximumWindowBounds();
        System.out.println(p);
        System.out.println(rec);
    }
}

Deviceごとに240のConfigurationがある。この出力ではvisの値だけが異なる。5番目まで表示させているがこの後も同じ。

adachi@debian64:/media/adachi/S1T/m06/jpgseldlg$ java ScreenTest01
2 ScreenDevices found.
X11GraphicsDevice[screen=0]
240 Configurations
Config:1:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x21]
  bounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
Config:2:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x2c5]
  bounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
Config:3:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x2c6]
  bounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
Config:4:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x2c7]
  bounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
Config:5:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x2c8]
  bounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
....
Config:D:X11GraphicsConfig[dev=X11GraphicsDevice[screen=0],vis=0x21]
  bBounds:java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]
X11GraphicsDevice[screen=1]
240 Configurations
Config:1:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x21]
  bounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
Config:2:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x2c5]
  bounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
Config:3:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x2c6]
  bounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
Config:4:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x2c7]
  bounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
Config:5:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x2c8]
  bounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
....
Config:D:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x21]
  bBounds:java.awt.Rectangle[x=0,y=0,width=1920,height=1200]
VBounds:java.awt.Rectangle[x=0,y=0,width=3200,height=1200]
lastConf:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x21]
java.awt.Point[x=2560,y=688]
java.awt.Rectangle[x=1920,y=176,width=1280,height=1024]

API仕様には各Confを使ってJFrameオブジェクトを生成する例が出ている。

どれも同じなので、それぞれのデバイスのデフォルトのConfだけを使って、TreeMapでheightが最大なもの(last)を得ている。

virtualBounds.union(drectn)で、合成された仮想スクリーンを得ている

合成[x=0,y=0,width=3200,height=1200]
dev1[x=0,y=0,width=1920,height=1200],dev2[x=1920,y=176,width=1280,height=1024]

最後は、Graphics­Environment­#get­Center­Point()とGraphics­Environment­#get­Maximum­Window­Bounds()だが、仮想デバイスのセンターではなく、仮想デバイスの座標で見たデフォルトデバイスのセンターの座標と、最大のサイズということらしい。このプログラムで出てくる値はすべて仮想デバイスの座標だけれども。

実際にJFrameを表示する

ScreenTest02.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
public class ScreenTest02 {
    public ScreenTest02(){
        //GG java.awt.GraphicsDevice
        GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gdevs = genv.getScreenDevices();
        TreeMap gdevmap = new TreeMap<>();
        for(GraphicsDevice gdev: gdevs){
            GraphicsConfiguration dcnf = gdev.getDefaultConfiguration();
            Rectangle drectn = dcnf.getBounds();
            System.out.println("["+gdev.getIDstring()+"]"+
                "["+drectn.getX()+","+drectn.getX()+","+drectn.getWidth()+","+drectn.getHeight()+"]");
            gdevmap.put(drectn.getHeight(),dcnf);
        }
        GraphicsConfiguration selectedConf = gdevmap.lastEntry().getValue();
        System.out.println("lastConf:"+ selectedConf);
        JFrame frame = new JFrame(selectedConf);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLocationByPlatform(true);
        //frame.pack();
        frame.setVisible(true);
    }
    public static void main (String [] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ScreenTest02();
            }
        });
    }
}

これで空のJFrameが1920x1200の方へ表示される。

adachi@debian64:/media/adachi/S1T/m06/jpgseldlg$ java ScreenTest02
[:0.0][1920.0,1920.0,1280.0,1024.0]
[:0.1][0.0,0.0,1920.0,1200.0]
lastConf:X11GraphicsConfig[dev=X11GraphicsDevice[screen=1],vis=0x21]

クリックでスクリーン間の移動をするプログラム

複数あるスクリーン間をJFrameが移動するプログラムを作ってみる。

表示後は仮想スクリーン内の座標になるので、GraphicsConfigurationで切り替えるのではなく、単なる座標指定となる。

今回の例では、1280x1024と1920x1200が合成されて、3200x1200のスクリーンになっている。

ScreenTest03.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
public class ScreenTest03 implements ActionListener {
    java.util.List gclist;
    int gcn;
    JTextArea textarea;
    JFrame frame;
    public ScreenTest03(){
        GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gdevs = genv.getScreenDevices();
        TreeMap gdevmap = new TreeMap<>();
        for(GraphicsDevice gdev: gdevs){
            GraphicsConfiguration dcnf = gdev.getDefaultConfiguration();
            Rectangle drectn = dcnf.getBounds();
            System.out.println("["+gdev.getIDstring()+"]"+
                "["+drectn.getX()+","+drectn.getY()+
                ","+drectn.getWidth()+","+drectn.getHeight()+"]");
            gdevmap.put(drectn.getHeight(),dcnf);
        }
        //GraphicsConfiguration[] gcs = gdevmap.values().toArray(new GraphicsConfiguration[0]);
        //List gclist = new LinkedList(gdevmap.values());
        gclist = new LinkedList<>(gdevmap.values());
        gcn = gclist.size()-1;
        GraphicsConfiguration selectedConf = gclist.get(gcn);
        System.out.println("lastConf:"+ selectedConf);
        frame = new JFrame(selectedConf);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setLocationByPlatform(true);
        textarea =  new JTextArea();
        textarea.setLineWrap(true);  //自動的に改行するように
        JScrollPane scrollpane = new JScrollPane(textarea);
        scrollpane.setPreferredSize(new Dimension(400, 200));

        JButton mvbttn = new JButton("move");
        mvbttn.setActionCommand("move");
        mvbttn.addActionListener(this);
        JButton endbttn = new JButton("終了");
        endbttn.setActionCommand("quit");
        endbttn.addActionListener(this);
        frame.add(mvbttn,BorderLayout.NORTH);
        frame.add(scrollpane,BorderLayout.CENTER);
        frame.add(endbttn,BorderLayout.SOUTH);
        frame.setVisible(true);
        System.out.println("location:"+ frame.getX() + ","+ frame.getY() );
        System.out.println("location:"+ frame.getLocationOnScreen() );
        System.out.println("location:"+ frame.getLocation() );
    }
    public void actionPerformed( ActionEvent e ){
        if ("quit".equals(e.getActionCommand())) {
           System.exit(0);
        }else if ("move".equals(e.getActionCommand())) {
            textarea.append(frame.getX() + ","+ frame.getY()+"\n");
            //textarea.append("locationOns:"+ frame.getLocationOnScreen()+"\n" );
            //textarea.append("location:"+ frame.getLocation() +"\n");
            GraphicsConfiguration gc = gclist.get(gcn);
            Rectangle rect = gc.getBounds();
            int dx = frame.getX()-rect.x; //Rectangle#getX()はdouble
            int dy = frame.getY()-rect.y;
            textarea.append(rect.x + "," + dx +"," + rect.y + "," + dy+"\n");
            gcn=++gcn%gclist.size();
            gc = gclist.get(gcn);
            rect = gc.getBounds();
            frame.setLocation(rect.x+dx,rect.y+dy);
        }
    }

    public static void main (String [] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ScreenTest03();
            }
        });
    }
}

actionPerformed()内で、個別スクリーンの左上の座標を求めて、現在の位置から引いて、個別スクリーン内の位置をdx,dyに格納。その後次のGraphicsConfigurationから移動先のスクリーンの左上座標を求め、dx,dyを足して移動先とする。

JFrame#getLocationOnScreen()もJFrame#getLocation()も変わりない。

スクリーンを移動するフレーム