2022/08/05(金) [n年前の日記]
#1 [pygame][xscreensaver] pygameを使ってxscreensaver用スクリーンセーバを作成
Python + pygame を使って xscreensaver用のスクリーンセーバを作れないか試していた。
環境は、Ubuntu Linux 20.04 LTS + Python 2.7.18 + pygame 1.9.61 + xscreensaver 6.04 + xwininfo 1.1.5。
ちなみに、Ubuntu Linux は Windows10 x64 21H2 + VMware Player上で動かしてる。
実行結果は以下。
- xscreensaver : Linux や Mac で利用できる、スクリーンセーバの管理プログラム。
- Python : プログラミング言語。
- pygame : Python を使って2Dゲームを制作できるライブラリ。
環境は、Ubuntu Linux 20.04 LTS + Python 2.7.18 + pygame 1.9.61 + xscreensaver 6.04 + xwininfo 1.1.5。
ちなみに、Ubuntu Linux は Windows10 x64 21H2 + VMware Player上で動かしてる。
実行結果は以下。
◎ 制約。 :
pygame を使って xscreensaver用スクリーンセーバを作成する場合、現時点では制約があるっぽい。pygame は 1.x.x を使わないといけない。pygame 2.x.x では動作しない可能性が高い。
何故かと言うと…。
_pygameでxscreensaver用スクリーンセーバを作れないものか - mieki256's diary
_pygameでスクリーンセーバを作りたいのだけど - mieki256's diary
もう一つ。今回のスクリプトを動かすには xwininfo というツールが必要になる。Ubuntu Linux の場合、x11-utils というパッケージに入ってる。
※ 2022/08/06追記。Ubuntu Linux 22.04 LTS を VMware Player上でインストールして動作確認してみたところ、やはり Ubuntu 22.04 + pygame 2.1.2 では正常動作しなかった。
何故かと言うと…。
- pygame 1.x.x は SDL 1.x を利用して描画その他を行う。
- pygame 2.x.x は SDL 2.x を利用して描画その他を行う。
- SDL 1.x なら、環境変数 SDL_VIDEO_DRIVER と SDL_WINDOWID を設定することで、SDL の動作を制御することができる。
- SDL 2.x は、環境変数 SDL_VIDEO_DRIVER と SDL_WINDOWID を設定しても反映されない。らしい。確認してないけどそういう話を見かけた。
_pygameでxscreensaver用スクリーンセーバを作れないものか - mieki256's diary
_pygameでスクリーンセーバを作りたいのだけど - mieki256's diary
- Ubuntu Linux 20.04 LTS は、python*-pygame で pygame 1.9.6 がインストールされるので大丈夫。
- Ubuntu Linux 22.04 LTS は、python3-pygame で pygame 2.1.2 がインストールされるので、今回のスクリプトはおそらく動かない。
もう一つ。今回のスクリプトを動かすには xwininfo というツールが必要になる。Ubuntu Linux の場合、x11-utils というパッケージに入ってる。
※ 2022/08/06追記。Ubuntu Linux 22.04 LTS を VMware Player上でインストールして動作確認してみたところ、やはり Ubuntu 22.04 + pygame 2.1.2 では正常動作しなかった。
◎ 必要なパッケージのインストール。 :
Ubuntu Linux 20.04 LTS の場合、以下で pygame 1.9.6 をインストールできる。
xwininfo は、以下でインストールできる。
sudo apt install python-pygame sudo apt install python3-pygame
- python-pygame は、Python 2.7 用のパッケージ。
- python3-pygame は、Python 3.x 用のパッケージ。
xwininfo は、以下でインストールできる。
sudo apt install x11-utils
◎ ソース。 :
ソースは以下。処理内容は、ウインドウ内でいくつかのボールが跳ね回るだけのもの。
_xscrsavpygame.py
使用画像は以下。
_ball_64x64.png
まずは xscreensaver を経由させずに動作確認してみる。
xscreensaver を経由させて実行してみる。xscreensaver の設定ファイル、~/.xscreensaver を編集。
xscreensaverの設定画面を表示して、上記で追加した「PYGAMESAVER」を選ぶと、プレビュー画面の中でもボールが跳ね回ってくれた。「プレビュー」ボタンをクリックしたら、フルスクリーンでテスト表示された。
これで、C/C++ を使わずに Python + pygame 1.x.x を使っても、xscreensaver用のスクリーンセーバを書けそうだと分かった。
_xscrsavpygame.py
import os import sys import math import platform import random import subprocess import re IMG_FILE = "ball_64x64.png" FPS = 60 class Ball: def __init__(self, img, scrw, scrh, t): self.img = img self.img_w = img.get_width() self.img_h = img.get_height() self.scrw = scrw self.scrh = scrh self.x = random.randint(16, scrw - self.img_w - 16) self.y = random.randint(16, scrh - self.img_h - 16) ang = random.randint(0, 360) spd = float(scrw) / t self.dx = spd * math.cos(math.radians(ang)) self.dy = spd * math.sin(math.radians(ang)) def update(self): self.x += self.dx self.y += self.dy if self.x <= 0 or self.x >= self.scrw - self.img_w: self.dx *= -1 if self.y <= 0 or self.y >= self.scrh - self.img_h: self.dy *= -1 def draw(self, screen): screen.blit(self.img, (self.x, self.y)) def main(): if platform.system() == "Windows": os.environ["SDL_VIDEODRIVER"] = "windib" print("$SDL_VIDEODRIVER = %s" % os.environ["SDL_VIDEODRIVER"]) else: # Linux or Darwin # os.environ["SDL_VIDEODRIVER"] = "x11" pass if "XSCREENSAVER_WINDOW" in os.environ: # xscreensaver hwnd = os.environ["XSCREENSAVER_WINDOW"] os.environ["SDL_WINDOWID"] = str(int(hwnd, 16)) # print("$XSCREENSAVER_WINDOW = %s" % os.environ["XSCREENSAVER_WINDOW"]) # print("$SDL_WINDOWID = %s" % os.environ["SDL_WINDOWID"]) # get window size using xwininfo cmd = ["xwininfo", "-id", os.environ["XSCREENSAVER_WINDOW"]] p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ret = str(p.communicate()) size_info = re.search('Width:\s+(\d+)[^H]+Height:\s+(\d+)', ret) w, h = size_info.groups() scrw = int(w) scrh = int(h) else: # not xscreensaver hwnd = None scrw, scrh = 512, 256 import pygame # pygame.init() pygame.display.init() # print("driver = %s" % pygame.display.get_driver()) # print("pygame.display.get_wm_info()") # print(pygame.display.get_wm_info()) # print("pygame.display.Info()") # print(pygame.display.Info()) screen = pygame.display.set_mode((scrw, scrh)) # pygame.display.set_caption('xscreensaver by pygame') # load image base_dir = os.path.realpath(os.path.dirname(__file__)) file_path = os.path.join(base_dir, IMG_FILE) img = pygame.image.load(file_path).convert() img.set_colorkey(img.get_at((0, 0)), pygame.RLEACCEL) # create balls balls = [] for i in range(16): balls.append(Ball(img, scrw, scrh, 60.0 * 3.0)) clock = pygame.time.Clock() looping = True while looping: # main loop # event check for event in pygame.event.get(): if event.type == pygame.QUIT: looping = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE or event.key == pygame.K_q: looping = False if not looping: break # move balls for obj in balls: obj.update() # fill bg screen.fill((0, 0, 0)) # draw balls for obj in balls: obj.draw(screen) pygame.display.flip() clock.tick(FPS) pygame.quit() sys.exit() if __name__ == '__main__': main()
使用画像は以下。
_ball_64x64.png
まずは xscreensaver を経由させずに動作確認してみる。
- xscrsavpygame.py と ball_64x64.png を任意の場所に置く。
- chmod +x xscrsavpygame.py で実行権限をつける。
- ./xscrsavpygame.py と打てば、pygameのウインドウが開いてボールが跳ね回るはず。
xscreensaver を経由させて実行してみる。xscreensaver の設定ファイル、~/.xscreensaver を編集。
programs: \ "PYGAMESAVER" \ /home/USERNAME/hoge/fuga/xscrsavpygame.py \n\ maze -root \n\
- TAB幅は8文字、TAB文字有効で編集を行う。
- 「programs:」以下に、追加するスクリーンセーバの名前と、Pythonスクリプトのパスを記述。
- 行の最後が「\」なら継続行、「\n\」ならスクリーンセーバ1つ分の記述が終わったことを示す、のだと思う。
xscreensaverの設定画面を表示して、上記で追加した「PYGAMESAVER」を選ぶと、プレビュー画面の中でもボールが跳ね回ってくれた。「プレビュー」ボタンをクリックしたら、フルスクリーンでテスト表示された。
これで、C/C++ を使わずに Python + pygame 1.x.x を使っても、xscreensaver用のスクリーンセーバを書けそうだと分かった。
◎ 環境変数 XSCREENSAVER_WINDOW について。 :
以下、スクリプトソースについて少々解説。
環境変数 XSCREENSAVER_WINDOW 関係は、ruby-gtk2 で作ったスクリーンセーバの解説が参考になるかと。
_ruby-gtk2を使ってxscreensaver用スクリーンセーバを作る
環境変数 XSCREENSAVER_WINDOW が存在するかどうかは、以下の記述で調べられる。
環境変数 XSCREENSAVER_WINDOW 関係は、ruby-gtk2 で作ったスクリーンセーバの解説が参考になるかと。
_ruby-gtk2を使ってxscreensaver用スクリーンセーバを作る
環境変数 XSCREENSAVER_WINDOW が存在するかどうかは、以下の記述で調べられる。
if "XSCREENSAVER_WINDOW" in os.environ: # xscreensaver hwnd = os.environ["XSCREENSAVER_WINDOW"] else: # not xscreensaver pass
◎ 環境変数 SDL_WINDOWID について。 :
pygame 1.x.x は、環境変数 SDL_WINDOWID にウインドウハンドルを設定してある状態で pygame.display.init() もしくは pygame.init() を呼ぶと、SDL_WINDOWID で指定されたウインドウで pygame のウインドウ(SDL 1.x のウインドウ)を作成してくれる。
また、環境変数 SDL_VIDEODRIVER に x11 や windib 等を設定すれば、どのビデオドライバーを使って描画するかを変更できる。まあ、pygame 1.x.x の場合、Linux は x11、Windows は windib がデフォルトで利用されるようではあるけれど…。
ただし、注意点がある。環境変数 SDL_WINDOWID や SDL_VIDEODRIVER の設定は、import pygame を呼ぶ前にしておかないといけない。import pygame を呼んでから、それらの環境変数を変更しても反映されない、らしい。試してないけど。
また、環境変数 SDL_VIDEODRIVER に x11 や windib 等を設定すれば、どのビデオドライバーを使って描画するかを変更できる。まあ、pygame 1.x.x の場合、Linux は x11、Windows は windib がデフォルトで利用されるようではあるけれど…。
ただし、注意点がある。環境変数 SDL_WINDOWID や SDL_VIDEODRIVER の設定は、import pygame を呼ぶ前にしておかないといけない。import pygame を呼んでから、それらの環境変数を変更しても反映されない、らしい。試してないけど。
◎ ウインドウサイズの取得について。 :
指定されたウインドウハンドル(ウインドウID)のウインドウから、サイズ(横幅と縦幅)を取得したかったのだけど、Python で取得する方法が分からなくて、かなりハマった…。先日書いた条件に、もう一つ条件を追加しないといかんなと…。
ググってみた感じでは、pygame で環境変数 SDL_WINDOWID を使う事例として、「tkinter の Frame の中に pygame のウインドウを埋め込むサンプル」がほとんどだったのだけど。どのサンプルもウインドウサイズを事前に固定してるものばかり。どうやら Python のみを使ってウインドウサイズを取得するのは難しい模様。
Python でダメなら、他のツールではどうだろう。X11関連ツールの中にウインドウ情報を取得するツールがあったりしないか ―― と探してみたら、xwininfo というツールがあると知った。
_Ubuntu xwininfoコマンド その1 - ウィンドウの情報を調べる - kledgeb
このツールを Python から呼び出せば目的を果たせるのではないか。ググってみたら、そのものズバリ、Python から xwininfo を利用する事例が以下で紹介されていた。ありがたや。
_pythonで端末画面を保存する。 - Kinaconの技術ブログ
_pythonでウィンドウ座標を取得する - キラキラするのが筋トレです
そんなわけで、環境変数 XSCREENSAVER_WINDOW で指定されたウインドウのサイズを取得する記述は以下になる。
- 「環境変数 XSCREENSAVER_WINDOW があるかどうかを調べることができて」
- 「XSCREENSAVER_WINDOW で指定されたウインドウハンドルを使って描画ができて」
- 「XSCREENSAVER_WINDOW で指定されたウインドウのサイズを取得できる」 ← New!
ググってみた感じでは、pygame で環境変数 SDL_WINDOWID を使う事例として、「tkinter の Frame の中に pygame のウインドウを埋め込むサンプル」がほとんどだったのだけど。どのサンプルもウインドウサイズを事前に固定してるものばかり。どうやら Python のみを使ってウインドウサイズを取得するのは難しい模様。
Python でダメなら、他のツールではどうだろう。X11関連ツールの中にウインドウ情報を取得するツールがあったりしないか ―― と探してみたら、xwininfo というツールがあると知った。
_Ubuntu xwininfoコマンド その1 - ウィンドウの情報を調べる - kledgeb
このツールを Python から呼び出せば目的を果たせるのではないか。ググってみたら、そのものズバリ、Python から xwininfo を利用する事例が以下で紹介されていた。ありがたや。
_pythonで端末画面を保存する。 - Kinaconの技術ブログ
_pythonでウィンドウ座標を取得する - キラキラするのが筋トレです
そんなわけで、環境変数 XSCREENSAVER_WINDOW で指定されたウインドウのサイズを取得する記述は以下になる。
import subprocess import re # get window size using xwininfo cmd = ["xwininfo", "-id", os.environ["XSCREENSAVER_WINDOW"]] p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ret = str(p.communicate()) size_info = re.search('Width:\s+(\d+)[^H]+Height:\s+(\d+)', ret) w, h = size_info.groups() scrw = int(w) scrh = int(h)
◎ 画像ファイル読み込みについて。 :
pygameスクリプトから画像を読み込むあたりでもハマった。スクリプトが置いてある場所をカレントディレクトリにした状態で動作確認している分には動くのだけど、別のカレントディレクトリから実行した際に「画像ファイルが読み込めない」と言われてしまった。
どうやら、スクリプトの置いてあるディレクトリを取得して、それを利用して画像のファイルパスを求めないといかんようだなと…。記述は以下。
_Pythonで実行中のファイルの場所(パス)を取得する__file__ | note.nkmk.me
_Python スクリプトが格納されているディレクトリのパスを取得する - まくまくPythonノート
どうやら、スクリプトの置いてあるディレクトリを取得して、それを利用して画像のファイルパスを求めないといかんようだなと…。記述は以下。
import os import pygame # load image base_dir = os.path.realpath(os.path.dirname(__file__)) file_path = os.path.join(base_dir, "hoge.png") img = pygame.image.load(file_path).convert() img.set_colorkey(img.get_at((0, 0)), pygame.RLEACCEL)
- __file__ にスクリプトのファイルパスが入っているから、それを利用してディレクトリを求める。
- os.path.join() で、ディレクトリと画像ファイル名を結合する。
_Pythonで実行中のファイルの場所(パス)を取得する__file__ | note.nkmk.me
_Python スクリプトが格納されているディレクトリのパスを取得する - まくまくPythonノート
◎ 余談。 :
pygame 2.x.x (Ubuntu Linux 22.04 LTS) では本当に動かないのかどうかがちょっと気になる。Ubuntu 22.04 LTS をどこかにインストールして実験してみないとダメかな…。
[ ツッコむ ]
以上です。