Edited at
NIJIBOXDay 1

xrandrコマンドの出力結果をもとに、よしなにモニターのミラーリングをする

春に買い替えた個人ノートPCで、Arch Linuxを使ってます。

(ゆっくりと慣らしているので、まだ使いこなせてるとは言い難い

外で何かしらの発表する機会がちょこちょこ増えてきたり、モニター買い替えて積極的に使うようになってきたのですが、そうなると面倒なのがモニターの設定。

自分はxrandrコマンドを使っているのですが、いろいろと引数を考えるのが面倒になってきたので、xrandrコマンドの出力を加工して、都度いい感じにするxonshコマンドにしてみました。


TL;DR



  • xrandrの出力を元に、いい感じに出力設定を変えるコマンドを作ってみた


  • xonshだとオブジェクトやPython制御構造を使えて良さげ


コードに関する前提



  • xonsh上で使うことを前提としたコードのため、途中からのコードがPythonのような何かになっていることがあります

  • 一旦、今使っているコードをなるべくそのまま載せます。そのため、オーバースペック感があります


xrandrのコマンド


情報表示だけするケース

何のオプションもなく使うと、次のものがわかります。


  • 使えるディスプレイ(出力先)

  • 出力先の接続有無

  • 接続している出力先で使える画面モード

何も接続していないと、こんな感じになります。

Screen 0: minimum 320 x 200, current 2560 x 1440, maximum 8192 x 8192

eDP-1 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
2560x1440 60.00*+ 59.99 59.99 59.96 59.95
1920x1440 60.00
1856x1392 60.01
1792x1344 60.01
2048x1152 59.99 59.98 59.90 59.91
1920x1200 59.88 59.95
1920x1080 60.01 59.97 59.96 59.93
1600x1200 60.00
1680x1050 59.95 59.88
1400x1050 59.98
1600x900 59.99 59.94 59.95 59.82
# モード数がそこそこ多くて長くなったので、略します
320x240 60.05
360x202 59.51 59.13
320x180 59.84 59.32
DP-1 disconnected (normal left inverted right x axis y axis)
HDMI-1 disconnected (normal left inverted right x axis y axis)
DP-2 disconnected (normal left inverted right x axis y axis)
HDMI-2 disconnected (normal left inverted right x axis y axis)

eDP-1がPC本体のディスプレイです

HDMI端子に接続すると、こうなります。

HDMI-1の状態がconnectedになり出力が可能な状態になります。1

Screen 0: minimum 320 x 200, current 2560 x 1440, maximum 8192 x 8192

eDP-1 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
2560x1440 60.00*+ 59.99 59.99 59.96 59.95
1920x1440 60.00
1856x1392 60.01
1792x1344 60.01
2048x1152 59.99 59.98 59.90 59.91
1920x1200 59.88 59.95
1920x1080 60.01 59.97 59.96 59.93
1600x1200 60.00
1680x1050 59.95 59.88
1400x1050 59.98
1600x900 59.99 59.94 59.95 59.82
# モード数がそこそこ多くて長くなったので、略します
320x240 60.05
360x202 59.51 59.13
320x180 59.84 59.32
DP-1 disconnected (normal left inverted right x axis y axis)
HDMI-1 connected (normal left inverted right x axis y axis)
1920x1080 60.00 + 60.00 50.00 50.00 59.94 30.00 25.00 24.00 29.97 23.98
1920x1080i 60.00 60.00 50.00 50.00 59.94
1366x768 59.79
1280x720 60.00 50.00 59.94
1024x768 60.00
720x576 50.00
720x576i 50.00
720x480 60.00 59.94
720x480i 60.00 59.94
640x480 60.00 59.94
DP-2 disconnected (normal left inverted right x axis y axis)
HDMI-2 disconnected (normal left inverted right x axis y axis)


出力を変えるケース

雑に書くと、--output [出力先] を起点にオプションを組み合わせることで、出力先設定を切り替えることができます。


example

xrandr --auto --output eDP-1 --right-of HDMI-1


この場合、



  • --auto: 画面モードは自動決定


  • --output eDP-1: メインとしてeDP-1に出力して


  • --right-of HDMI-1: メインの左にHDMIモニターのディスプレイを出力 2

と言ったように、「使用する出力先」「出力先ごとの画面モード」「各出力先の配置」を決められます。


これを使って、どうしたいのか

この情報を元に、こんなケースに対応したくなりました。


  • 外付けの出力先が出力されていたときに、自動で見つけて接続したい

  • 稀にミラーリングしたいときがあるので、画面モードを揃えて出力したくもなる


その1: xrandr の出力を加工する

まずは、出力内容を分析して、扱いやすくしましょう。

xrandrの出力は、分解するとこんな感じになります。(自PCでの挙動)


  • Screen~で始まる行

  • [出力先名]で始まる行 x 出力先の数だけ


    • 指定できる画面モード x モード数



大まかにはこの3種類しかなく、規則もシンプルかつ文法がわかりやすくなってます。

そのため、こんな理屈で全出力先x画面モードを構造化できます。

import re

from subprocess import Popen, PIPE

re_output = re.compile(r'^(?P<name>.+) (?P<status>connected|disconnected).*')
re_mode = re.compile(r'^ (?P<mode>[0-9]+x[0-9]+).*')

def find_outputs():
# xrandrの出力取ってくる
proc = Popen('xrandr', stdout=PIPE)
out, err = proc.communicate()
displays = DisplayList()
for line in out.decode().split('\n'):
# 出力先情報か判定して、該当したらリスト登録
re_ = re_output.search(line)
if re_:
d = Display(re_.group('name'), re_.group('status'))
displays.append(d)
# 画面モード情報を判定して、該当したら出力先リストの最後の情報に画面モードを情報を登録
re_ = re_mode.search(line)
if re_:
d = displays.last()
d.mode_list.append(Size.from_string(re_.group('mode')))
return displays



  • Displayは、出力先名と画面モードのリストを持つだけのクラスです


  • DisplayListは、Displayをまとめるlistよりちょっとだけ便利にしてみたクラスです。


xsh

def mirror():

"""Slide mode
"""

# とりあえず、一式設定を取ってくる
displays = find_outputs()
# eDP-1がメイン
main_display = displays.get('eDP-1')
try:
# 接続されている出力先でeDP-1以外をサブとする
sub_display = [d for d in displays.connected() if d.name != 'eDP-1'][0]
# メインとサブの最大公約数的な最も広い画面モードを探す
mode = min([main_display.max_size, sub_display.max_size])
# xrandrコマンドを呼んでミラーリング
xrandr --output @(main_display.name) --same-as @(sub_display.name) --mode @(mode)
except IndexError:
# サブがないならその旨書いて終了
print('Currently solo display')

aliases['mirror'] = mirror


これを、xonshrcあたりで読ませるようにすると、コマンド一発で


出力先を探して、複数の出力先があれば1個を選んでミラーリングする


が実現可能となります。


おまけ

ミラーリング状態からモニターを外しても、画面モードが元に戻らないため、

こんな感じのコマンドを用意して、ディスプレイが外れたときの最適化にも使ってます。3

def solo():

displays = xrandr.find_outputs()
d = displays.get('eDP-1')
xrandr --output eDP-1 --mode @(d.max_size)





  1. この時点では接続のみで、出力はされていません 



  2. 表現的には「eDP-1 is right side of HDMI-1」みたいな文法になるっぽいです 



  3. 実は画面モードは--autoでもいいし出力先は固定値なので、ここまでしなくても良いです