mieki256's diary



2022/08/01(月) [n年前の日記]

#1 [ruby] ruby-gtk2をWindows10上で動かしたい

Windows10 x64 21H2 + Ruby で、ruby-gtk2 を動かしたい。昨日動作確認したところ、どのバージョンの Ruby でも動いてくれなかったのだけど、しつこく試していたら動く組み合わせが少しだけ見つかった。

Ruby は、RubyInstaller 版。

_RubyInstaller for Windows

前提知識として、各gem に x86-mingw32 や x64-mingw32 とついているバージョンは、バイナリ(*.soファイル)も含んでいるのでインストール時にビルドしなくて済む。ついてないバージョンはソースからビルドすることになるので、DevKit のインストールも必要になる。

Ruby 2.2.6 x86 の場合。 :

Windows10 x64 21H2 + Ruby 2.2.6 p396 x86-mingw32 では、以下の組み合わせで動作した。

  • Ruby 2.2.6 x86-mingw32 + cairo 1.15.9 x86-mingw32 + gtk2 3.1.1 x86-mingw32

> ruby -v
ruby 2.2.6p396 (2016-11-15 revision 56800) [i386-mingw32]

> gem list | grep -E "gobject|gio2|gdk_pixbuf2|cairo|pango|atk|gtk2|glib2"
atk (3.1.1 x86-mingw32)
cairo (1.15.9 x86-mingw32)
gdk_pixbuf2 (3.1.1 x86-mingw32)
gio2 (3.1.1 x86-mingw32)
glib2 (3.1.1 x86-mingw32)
gobject-introspection (3.1.1 x86-mingw32)
gtk2 (3.1.1 x86-mingw32)
pango (3.1.1 x86-mingw32)

ただし、Ruby 2.2.6 x86 をインストールした直後は、gem が使えなかった。

gem を使うためには、rubygems-update-2.7.11.gem をダウンロードして、ローカルからインストールして、gem (rubygems)をアップデートする必要がある。ちなみに、rubygems-update 2.7.11 の次バージョンは 3.0.0 になるけれど、そこからは Ruby 2.3.0 以上を要求されるので Ruby 2.2.6 では使えない。

gem のアップデートは、以下のページを参考にして作業した。

_Windowsでgem installでSSLのエラーが出た話 - Qiita

wget https://rubygems.org/downloads/rubygems-update-2.7.11.gem
gem install --local ./rubygems-update-2.7.11.gem
update_rubygems --no-ri --no-rdoc

gem のアップデートができたら、cairo、glib2、gtk2 を、バージョンを指定してインストール。

gem install cairo -v 1.15.9
gem install glib2 -v 3.1.1
gem install gtk2 -v 3.1.1

  • cairo 1.15.9 (2017/06/03) ならインストールできたし、require "cairo" も実行できた。
  • しかし、gtk2 3.2.1 をインストールしたら、pango.so がロードできなくてエラーになった。
  • cairo 1.15.9 と同時期の pango は 3.1.6 のはずだけど、glib2 3.1.6 や gtk2 3.1.6 をインストールしても pango.so がロードできなくてエラーになった。
  • gtk2 3.1.1 をインストールしたところ、require "gtk2" が通った。

そんなわけで、Ruby 2.2.6 x86-mingw32 + cairo 1.15.9 x86-mingw32 + gtk2 3.1.1 x86-mingw32 の組み合わせなら、ruby-gtk2 が一応動いてくれた。

Ruby 2.3.3 x86 の場合。 :

手元のメモによると、2019/04/19 の時点では、以下の組み合わせでも動いていたらしいのだけど…。

  • Ruby 2.3.3 x86-mingw32 + cairo 1.15.14 x86-mingw32 + gtk2 3.2.9 x86-mingw32
  • Ruby 2.3.3 x86-mingw32 + cairo 1.15.11 x86-mingw32 + gtk2 3.2.1 x86-mingw32

2022/08/01現在、上記の組み合わせでは何故か動かなくなっていた。

しかし、バージョンを変えて試していったところ、以下の組み合わせなら動作した。

  • Ruby 2.3.3 x86-mingw32 + cairo 1.15.9 x86-mingw32 + gtk2 3.1.1 x86-mingw32

> ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [i386-mingw32]

> gem list | grep -E "gobject|gio2|gdk_pixbuf2|cairo|pango|atk|gtk2|glib2"
atk (3.1.1 x86-mingw32)
cairo (1.15.9 x86-mingw32)
cairo-gobject (3.1.1 x86-mingw32)
gdk_pixbuf2 (3.1.1 x86-mingw32)
gio2 (3.1.1 x86-mingw32)
glib2 (3.1.1 x86-mingw32)
gobject-introspection (3.1.1 x86-mingw32)
gtk2 (3.1.1 x86-mingw32)
pango (3.1.1 x86-mingw32)

ただ、インストール時に注意点がある。glib2 3.1.1 その他をインストールしようとした際、何故か cairo 1.15.14 も一緒にインストールされてしまう。cairo 1.15.14 は Ruby 2.3.3 x86 上で動かないので、そのままだと require "gtk2" をした時に「cairo.so が読み込めない」と言ってきて動かない。gem uninstall cairo で 1.15.14 をアンインストールして、1.15.9 だけを残した状態にすれば動く。

ちなみに、Ruby 2.3.3 x86-mingw32 も、インストールした直後は gem が動かない。rubygems-update 最新版を入手してアップデートする必要がある。

_Download RubyGems | RubyGems.org

rubygems-3.3.19.zip を入手して解凍。中に入って、ruby setup.rb を実行すればアップデートされる。

Ruby 1.8.7 の場合。 :

昨日メモしたのでそちらを参考に。

_2022/07/31 何が何でもruby-gtk2を動かす。

余談。 :

それにしても、昔は Ruby の各バージョンで動いてたのに、どうして動かなくなってしまったのか…。Windows10 x64 21H2 側に原因があるのだろうか。それとも自分の環境に起因している不具合なのだろうか。

どうも Windows上でGTK関係は鬼門だなと…。

#2 [ruby] ruby-gtk2は殺されていた

関連情報をググっていて今頃知ったのだけど、gtk2 (ruby-gtk2) は 3.4.3 が最終版で、3.4.4 以降は存在していない模様。

_mikutter GTK3対応(4) ?そして 5.0リリースへ? - tsutsuiの作業記録置き場
_News (2021-04-22 No.1) - Ruby-GNOME2 Project Website (WebArchive)
_ruby-gnome/NEWS at master - ruby-gnome/ruby-gnome

Ruby-GNOME (かつては ruby-gtk2 と ruby-gtk3 の両方を含んでいた) は、3.4.4 で色々なサポートを打ち切っていたらしい。
Windows版 Ruby 2.6.10 (x86-mingw32) 上で gtk2 をインストールしようとしてもエラーになるのはそのせいだった。
つまり、Windows版 Ruby 2.6.x でビルドが通る gtk2 のソースはそもそも存在してなかった、というオチだった。道理で、何度インストールを試みてもビルドエラーになってしまうわけで…。

ruby-gtk3は生きている。 :

ちなみに、Ruby 2.6.10 x86-mingw32、及び、Ruby 3.0.4 x86-mingw32 上で gtk3 をインストールしたら(gem install gtk3)、cairo 1.17.1 + gtk3 3.5.1 のビルドが通って gtk3 を使うことができた。

ruby-gtk2 は削除されてしまったけれど、ruby-gtk3 は今もメンテナンスされているし、一般的なGUIアプリを作りたいなら ruby-gtk3 を使えば済むはず、という状況ではあるらしい。

将来的な不安。 :

しかし、困った…。Windows上ではともかく、Debian Linux上では、まだ ruby-gtk2パッケージが用意されているから、ruby-gtk2 を使ったアプリも作れるだろうし、xscreensaver用のスクリーンセーバを Ruby + ruby-gtk2 で書くことだってできるのかもしれないけれど。公式には ruby-gtk2 が殺されてしまったわけだから、将来的には Debian Linux 上でも ruby-gtk2パッケージが消滅してしまいそうな気がする。そうなると、xscreensaver用スクリーンセーバを Ruby で書くこともできなくなるわけで…。ノリとしては、今から Flash を使ってスクリーンセーバを書くぞ、みたいなソレに結構近い可能性もありそうだなと。

ruby-gtk2 ではなく、ruby-gtk3 を使って xscreenaver用のスクリーンセーバを書くことができればいいのだろうけど、そのあたりは gtk3 に詳しい人じゃないとできないことだよな…。しかし、gtk3 でそんなことができるのかどうか…。

2022/08/02(火) [n年前の日記]

#1 [python][pygame] pygameでスクリーンセーバを作りたいのだけど

Python + pygame でWindows用のスクリーンセーバを作れないものかとググっていたら気になる話を見かけたのでメモ。2003年頃のやり取りらしい。

_[pygame] windows screen saver in pygame?
_IntegratingPyGame - wxPyWiki

環境変数 SDL_VIDEO_DRIVER と SDL_WINDOWID を設定することで、指定されたウインドウハンドルをSDLの描画領域として利用することができるかもしれない、とのことで。それが可能なら、Windows用のスクリーンセーバを作れるのかもしれないなと…。

ただ、今現在の pygame は SDL1.x から SDL2 になってるので、件の環境変数は反映されないかも、という話も見かけた。

_Using 'SDL_WINDOWID' does not embed pygame display correctly into another application - Issue #1574 - pygame/pygame
_"SDL_CreateWindowFrom" in "display" module by Rabbid76 - Pull Request #2981 - pygame/pygame
_class method "from_foreign_window" added to class "Window" by Rabbid76 - Pull Request #2982 - pygame/pygame

解決するためのパッチ(SDL_CreateWindowFrom()を利用するらしい)も投稿されてるけれど、マージされてるかはちょっと不明。

余談。Ruby/SDL も件の環境変数を使って、指定のウインドウハンドルで描画領域を用意できるっぽい。

_Ruby/SDL Reference Manual

ただ、Ruby/SDL って、今も使えるのだろうか…?

Windows用スクリーンセーバについて少し説明。 :

ウインドウハンドル云々については、ちょっと説明が必要かもしれない。

Windows用のスクリーンセーバは、拡張子が .scr のファイルなのだけど。この .scr、実態は .exe の拡張子を変更しただけのファイル。3種類のコマンドラインオプション、/s、/c、/p xxxx のどれかしらを渡した際に動作が変わる .exe を作って、それを .scr にリネームすればWindows用のスクリーンセーバとして利用できる、ということになっている。

オプションの意味は以下。
  • /s : フルスクリーン表示。
  • /c : 設定画面ダイアログを表示する。
  • /p xxxx : プレビュー表示。xxxx は、Windowsのスクリーンセーバ選択画面のプレビュー窓のウインドウハンドル。

/s か /c を指定した時の動作なら、おそらくはどんな言語でも、ある程度は実装できる。

問題は、/p xxxx を指定した場合。ウインドウハンドル xxxx に描画をしないといけないわけだけど、他から指定されたウインドウハンドルに対して描画ができる言語やフレームワークってそれほどなかったりするので、ちゃんとしたスクリーンセーバを作るなら C/C++ で書くしかないよね、という話になってしまう。まあ、C# の作例はあるし、HSP でも書けるのだけど…。 *1

そんなわけで、Python + pygame も与えられたウインドウハンドルで描画ウインドウを作れるなら、スクリーンセーバを作る際のハードルが随分と下がってくれそうだなと。

*1: ただ、HSP で作ったスクリーンセーバは、起動時か終了時かタイミングが分からんけれど、Windowsのイベントログにエラーを残していくのだよな…。なんでだろ。

2022/08/03(水) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、22:50-02:10の間、自宅サーバ止めてました。申し訳ないです。

夜中に鳴り始めて、そろそろ鳴り止んだかなと思ったら、また鳴り始めて…。TVニュースによると、東北地方の各地で警報が出るレベルの大雨が降っているそうで。その影響なのかしらん。

#2 [windows] PowerToysがアンインストールできない

Windows10 x64 21H2上で久々に PowerToys 0.57.0 を起動したら、「更新プログラムが利用可能です。v0.61.1」と表示されたので、「ダウンロードしてインストール」をクリックしてみたのだけど。いきなり Windows10 がブルースクリーンに…。

さてはセットアップファイルを自分でDLして実行しないとダメなパターンだろうかと、github から PowerToysSetup-0.61.1-x64.exe をダウンロードして実行してみたところ、ウインドウが出て次をクリックしたタイミングでブルースクリーン。

_Releases - microsoft/PowerToys

これは前バージョンをアンインストールしてから試さないとダメなヤツかなと、コントロールパネル経由でアンインストールしようとしたら、そこでもブルースクリーン。どうしろと。

こんなに頻繁にブルースクリーン+再起動をされたら、SSDやHDDが壊れてしまう…。一応、管理者権限で開いた PowerShell 上で chkdsk /f C: を打ってチェックはしたけど…。

何にせよ、こんな怖いツールはインストールしておけない…。どうにかしてアンインストールする手段を見つけないと…。

クリーンアップツールがあるらしい。 :

以下のページによると、クリーンアップツールがあるらしい。

_PowerToys のインストール | Microsoft Docs
_PowerToys/tools/CleanUp_tool_powershell_script at main - microsoft/PowerToys
_PowerToys/tools/CleanUp_tool at main - microsoft/PowerToys

CleanUp_tool.ps1 を眺めた感じでは、設定ファイルとレジストリを削除する処理っぽい。実行ファイル本体には手を付けないように見える。どうにかしてアンインストーラを正常動作させないとダメ、ということかな…。

2022/08/04(木) [n年前の日記]

#1 [windows] Windows10からPowerToysをアンインストールした

昨晩、Windows10 x64 21H2 上で PowerToys 0.57.0 を 0.61.1 にアップデートしようとしたら Windows10 がブルースクリーン(BSOD)になってしまう不具合に遭遇した。アンインストールしようとしても必ずブルースクリーンになる。ブルースクリーンには、thread がどうとか、Ntfs.sys がどうとか表示されている。

そんなわけで、どうにか PowerToys をアンインストールしようと四苦八苦していた。

ハードウェア環境は、CPU が AMD Ryzen 5 5600X。CドライブがSSD、DドライブがHDD。

以下、参考ページ。

_PowerToys のインストール | Microsoft Docs
_I can't delete PowerToys or Uninstall it - Issue #1490 - microsoft/PowerToys
_Cant uninstall Microsoft Power Toys. - Microsoft Community

試したこと。 :

まず、PowerToys の設定画面を表示して、全機能をオフにしてみた。PowerToys はエクスプローラに色々な機能を追加するので、そのあたりで問題が発生してそうだよなと…。設定を変更したらOS再起動。

PowerToys 0.57.0 のセットアップファイル、PowerToysSetup-0.57.0-x64.exe を github から入手。管理者権限で PowerShell を開いて、以下を打ってみた。

.\PowerToysSetup-0.57.0-x64.exe --extract_msi

セットアップファイルは .exe で提供されているけれど、アンインストール処理時は .msi が必要になる、という話をどこかで見かけた。--extract_msi とつけると、おそらくはどこかしらに .msi を解凍して、そちらから処理をするのではないか。たぶん。

アンインストール画面が出てきたので、「Uninstall」をクリック。ブルースクリーンにならないでくれと祈りながら処理が終わるのを数十秒ほど待った。幸い、ブルースクリーンにはならず、アンインストールも成功した。助かった。

またこんな目にあったら嫌なので、今後、PowerToys のインストールは避けよう…。機能が豊富な分、Windowsの奥深いところに関わってそうでもあるし…。

#2 [ruby][xscreensaver] ruby-gtk2を使ってxscreensaver用スクリーンセーバを作る

Ruby + ruby-gtk2 を使って、xscreensaver用スクリーンセーバを作れそうか試してみた。

一応ざっくり説明しておくと…。
環境は、Ubuntu Linux 20.04 LTS + Ruby 2.7.0 p0 + ruby-gtk2 3.4.1-2build1 + xscreensaver 6.04。ちなみに、Ubuntu Linux は Windows10 x64 21H2 + VMware Player上で動かしてる。

実行結果は以下のような感じ。

参考にしたスクリプト。 :

今回参考にさせてもらった Rubyスクリプトは以下。お天気情報を表示してくれる、Ruby製の xscreensaver用スクリーンセーバらしい。

_zhum/rubysaver: Xscreensaver module, displaying weather, forecast, clock and now playing song title and author.
_rubysaver/rs.rb at master - zhum/rubysaver

必要なパッケージのインストール。 :

端末を開いて、以下を打ってインストール。

sudo apt install ruby-gtk2

ちなみに、Ubuntu Linux 20.04 LTS の場合、デフォルトで Ruby 2.7.0 がインストールされているっぽい。

ソース。 :

前述のスクリプトから、xscreensaver に絡んでいる部分だけを抜き出して、簡単なサンプルにしてみた。処理内容としては、赤い円がウインドウ内を跳ね回るだけのスクリプト。

_04_xscrsav.rb
#!/usr/bin/ruby
# encoding: UTF-8

require "gtk2"
require "logger"

# for gtk2
SIGNAL_DRAW = "expose_event"

# for gtk3
# SIGNAL_DRAW = "draw"

TMOUT = 16

$drawing_area = nil
$logger = Logger.new("/tmp/rubysaver-#{ENV["USER"]}.log", "monthly")

class RubyXscrApp < Gtk::Window
  def initialize
    super

    set_title "xscreensaver module"

    signal_connect "destroy" do
      $logger.warn "EXIT by destroy"
      Gtk.main_quit
    end

    signal_connect "delete-event" do
      $logger.warn "EXIT by delete-event"
      Gtk.main_quit
      false
    end

    signal_connect "delete_event" do
      $logger.warn "EXIT by delete_event"
      Gtk.main_quit
      false
    end

    realize

    ident = ENV["XSCREENSAVER_WINDOW"]
    if not ident.nil?
      $logger.warn "XSCREENSAVER_WINDOW = #{ident}"
      self.window = Gdk::Window.foreign_new(ident.to_i(16))
      self.window.set_events(Gdk::Event::EXPOSURE_MASK | Gdk::Event::STRUCTURE_MASK)

      x, y, width, height, depth = self.window.geometry
      # puts "window = #{self.window}, (x,y,w,h) = #{x}, #{y}, #{width}, #{height}"
      $logger.warn "window = #{self.window}, (x,y,w,h) = #{x}, #{y}, #{width}, #{height}"

      set_default_size width, height
      move(x, y)
      # set_window_position :center
    else
      x, y, width, height, depth = self.window.geometry
      width, height = 640, 360
      set_default_size width, height
      set_window_position :center
      puts "window = #{self.window}, (w,h) = #{width}, #{height}"
    end

    @x = width / 2
    @y = height / 2
    @dx = width.to_f / (60 * 1)
    @dy = height.to_f / (60 * 1.5)

    $drawing_area = @darea = Gtk::DrawingArea.new
    @bgcol = Gdk::Color.new(0x2000, 0x4000, 0xa000)
    @darea.modify_bg(:normal, @bgcol)
    @darea.set_size_request(width, height)

    @darea.signal_connect SIGNAL_DRAW do |widget, event|
      on_draw widget
    end

    @fixed = Gtk::Fixed.new
    @fixed.put(@darea, 0, 0)
    add(@fixed)

    show_all
  end

  def on_draw(widget)
    cr = widget.window.create_cairo_context
    x, y, width, height, depth = widget.window.geometry

    # update position
    r = 24
    @x += @dx
    @y += @dy
    @dx *= -1 if (@x <= r or @x >= width - r)
    @dy *= -1 if (@y <= r or @y >= height - r)

    # clear bg
    cr.set_source_rgb(0.1, 0.3, 0.5)
    cr.paint

    # draw circle
    cr.set_source_rgb(1.0, 0.0, 0.0)
    cr.arc(@x, @y, r, 0, 2 * Math::PI)
    cr.fill

    cr.destroy
  end
end

Gtk.init
window = RubyXscrApp.new

# animation
GLib::Timeout.add(TMOUT) do
  $drawing_area.queue_draw if (not $drawing_area.nil?)
  true
end

Gtk.main

chmod +x 04_xscrsav.rb で実行権限をつけて、端末上で ./04_xscrsav.rb としてテスト実行。ウインドウが開いて、赤い円が跳ね回った。これでひとまず、xscreensaver を経由しなくても、アニメーション部分の動作テストや調整はできそうだなと…。

xscreensaver から呼び出せるように、設定ファイル ~/.xscreensaver を編集。
programs:                                                                     \
                   "RUBYSAVER"                                                \
                                  /home/USERNAME/prg/ruby/ruby-gtk2/04_xscrsav.rb \n\
                                maze -root                                  \n\
  • TAB幅は8文字、TAB文字有効で編集を行う。
  • 「programs:」以下に、追加するスクリーンセーバの名前と、Rubyスクリプトのパスを記述。
  • 行の最後が「\」なら継続行、「\n\」ならスクリーンセーバ1つ分の記述が終わったことを示す、のだと思う。

xscreensaverの設定画面を表示して、上記で追加した「RUBYSAVER」を選んでみると、プレビュー画面の中でも赤い円が跳ね回ってくれた。「プレビュー」ボタンをクリックしたら、フルスクリーンでテスト表示された。

これで、ハードルの高い C/C++ で書かなくても、Ruby + ruby-gtk2 で xscreensaver用スクリーンセーバを書くことができそうだと分かった。

問題点。 :

上記のサンプルは、少々問題が残ってる。xscreensaverの設定画面でスクリーンセーバ種類を切り替えた際に、以下のメッセージが表示されてしまう。

$ xscreensaver-settings: 06:27:44: XScreenSaver-debug: Name com.canonical.AppMenu.Registrar does not exist on the session bus
Gdk-WARNING **: GdkWindow 0x5000023 unexpectedly destroyed
        from /home/USERNAME/prg/ruby/ruby-gtk2/04_xscrsav.rb:125:in `<main>'

「GdkWindow 0xXXXX が予期せず破壊された」的な警告が出ている。おそらく、xscreensaver が強制的にスクリーンセーバを殺してしまうことで、こういう警告が出ているのかなと…。何か正しい終了手順があるなら、対策したいものだけど…。

少し解説。 :

xscreensaver は、スクリーンセーバを呼び出す際、「このウインドウを使ってスクリーンセーバの描画処理をするべし」的に、環境変数 XSCREENSAVER_WINDOW に16進数文字列でウインドウハンドルを設定してくれる。

_XScreenSaver Manual
_XScreenSaver FAQ

つまり、
  • 「環境変数 XSCREENSAVER_WINDOW があるかどうかを調べることができて」
  • 「XSCREENSAVER_WINDOW で指定されたウインドウハンドルを使って描画できる」
そんな言語/フレームワーク/プログラムなら、xscreensaver用スクリーンセーバが作れますよ、ということになっている。

実際、xscreensaver のFAQページには、mpv という動画再生ソフトを呼び出して動画を再生するサンプルが載っているけど、それは環境変数 XSCREENSAVER_WINDOW を mpv のコマンドラインオプションに渡すことで目的を果たしている。

Q. How do I make XScreenSaver play a video clip?

A. Install mpv and add something like the following to the "programs" preference in your .xscreensaver file:

"Movies" mpv --really-quiet --no-audio --fs --loop=inf \
--no-stop-screensaver --shuffle \
--wid=$XSCREENSAVER_WINDOW \
$HOME/Videos/poop.mp4 \n\

Or, point it at an .m3u file instead: a text file listing your video files, one per line.

XScreenSaver FAQ より


まあ、xscreensaver のドキュメントには、「ウインドウハンドルを渡して処理できる言語やフレームワークはそれほど多くないから、実際にはC/C++で書くことになるだろうね」と書いてあったりするのだけど…。

_README.hacking.edit.txt

幸い、Ruby は環境変数の有無を調べることができるし、ruby-gtk2 は指定されたウインドウハンドルを自身のウインドウとして再設定する機能があるっぽいので、こうして xscreensaver用スクリーンセーバを書けそう、という話になるわけで。

一応、そのあたりの処理を以下に抜き出してみる。

    ident = ENV["XSCREENSAVER_WINDOW"]
    if not ident.nil?
      # xscreensaver

      self.window = Gdk::Window.foreign_new(ident.to_i(16))
      self.window.set_events(Gdk::Event::EXPOSURE_MASK | Gdk::Event::STRUCTURE_MASK)
      x, y, width, height, depth = self.window.geometry
      set_default_size width, height
      move(x, y)
    else
      # not xscreensaver

      x, y, width, height, depth = self.window.geometry
      width, height = 640, 360
      set_default_size width, height
      set_window_position :center
    end

ちょっとハマったのは、ウインドウの表示位置の指定方法。参考にしたスクリプトは、set_window_position :center を呼んで表示位置を決めていたっぽいのだけど、それだと xscreensaver設定画面のプレビュー窓に位置が合ってくれなかった。move(x, y) にしてみたら上手くいった。

さておき。今回、描画処理は、RubyXscrAppクラス内の on_draw() の中でやっている。件のメソッドの中を魔改造すれば違う描画処理になってくれるはず。

アニメーションさせるためには、一定の周期で描画処理を呼び出す指定が必要だけど、ruby-gtk2 の場合、GLib::Timeout.add(ミリ秒) do - end で指定ができるらしい。以前は Gtk.timeout_add(ミリ秒) で指定してたらしいけど、その指定は非推奨になったそうで。

GLib::Timeout.add(TMOUT) do
  $drawing_area.queue_draw if (not $drawing_area.nil?)
  true
end

描画は、Cairo::Context を使う方法と、Gdk::Drawable を使う方法の2種類があるそうだけど、今回は前者を使ってみた。ただ、Mac上で動かしたときは、Cairo::Context を毎回破棄しないとおかしくなるらしいので、一応 .destroy を呼んで破棄するようにしておいた。 *1

最初、DrawingArea をどこに登録すればいいのか分からなかったけど、どうやら gtk2 のウインドウに登録してやればいいようだなと…。

    $drawing_area = @darea = Gtk::DrawingArea.new
    @bgcol = Gdk::Color.new(0x2000, 0x4000, 0xa000)
    @darea.modify_bg(:normal, @bgcol)
    @darea.set_size_request(width, height)
...
    @fixed = Gtk::Fixed.new
    @fixed.put(@darea, 0, 0)
    add(@fixed)

Gtk::Fixed は、座標指定で配置するためのWidgetらしい。

懸念事項。 :

Debian Linux において、ruby-gtk2 パッケージは、Debian 11 bullseye の時点までは公式に用意されている。故に、Debian系である Ubuntu Linux 20.04 LTS でもインストールして利用することができたのだけど。この ruby-gtk2 パッケージは、将来的にどうなるかちょっと分からないなと…。

_Debian -- bullseye の ruby-gtk2 パッケージに関する詳細

上記ページを見ると、Debian 9 stretch、Debian 10 buster、Debian 11 bullseye の時点までは公式パッケージとして ruby-gtk2 が用意されていた。ただ、おそらくは Debian 12 になるのであろう bookworm にはパッケージが用意されてないっぽい。sid には用意されているみたいだけど…。

例えば、ここで python3-pygame パッケージ情報を眺めると、こちらは Debian 12 bookworm 用パッケージも用意されているように見える。ruby-gtk2 とは扱いが違う。

_Debian -- bookworm の python3-pygame パッケージに関する詳細

そんなわけで、ひょっとすると次期バージョンの Debian系では、ruby-gtk2 を使って xscreensaver用スクリーンセーバを作成するのは難しくなるのかもしれない。ruby-gtk2 パッケージが無くなっているかもしれないので…。

ruby-gtk3 で書き直せたら、しばらくは安心(?)なのかもしれない。自分は知識が無いのでちょっと無理だけど…。

参考ページ。 :


*1: でも、Mac なら JavaScript でスクリーンセーバが書けるという話も見かけたのだよな…。だったらフツーは JavaScript で書くよな…。

#3 [xscreensaver][pygame] pygameでxscreensaver用スクリーンセーバを作れないものか

Python + pygame で、xscreensaver用スクリーンセーバを作れないものだろうか。ちょっと気になったので、少し調べてみた。

pygameに求められる条件。 :

SDL1.x を使っていた頃の pygame は ―― おそらく pygame 1.9.x の頃は、環境変数 SDL_VIDEO_DRIVER と SDL_WINDOWID を利用することで、指定されたウインドウハンドルに対して描画することができていたらしい。

_pygameでスクリーンセーバを作りたいのだけど - mieki256's diary

そういうことができるなら、xscreensaver が指定してきたウインドウハンドルを使って描画することもできそうな気がする。

ただ、最近の pygame 2.x.x は SDL2 を使うようになったそうで、SDL1.x 時代に利用できたその手の技(?)が使えなくなっている模様。

つまり、「xscreensaver用スクリーンセーバを pygame を使って作りたい」なら、「pygame 1.9.x が動く環境が必要」ということになる。たぶん。

OS側の事情。 :

それを踏まえて。Ubuntu Linux 20.04 LTS (focal) の場合、python-pygame (Python 2.7.x用) や python3-pygame (Python 3.x用) といったパッケージをインストールすることで pygame 1.9.6 が利用できる。

_Ubuntu - focal の python-pygame パッケージに関する詳細
_Ubuntu - focal の python3-pygame パッケージに関する詳細

であれば、Ubuntu 20.04 LTS 環境なら、pygame を使って xscreensaver用スクリーンセーバを作ることも不可能ではないのかもしれない。

しかし、Ubuntu 22.04 LTS (jammy) の場合、python3-pygame (Python 3.x用) でインストールされるのは、pygame 2.1.2 らしい…。

_Ubuntu - jammy の python3-pygame パッケージに関する詳細

ちなみに、Ubuntu 22.04 LTS (jammy) において、python-pygame (Python 2.7.x用) は削除された模様。Ubuntu は積極的に Python 2.7系を殺しにかかってきているディストリビューションなので、まあ、仕方ない。

ということで、pygame で xscreensaver用スクリーンセーバを作成できたとしても、Ubuntu 20.04 LTS なら動くけど、Ubuntu 22.04 LTS では動かない、ということになってしまいそうだなと。

Windowsは条件が緩い。 :

ちなみに、Windows と Linux の場合、このあたりちょっと事情が違う気がする。

Windowsの場合、ローカルで pygame 1.9.x をインストールして何かしらを作って、ソレを最終的に .exe に変換すれば、どのPCにソレを持って行っても、pygame 1.9.x を使って動かせる、ということができそうな気がする。

しかし、Linux の場合、OSにインストールされたSDL関連ライブラリを利用して pygame が動くはずで…。おそらく Linux上では、バージョンが事なる pygame を共存させて使い分けるのは少々難しいのではなかろうか。分からんけど。

そんなわけで、もしかすると以下のような状況かもしれないなと。

  • Windows用スクリーンセーバを pygame を使って作成するのは、まだどうにか可能なのかもしれない。
  • Linux + xscreensaver用スクリーンセーバを pygame を使って作成するのは、OSのバージョンが古ければできるけど、最新バージョンのOSではちょっと難しいかもしれない。

何か抜け道は無いものか…。例えば AppImage を利用したりはできないものかな…。

_Linux Mint Tips : SNAP、FLATPAK、APPIMAGE 違い比較 | 221B Baker Street

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上で動かしてる。

実行結果は以下。

制約。 :

pygame を使って xscreensaver用スクリーンセーバを作成する場合、現時点では制約があるっぽい。pygame は 1.x.x を使わないといけない。pygame 2.x.x では動作しない可能性が高い。

何故かと言うと…。
  • 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 をインストールできる。
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
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
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 が存在するかどうかは、以下の記述で調べられる。

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 を呼んでから、それらの環境変数を変更しても反映されない、らしい。試してないけど。

ウインドウサイズの取得について。 :

指定されたウインドウハンドル(ウインドウID)のウインドウから、サイズ(横幅と縦幅)を取得したかったのだけど、Python で取得する方法が分からなくて、かなりハマった…。先日書いた条件に、もう一つ条件を追加しないといかんなと…。

  • 「環境変数 XSCREENSAVER_WINDOW があるかどうかを調べることができて」
  • 「XSCREENSAVER_WINDOW で指定されたウインドウハンドルを使って描画ができて」
  • 「XSCREENSAVER_WINDOW で指定されたウインドウのサイズを取得できる」 ← New!
そんな言語/フレームワーク/プログラムなら、xscreensaver用スクリーンセーバが作れますよ、みたいな。

ググってみた感じでは、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スクリプトから画像を読み込むあたりでもハマった。スクリプトが置いてある場所をカレントディレクトリにした状態で動作確認している分には動くのだけど、別のカレントディレクトリから実行した際に「画像ファイルが読み込めない」と言われてしまった。

どうやら、スクリプトの置いてあるディレクトリを取得して、それを利用して画像のファイルパスを求めないといかんようだなと…。記述は以下。

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 をどこかにインストールして実験してみないとダメかな…。

2022/08/06() [n年前の日記]

#1 [xscreensaver][ubuntu][pygame] Ubuntu Linux 22.04 LTSを試用

Windows10 x64 21H2 + VMware Player上で、Ubuntu Linux 22.04 LTS をインストールして少し触ってみた。

先日作成した、Ruby + ruby-gtk2 を使った xscreensaver用スクリーンセーバ(の雛形サンプル)は動いてくれた。Ubuntu 22.04 にも ruby-gtk2 パッケージは存在していて、sudo apt install ruby-gtk2 でインストールできた。

_ruby-gtk2を使ってxscreensaver用スクリーンセーバを作る

Python + pygame 2.1.2 を使った xscreensaver用スクリーンセーバ(の雛形サンプル)は、予想通り動かなかった。やはり pygame 2.x.x (SDL2) は、環境変数 SDL_WINDOWID にウインドウハンドルを設定しても反映してくれないらしい。

_pygameを使ってxscreensaver用スクリーンセーバを作成

pygame 1.9.6を動かしたい。 :

Ubuntu 22.04 LTS上で pygame 1.9.6 を動かす方法はないものかと少し考えてみたけれど名案は思い付かず。一般的には sudo apt install python3-pygame で pygame をインストールするはずだから、Ubuntu 22.04 では pygame 2.1.2 が動くものと考えておかないといかんわけで…。

おそらくビルド等すれば pygame 1.9.6 も使えるのかもしれないけれど、pygame を使った他のスクリプトにも影響を与えるだろうから、そういうことはやらないほうがいいのだろうな、と…。

動かすだけなら pipenv 等を使って仮想環境を作成して、というのはどうだろうか。そのあたり、ちょっと勉強してみよう…。

#2 [pygame] tkinterの中にpygameを埋め込む

Windows10 x64 21H2 + Python 2.7.18 + pygame 1.9.6 を使って、tkinter のウインドウの中に pygame のウインドウを埋め込めそうか実験してみた。


実行結果は以下。tkinter のウインドウの中に、pygame のウインドウ(青い部分)が表示されて、赤いボールが跳ね回ってる。ボタンをクリックするとボールの座標が初期化される。

ss_01_embed_pygamewindow.gif

必要なモジュールのインストール。 :

Windows10 x64 21H2 + Python 2.7.18 上で、pip install pygame と打って pygame をインストールしようとすると、現時点では pygame 2.0.3 がインストールされてしまう。 *1

しかし、pygame 2.x.x は、環境変数 SDL_WINDOWID を使った制御ができないので、今回はそのあたりの制御ができる pygame 1.9.6 をインストールしないといけない。

以下は、pygame をアンインストールしてから、pygame 1.9.6 をインストールする例。
pip uninstall pygame
pip install pygame==1.9.6

pygame のバージョン確認は以下。
> python -c "import pygame; print(pygame.version.vernum);"
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
1.9.6

_コマンドラインからpygameバージョンを調べる - Qiita

それとは別に。これは Windows限定の話だけど、今回、win32gui というモジュールを利用してウインドウのサイズ(横幅と縦幅)を取得してみた。win32gui は pywin32 というモジュールの中に入っているらしいので、pywin32 をインストールしないといけない。

pip install pywin32
> pip install pywin32
...
Collecting pywin32
  Using cached pywin32-228-cp27-cp27m-win32.whl (6.9 MB)
Installing collected packages: pywin32
Successfully installed pywin32-228

pywin32 228 がインストールされた。

ソース。 :

ソースは以下。処理内容としては、tkinter のウインドウの中に pygame のウインドウが埋め込まれて、赤いボールが跳ね回るだけ。

_01_embed_pygamewindow.py
"""
Windows10 x64 21H2 + Python 2.7.18 32bit + pygame 1.9.6 + tkinter
"""

import Tkinter
import os
import sys
import platform


w, h = 640, 360
x, y = (w / 2), (h / 2)
dx = float(w) / (60 * 1.0)
dy = float(h) / (60 * 1.5)


def update_pygame_window():
    global x, y, dx, dy, screen, root

    w = screen.get_width()
    h = screen.get_height()
    x += dx
    y += dy
    r = 24
    if x <= r or x >= w - r:
        dx *= -1
    if y <= r or y >= h - r:
        dy *= -1
    
    # draw pygame window
    screen.fill(pygame.Color(20, 80, 160))
    pygame.draw.circle(screen, pygame.Color(255, 0, 0), (int(x), int(y)), r)

    # Update the pygame display
    pygame.display.flip()

    root.after(16, update_pygame_window)


def reset_pos():
    global x, y, w, h
    x, y = (w / 2), (h / 2)


# init tkinter widget
root = Tkinter.Tk()
root.title("Embed pygame window in tkinter")

embed = Tkinter.Frame(root, width=w, height=h)
embed.pack()

btn = Tkinter.Button(root, text="Reset Position", command=reset_pos)
btn.pack()

root.update()
root.update_idletasks()

# set SDL environ
hwnd = embed.winfo_id()
os.environ['SDL_WINDOWID'] = str(hwnd)

if platform.system() == "Windows":
    # Windows
    os.environ['SDL_VIDEODRIVER'] = 'windib'

    # get window size (Windows only)
    import win32gui
    rect = win32gui.GetWindowRect(hwnd)
    x0, y0, x1, y1 = rect[0], rect[1], rect[2], rect[3]
    frm_size = ((x1 - x0), (y1 - y0))
    print("size : %d x %d" % frm_size)
else:
    # Linux or Darwin
    os.environ['SDL_VIDEODRIVER'] = 'x11'

# init pygame
import pygame

pygame.display.init()

# get tkinter.widget size
frm_w = embed.winfo_width()
frm_h = embed.winfo_height()
# print("frame size : %d x %d" % (frm_w, frm_h))

screen = pygame.display.set_mode((frm_w, frm_h))
# screen = pygame.display.set_mode((0, 0))

update_pygame_window()

root.mainloop()  # tkinter main loop

pygame.quit()
sys.exit()

python 01_embed_pygamewindow.py で実行できる。

終了は、ウインドウの右上の閉じるボタンをクリック。

tkinter widget のウインドウハンドルを取得する。 :

pygame 1.x.x は、環境変数 SDL_WINDOWID にウインドウハンドル(ウインドウID)を指定してから、pygame.display.init() (または pygame.init()) を呼ぶことで、SDL_WINDOWID で指定されたウインドウを pygame のウインドウとして使ってくれる。ソレを利用して、tkinter のウインドウ内に pygame ウインドウを埋め込んでいる。

tkinter でウインドウハンドル(ウインドウID) を取得するには、.winfo_id() を使うらしい。

embed = Tkinter.Frame(root, width=w, height=h)
embed.pack()

# ...

hwnd = embed.winfo_id()
os.environ['SDL_WINDOWID'] = str(hwnd)

# ...

import pygame

pygame.display.init()

また、import pygame をする前に、環境変数 SDL_WINDOWID を設定しておかないといけない、という話も見かけたので、一応そのような流れにしておいた。

ウインドウサイズを取得する。 :

pygame ウインドウの描画用サーフェイスを取得するには、pygame.display.set_mode((横幅, 縦幅)) を呼ばないといけない。そのためには、埋め込み先の tkinter.Frame のサイズ(横幅と縦幅)が分かってないといけない。

tkinter を使ってる場合は、.winfo_width()、.winfo_height() を使えば、横幅、縦幅を取得できる模様。

root.update()
root.update_idletasks()

# ...

frm_w = embed.winfo_width()
frm_h = embed.winfo_height()
print("frame size : %d x %d" % (frm_w, frm_h))

screen = pygame.display.set_mode((frm_w, frm_h))

ただし、事前に tkinter のウインドウが表示されて、レイアウト等が決まった状態になってないとサイズの取得はできないそうで。そのため、.winfo_width() 等を呼ぶ前に .update_idletasks() か .update() を一度呼んでおく必要がある。

ところで、.winfo_width() 等は tkinter の利用時しか使えない。tkinter を使っていない場合はどうやってウインドウサイズを取得すればいいのだろう…? 別の方法も調べてみた。

これは Windows 限定の話だけれど、pywin32 の中に入っている win32gui を使ってウインドウサイズを取得する方法もあると知った。win32gui を使えば、Windows上のどのウインドウでもサイズを取得できるはずなので、こちらのやり方のほうが色んな場面で利用できそうな気もする。もっとも、pywin32 をインストールする必要が出てくるけれど…。

import platform

if platform.system() == "Windows":
    # Windows
    os.environ['SDL_VIDEODRIVER'] = 'windib'

    # get window size (Windows only)
    import win32gui
    rect = win32gui.GetWindowRect(hwnd)
    x0, y0, x1, y1 = rect[0], rect[1], rect[2], rect[3]
    frm_size = ((x1 - x0), (y1 - y0))
    print("size : %d x %d" % frm_size)
else:
    # Linux or Darwin
    os.environ['SDL_VIDEODRIVER'] = 'x11'

win32gui.GetWindowRect(ウインドウID) を呼ぶと、左上 x 座標、左上 y 座標、右下 x 座標、右下 y 座標を返してくる。それらの値を使えば、横幅と縦幅は求められる。

tkinter の mainloop を呼びたい。 :

これは tkinter を使う時だけ意識しないといけない話だけれど。tkinter を使うなら、最後に .mainloop() を呼ぶことで、そこでずっとメインループを回してやらないといけない。らしい。もし、.mainloop() を呼ばずに、独自に while 等でメインループを回してしまうと、例えばウインドウの閉じるボタンをクリックした時にエラーが表示されてしまったりする。.mainloop() の中で tkinter の動作に必要なアレコレを行っているのだろうなと…。

しかし、tkinter 側の .mainloop() を回してしまうと、今度は pygame を利用した処理部分をメインループとして回せなくなる。

そんな時は、tkinter の .after() を使うといいらしい。

def update_pygame_window():
    global x, y, dx, dy, screen, root

    # ...
    
    # draw pygame window
    screen.fill(pygame.Color(20, 80, 160))
    pygame.draw.circle(screen, pygame.Color(255, 0, 0), (int(x), int(y)), r)
    pygame.display.flip()

    root.after(16, update_pygame_window)

# ...

update_pygame_window()
root.mainloop()  # tkinter main loop

pygame.quit()
sys.exit()

.after(ミリ秒, 関数名) で、指定したミリ秒が経過したら、指定した関数を呼んでくれる模様。つまり、その関数の最後で、また .after() を呼んでやれば、おおよそ一定周期で処理が行われる状態になるはず。まあ、処理内容によっては、そこで処理時間も変動してしまうだろうから、正確に一定周期で呼ばれるわけでもないだろうけど…。

何にせよ、この書き方をしたら、tkinter ウインドウの閉じるボタンをクリックしてもエラーが出ずに終了してくれる状態になった。

参考ページ。 :


*1: ちなみに、Python 3.9 上で pygame をインストールしようとすると、pygame 2.1.2 がインストールされる。

2022/08/07() [n年前の日記]

#1 [python][ubuntu] virtualenvでPythonの別バージョン環境を作成

Ubuntu Linux 22.04 LTS上で、Python + pygame 1.9.6 が利用できる状態にしたい。そこで、複数の Python 動作環境を作成できるらしい virtualenv を試用してみた。

ちなみに、Ubuntu 22.04 は仮想PC上で ―― Windows10 x64 21H2 + VMware Player 16.2.4 build-20089737 上で動かしてる。

一応、Ubuntu 22.04 の公式リポジトリに virtualenv (20.13.0+ds-2) というパッケージは存在するのだけど…。その版をインストールしても、何故かエラーが出て動かない。
sudo apt install virtualenv

動かないのでは意味が無いので、アンインストールしておいた。
sudo apt purge virtualenv
sudo apt autoremove

どうやら pip3 でインストールした版ならそれらしく動くようだなと…。以下はユーザ権限でインストールする例。
pip3 install virtualenv
$ virtualenv --version
virtualenv 20.16.3 from /home/USERNAME/.local/lib/python3.10/site-packages/virtualenv/__init__.py

アンインストールは以下。
pip3 uninstall virtualenv

管理者権限でインストールしなおした。
sudo -H pip3 install virtualenv
$ virtualenv --version
virtualenv 20.16.3 from /usr/local/lib/python3.10/dist-packages/virtualenv/__init__.py

ユーザディレクトリ/.local/bin に各種コマンドがインストールされるみたいなので、~/.bashrc の最後のあたりでPATHを設定しておいた。
vi ~/.bashrc
export PATH=$PATH:/home/USERNAME/.local/bin

Python 2.7 の環境を作成。 :

Ubuntu 22.04 にインストールされている Python 2.7 をクローン(?)して、Python 2.7 の環境を作成してみる。

myenv27 というディレクトリを作成して、その中に環境を入れてみる。
virtualenv -p python2.7 myenv27

環境を有効化。
source myenv27/bin/activate

プロンプトの最初に「(ディレクトリ名)」が表示されていたら、その環境で動いてる。Python のバージョンを確認。
(myenv27) ... $ python --version
Python 2.7.18

環境を無効化。
(myenv27) ... $ deactivate

Python 3.10 の環境を作成、有効化、無効化。 :

Ubuntu 22.04 にインストールされている Python 3.10.4 をクローンして Python 3.10 の環境を作成、有効化、無効化するなら以下。
$ virtualenv -p python3.10 myenv310

$ source myenv310/bin/activate

(myenv310) ... $ python --version
Python 3.10.4

(myenv310) ... $ deactivate

システムに入ってないPythonバージョンで環境を作成。 :

virtualenv は、システムにインストール済みの Python をコピーして仮想環境を作る。システムにインストールされていないPythonバージョンを使いたい場合は、事前に別バージョンをインストールしておかないといけない。らしい。

_virtualenvで特定バージョンのPythonを指定する[Ubuntu]
_New Python Versions : “deadsnakes” team

sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update

Ubuntu 22.04 LTS の場合、Python 3.7、3.8、3.9 ならインストールできる。それより前のバージョンは用意されてない模様。
sudo apt install python3.7 python3.7-dev python3.7-tk python3.7-full
sudo apt install python3.8 python3.8-dev python3.8-tk python3.8-full
sudo apt install python3.9 python3.9-dev python3.9-tk python3.9-full

pygame 1.9.6 を Python 3.8環境上でインストール。 :

Ubuntu 22.04 は、sudo apt install python3-pygame をすると、pygame 2.1.2 がインストールされてしまう。どうにかして pygame 1.9.6 を動かしてみたい。

以下で、pygame 1.9.x をビルドするために必要なパッケージ群が紹介されていた。インストールしておく。

_python - Trying to install pygame on ubuntu which gives error - Stack Overflow
sudo apt install libfreetype6-dev libjpeg-dev libportmidi-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsdl1.2-dev libsmpeg-dev libx11-dev libavformat-dev libswscale-dev

pygame 1.9.6 は Python 2.7, 3.4 - 3.8 をサポートしてるので、今回は Python 3.8環境を作成してみる。

_pygame - PyPI

sudo apt install python3.8 python3.8-dev python3.8-tk

virtualenv -p python3.8 myenv38

Python 3.8環境を有効化。
source myenv38/bin/activate
(myenv38) ... $ python -V
Python 3.8.13

pygame 1.9.6 を pip を使ってインストールしてみる。
pip install pygame==1.9.6
$ pip list | grep pygame
pygame     1.9.6
$ python -c "import pygame; print(pygame.version.vernum);"
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
1.9.6

これで、Ubuntu 22.04上でも、Python 3.8 + pygame 1.9.6 がインストールできた。

昨日作成した、tkinter ウインドウ内に pygame ウインドウを埋め込むスクリプトを動かしてみた。Python 2.7 ではなく Python 3.8 上で動かすので、昨日のスクリプトの import 関連のあたりを少し修正してある。 *1

_01_embed_pygamewindow.py

Ubuntu 22.04 + Python 3.8.13 + pygame 1.9.6 上でも、このスクリプトが動いてくれた。ちゃんと pygame のウインドウが埋め込まれてる。pygame 2.x.x では埋め込まれないはずなので、たしかに pygame 1.9.6 が利用できている。

ただ、これはあくまで開発用の環境で動いてるだけなので…。フツーのアプリとして、「Python 3.8 + pygame 1.9.6 + Pythonスクリプト」を呼び出せるわけではなさそうだなと…。

virtualenvwrapperを導入。 :

virtualenvwrapper をインストールすると、環境の管理や切り替えが楽になるらしい。試用してみた。

_Virtualenvwrapperの導入 - Qiita
_virtualenvでpython環境を管理する - Qiita

インストール。
sudo -H pip3 install virtualenv
sudo -H pip3 install virtualenvwrapper

各環境を入れておくディレクトリ、~/.virtualenvs を作成しておく。
mkdir ~/.virtualenvs

virtualenvwrapper.sh の置き場所を確認。
$ which virtualenvwrapper.sh
/usr/local/bin/virtualenvwrapper.sh

~/.bashrc の最後に記述を追加。
vi ~/.bashrc
if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then
    export WORKON_HOME=$HOME/.virtualenvs
    source /usr/local/bin/virtualenvwrapper.sh
fi

使い方は以下。
  • mkvirtualenv --python=python3.8 env38 : 環境を作成
  • deactivate : 環境を無効化
  • workon : 環境を一覧表示
  • workon env38 : 環境を有効化
  • rmvirtualenv env38 : 環境を削除

参考ページ。 :


*1: Python 2.7 は Tkinter を import するが、Python 3.x は tkinter を import しないといけない。

2022/08/08(月) [n年前の日記]

#1 [python] Windows10 + virtualenvでPython環境を作成

昨日、Ubuntu Linux 22.04 LTS 上で virtualenv を使って Python 3.8 環境を作成したのだけど。

_virtualenvでPythonの別バージョン環境を作成 - mieki256's diary

Windows10上でも同じことができるのか試してみた。

環境は、Windows10 x64 21H2 + Python 3.9.13 + Python 3.8.10。

最初は venv を使って環境を作ろうとしたのだけど、何故か pip が利用できない環境ができてしまったので、代わりに virtualenv ならどうだろうと試してみた次第。

DOS窓を開いて以下を打つ。

virtualenvのインストール。
pych 38-64

python -m pip install -U setuptools virtualenv virtualenvwrapper-win
or
pip install virtualenv virtualenvwrapper-win setuptools -U
> virtualenv --version
virtualenv 20.16.3 from C:\Python\Python38-64\Lib\site-packages\virtualenv\__init__.py

環境作成 / 有効化 / 無効化。
virtualenv -p python3.8 env38
.\env38\Scripts\activate
deactivate

ただ、環境を作成した直後は、pip が正常動作しなかった。一旦DOS窓を終了して、再度開いてから試したら、何故か pip が動作した…。 *1

pip -V と打ってみて、仮想環境ディレクトリ内のパスが表示されていることを確認。もし、違うパスが表示されていたら、正常に切り替えることができていない。
> .\env38\Scripts\activate

> pip -V
pip 22.2.2 from D:\...\venv_test\env38\lib\site-packages\pip (python 3.8)

pip を使って、作業に必要なモジュールをインストールしていく。今回は、pygame 1.9.6、pywin32 (win32gui)、exe化ツールを使いたい。
pip install pygame==1.9.6
pip install pywin32
pip install py2exe
pip install pyinstaller
> pip list
Package                   Version
------------------------- ---------
altgraph                  0.17.2
cachetools                5.2.0
future                    0.18.2
pefile                    2022.5.30
pip                       22.2.2
py2exe                    0.11.1.1
pygame                    1.9.6
pyinstaller               5.3
pyinstaller-hooks-contrib 2022.8
pywin32                   304
pywin32-ctypes            0.2.0
setuptools                63.4.1
wheel                     0.37.1

必要なパッケージをインストールできた。

先日作成した、tkinterのウインドウ内に pygameウインドウを埋め込むスクリプト、01_embed_pygamewindow.py を使って動作確認。

_01_embed_pygamewindow.py
python 01_embed_pygamewindow.py

正常に動作した。このスクリプトは、pygame 1.x.x じゃないと動かないので、pygame 1.9.6 が使えていることが分かった。―― Python 3.9 には pygame 2.1.2 をインストールしてあるので、そちらが使われていたらこのスクリプトは正常動作しないはず。

virtualenvwrapper-winを使う。 :

Linux上では virtualenv を使いやすくするためのツール、virtualenvwrapper が存在するけれど。Windowsに移植した virtualenvwrapper-win というツールが存在するらしい。試用してみた。
pip install virtualenvwrapper-win -U
  • mkvirtualenv <name> : 環境作成
  • lsvirtualenv : 一覧表示
  • rmvirtualenv <name> : 環境削除
  • workon [<name>] : 環境を有効化
  • deactivate : 環境を無効化

デフォルトでは、C:\Users\USERNAME\Envs\ 以下に各環境(ディレクトリ)が作成される。環境変数 WORKON_HOME を設定しておけば場所を変更できる。

問題無く動作したけれど…。ただ、自分が把握できてないフォルダ内で、色々なファイルが増えていくのはちょっと引っ掛かるので、virtualenv だけ使って作業したいなと思えてきた。virtualenvwrapper-win はアンインストールした。
pip uninstall virtualenvwrapper-win

参考ページ。 :

venvも試用してみた。 :

Python 3.3以降は、virtualenv と似た機能を持つ venv というツールが同梱されている。

Windows10 x64 21H2 + Python 3.9.13 + Python 3.8.10 の環境で、デフォルトでパスが通してある Python 3.9.13 から venv を使った場合は、問題無く pip が利用できた。

しかし、Python 3.8 + venv で環境を作ると、pip が見つからないと表示されてしまう…。

色々試してたら原因が分かった。自作の Pythonバージョン切替batファイル、pych.bat を使って Python 3.8 に切り替えるとおかしな動作になる。

py.exe 経由で venv を使えば、Python 3.9 と同様に pip を利用できる環境を作成できた。
py -3.8 -m venv myenv38

myenv38\Scripts\activate

(myenv38) ... > python -V
Python 3.8.10

(myenv38) ... > python -m pip -V
pip 21.1.1 from D:\home\prg\python\_test_sample\pygame\venv_test\myenv38\lib\site-packages\pip (python 3.8)

deactivate
  • py -3.8 -m venv myenv38 : Python 3.8 を利用して、venv で myenv38 と名前で環境を作成。
  • myenv38\Scripts\activate : 環境を有効化。
  • python -V : Python のバージョンを表示。
  • python -m pip -V : pip のバージョンと、pip が置いてある場所を表示。
  • deactivate : 環境を無効化。

自作のPython切替bat。 :

一応、自作の、Python切替batファイルも置いておく。今回のように、時々問題が発生するけど…。

_pych.bat
@echo off

@rem 各Pythonは、C:\Python\PythonXX\ にインストールされてることが前提。
@rem XXの部分を指定して PATH を切り替えてる。

if "%1"=="39-64" goto SETPATH
if "%1"=="38-64" goto SETPATH
if "%1"=="27" goto SETPATH
if "%1"=="26" goto SETPATH
if "%1"=="25" goto SETPATH
if "%1"=="24" goto SETPATH

echo Pythonのパスを切替えます。
echo Usage:  
echo     pych 39-64 (default)
echo     pych 38-64
echo     pych 27
echo     pych 26
echo     pych 25
echo     pych 24
goto END

:SETPATH
set PPATH=C:\Python\Python%1
goto SETPATHCOMMON

:SETPATHCOMMON
@rem set PATH=%PPATH%;%PPATH%\Scripts;%PPATH%\Lib\site-packages\PyQt4;%PATH%
set PATH=%PPATH%;%PPATH%\Scripts;%PATH%

set PYTHONPATH=%PPATH%\Lib\site-packages
set PYTHON_ROOT=%PPATH%

@rem ftype Python.CompiledFile=%PPATH%\python.exe "%%1" %%*
@rem ftype python.file=%PPATH%\\python.exe "%%1" %%*
@rem ftype Python.NoConFile=%PPATH%\pythonw.exe "%%1" %%*

echo.
echo Pythonのパスを %PPATH% に設定しました
echo PYTHONPATH=%PYTHONPATH%
echo PYTHON_ROOT=%PYTHON_ROOT%
echo.
:END

Windows版のPythonなら py.exe がインストールされているだろうから、そちらを使ったほうがいいと思う。
> py --list
Installed Pythons found by py Launcher for Windows
 -3.9-64 *
 -3.8-64
 -2.7-32
 -2.6-32
 -2.5-32
 -2.4-32


> py -2.4 -V
Python 2.4.4

> py -2.7 -V
Python 2.7.18

> py -3.9 -V
Python 3.9.13

*1: もしかすると、自前のPythonバージョン切り替えbatファイル、pych.bat の中で、環境変数 PYTHONPATH まで設定していたのがマズかったのかもしれない。DOS窓を終了して再度開けば PYTHONPATH が未設定の状態になるので、それで正常動作してくれたのかも。

#2 [nitijyou] 茶の間のPCを自室に戻した

茶の間に置いてあった Windows PC を ―― 朱鼓という名前の、円筒状のPCケースに入れてあるPCを、自室に持ってきた。

以前、親父さんが骨折してあまり動けなくなった際、茶の間でソリティアを遊べるようにと設置したものの。親父さん自身がそのPCの存在をすっかり忘れて「おい。これは一体なんだ?」と言い出す始末。埃だらけになってたので、置いといてもしょうがないなと。

そもそも茶の間には、先日お袋さんが会社から持ち帰ってきたものも含めて、3台もノートPCが置いてあるわけで。PCを使いたくなったらノートPCを使ったほうがいいよなと…。

無線LAN子機を変えた。 :

今までつけていた無線LAN子機、ELECOM WDC-300SU2SWH (RTL8192CU) が不調。とにかく切れる。ガンガン切断される。熱で壊れたのかな…。

_300Mbps USB無線小型LANアダプタ - WDC-300SU2SWH


今まで Ubuntu機で使っていた ELECOM WDC-150SU2MBK (RTL8188EU? RTL8188EUS?) と交換してみた。こちらなら全然切断されない。

_150Mbps USB無線超小型LANアダプタ - WDC-150SU2MBK


どうもウチの環境は、300MBpsで接続できると謳う製品群と相性が悪い…。IO-DATA WN-G300UA (RTL8192CU) も頻繁に切断されてしまうから、ずっと埃を被ってるし…。

Windows Updateが大変。 :

Windows Update をしてみたら、めちゃくちゃ時間がかかった。

Windows10 x64 21H1 の状態でパッチを全部当ててから、21H2 にアップグレードしたけど、ダウンロードは比較的サクサク進むものの、そこから先が…。再起動を指示した直後の終了時、「Windowsの準備をしています」系のメッセージが、少なくとも1時間ずっと表示されたまま。

仕方ないので放置して寝てしまったけど、結局全部終わるまで、12時間ぐらいかかった気がする。AMD Athlon 5350 という非力なCPUのせいだろうか。ベースクロックは低いけど4コア4スレッドだし、CドライブはSSDなのだけどな…。でも、TDP 25W のCPUだから遅いのも仕方ないか…。

ちょっとググってみたら、Core 2 Duo E8400 より遅いCPUらしい。もっとも、Core 2 Duo E8400 は TDP 65W。内蔵GPUも無し。Athlon 5350 はGPUも内蔵しているのに TDP 25W だから、たしかに省電力ではある…。

ケースがベタベタする。 :

この朱鼓というPCケース、真っ赤な色のケースなのだけど。触るとベタベタする上に、指や床に赤い塗料がくっついてしまう状態になってしまった。高温多湿な日本の気候では酷いことになる材料を吹きつけたのだろうな…。見た目をよくしたいと思ったのか、触り心地をよくしたいと思ったのか分からんけど…。

ウチにある、他のアレコレも、いくつか似たような状態になっていて…。Gateway製ノートPCも表面がベタベタになったし、DELL製タブレットPCもベタベタだし、Microsoft製キーボードのパームレストのラバー部分もベタベタだし。どうしてこういう材料を使ってしまうのか。

この手の失敗した設計?が、一切どこにもフィードバックされないまま、今もどこかで、いずれべたべたになってしまう製品として作られ続けているあたりが悲しい。こういった製品を設計/製造してる地域は、一体どういう気候なのか…。

2022/08/09(火) [n年前の日記]

#1 [nitijyou] 自宅サーバ止めてました

雷が鳴ったので、16:40-18:30の間、自宅サーバを止めてました。申し訳ないです。

東のほうで結構落ちてたようでヒヤヒヤしました…。

#2 [ubuntu] Ubuntu 20.04 LTS上でr8169ドライバが使われてしまう問題

手元のサブPC、Core2Duo E8400機 + Ubuntu Linux 20.04 LTS の環境で、LAN? NIC? のドライバが間違ってロードされてることに今頃気づいた。

Core2Duo E8400機で使ってる M/B、GIGABYTE GA-G31M-ES2L は、LAN用のチップとして Realtek RTL8111C が載っている。

_GA-G31M-ES2L (rev. 1.x) スペック | マザーボード - GIGABYTE Japan
> LAN     1. RTL 8111C chip (10/100/1000 Mbit)

しかし、Ubuntu 20.04 上では r8169 というドライバが読み込まれていた。本来、RTL8111 なら、r8169 ではなく r8168 が読み込まれないといけない。

そんなわけで、r8168 が使われるように変更してみた。

まずは現状を確認。lsmod と打ってみる。r8169 が表示されたら、間違ったドライバが読み込まれている。

Ubuntu は、r8168 に変更するためのパッケージが公式リポジトリにちゃんと用意されていた。ありがたや。インストールする。
sudo apt install r8168-dkms

そのままだと r8169 が相変わらず読み込まれるっぽいので、r8169 をブラックリストに登録する。r8168-dkms パッケージをインストールした際に、r8169 をブラックリストに登録するためのファイルもインストールされているのだけど、該当行がコメントアウトされているので一部修正。
sudo vi /etc/modprobe.d/r8168-dkms.conf
#blacklist r8169
↓
blacklist r8169

sudo reboot で再起動後、lsmod で r8168 が表示されることを確認。
$ lsmod | grep r8
r8168                 548864  0

参考ページ。 :

余談。 :

lspci とか、sudo lshw -c network と打つと、もう少し情報が出てくるらしい。
$ lspci | grep Eth
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 02)

$ lspci -v | grep r8
        Kernel driver in use: r8168
        Kernel modules: r8168

$ sudo lshw -c network
  *-network
       詳細: イーサネット interface
       製品: RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
       ベンダー: Realtek Semiconductor Co., Ltd.
       物理ID: 0
       バス情報: pci@0000:03:00.0
       論理名: enp3s0
       バージョン: 02
       シリアル: 00:1f:d0:cf:6c:96
       サイズ: 1Gbit/s
       容量: 1Gbit/s
       幅: 64 bits
       クロック: 33MHz
       性能: pm msi pciexpress msix vpd bus_master cap_list rom __________________ physical tp 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation
       設定: autonegotiation=on broadcast=yes driver=r8168 driverversion=8.048.00-NAPI duplex=full ip=192.168.1.14 latency=0 link=yes multicast=yes port=twisted pair speed=1Gbit/s
       リソース: irq:17 IOポート:be00(サイズ=256) メモリー:fdeff000-fdefffff メモリー:fdee0000-fdeeffff メモリー:fd900000-fd90ffff

余談その2。 :

このあたりの作業をしていて、ふと気づいた。ひょっとして、足元に置いてある、別のサブPC、A8-3850機も同じ状態なのではないか…?

A8-3850機のM/B は GIGABYTE GA-A75M-UD2H (rev. 1.0)。

_GA-A75M-UD2H (rev. 1.0) スペック | マザーボード - GIGABYTE Japan

これも RTL8111C が載ってると書いてある。後で r8169 がロードされてないかチェックしてみないと…。

※ 2022/08/10追記。A8-3850機、GIGABYTE GA-A75M-UD2H (rev. 1.0) も r8169 がロードされてしまっていた。r8168 に変更しておいた。

#3 [ubuntu][linux] ELECOM WDC-300SU2SWH を Ubuntu 20.04 で利用

USB接続無線LAN子機 ELECOM WDC-300SU2SWH を Core2Duo E8400 + Ubuntu Linux 20.04 LTS 機に繋いでみた。

_300Mbps USB無線小型LANアダプタ - ELECOM WDC-300SU2SWH

しかし、認識してくれない。

この WDC-300SU2SWH を、Ubuntu Linux 20.04 LTSで利用したい。ちなみに、末尾の WH や BK は色を表しているようなので、型番としては WDC-300SU2S でいいのだろうか。

ググったところ、WDC-300SU2S に載っているチップは Realtek RTL8192CU らしい。

情報を調べる。 :

lsusb と打てば、ベンダーID(メーカー名を示す)とプロダクトID(製品型番を示す)は出てくる。
$ lsusb
Bus 001 Device 002: ID 056e:4009 Elecom Co., Ltd WDC-300SU2S
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
...

_Ubuntu 20.04でUSBのWiFiアダプタ(WDC-300SU2SBK)を認識させる方法 - Runner in the High

WDC-300SU2S のベンダIDとプロダクトIDは、056e:4009 のようだなと…。このIDを持っている機器を、どのドライバで動かせばいいのか、その情報を Linux 側で持っていないので、この機器が利用できない状態なのだろう。

ちなみに、lsusb -s 1:2 -v と打てば、もう少し情報が出てくる。-s X:Y は、X が Busナンバー、Y が Deviceナンバー。

_【 lsusb 】コマンド――USBデバイスの一覧と詳細情報を表示する:Linux基本コマンドTips(273) - @IT
$ lsusb --help
Usage: lsusb [options]...
List USB devices
  -v, --verbose
      Increase verbosity (show descriptors)
  -s [[bus]:][devnum]
      Show only devices with specified device and/or
      bus numbers (in decimal)
  -d vendor:[product]
      Show only devices with the specified vendor and
      product ID numbers (in hexadecimal)
  -D device
      Selects which device lsusb will examine
  -t, --tree
      Dump the physical USB device hierarchy as a tree
  -V, --version
      Show version of program
  -h, --help
      Show usage and help

$ lsusb -s 1:2 -v

Bus 001 Device 002: ID 056e:4009 Elecom Co., Ltd WDC-300SU2S
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x056e Elecom Co., Ltd
  idProduct          0x4009
  bcdDevice            2.00
  iManufacturer           1
  iProduct                2
  iSerial                 3
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x002e
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           4
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass    255 Vendor Specific Subclass
      bInterfaceProtocol    255 Vendor Specific Protocol
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1

rtl8xxxuをロード。 :

WDC-300SU2S のベンダIDとプロダクトID、056e:4009 を、ドライバ rtl8192cu の関連ファイルに追加してやれば自動認識(?)されるのだろうけど。しかし、rtl8192cu のドライバはとにかく不安定という話を目にした記憶があり…。

なので、Ubuntu 16.04時代は、改善されているドライバを github から入手して、ビルドして導入するのが妥当だったらしいけど。

_pvaret/rtl8192cu-fixes: Realtek 8192 chipset driver, ported to kernel 3.11.

ただ、そのドライバを配布してる上記ページでは、

「このドライバはもうメンテナンスされてないよ」
「kernel 4.4 から rtl8xxxu というドライバが使えるからそっちを利用すべき」
「若干いくつか設定しないといけないけどね」

みたいなことが書いてある。

であれば、まずは rtl8xxxu を導入できないか試してみよう。以下を参考に作業。

_Ubuntu 20.04でUSBのWiFiアダプタ(WDC-300SU2SBK)を認識させる方法 - Runner in the High
_Kali Linuxで無線LANアダプタ LAN-W150N/U2BK を使う | tarufulog
_wireless - Compile and install rtl8192cu driver - Ask Ubuntu

まずは、rtl8192cu が使われないように、ブラックリストに入れておく。
sudo modprobe -r rtl8192cu
sudo vi /etc/modprobe.d/blacklist.conf

/etc/modprobe.d/blacklist.conf の最後に、以下を追加。
blacklist rtl8192cu

rtl8xxxu ドライバをロード。
sudo modprobe rtl8xxxu
$ ls -al /sys/bus/usb/drivers/
合計 0
drwxr-xr-x 6 root root 0  8月  9  2022 .
drwxr-xr-x 4 root root 0  8月  9  2022 ..
drwxr-xr-x 2 root root 0  8月  9  2022 hub
drwxr-xr-x 2 root root 0  8月  9 11:12 rtl8xxxu
drwxr-xr-x 2 root root 0  8月  9  2022 usb
drwxr-xr-x 2 root root 0  8月  9  2022 usbfs
「rtl8xxxu」というディレクトリが見える。ロードできたっぽい。

ベンダーIDとプロダクトIDを、new_id というファイルに追記する。
$ echo "056E 4009" | sudo tee /sys/bus/usb/drivers/rtl8xxxu/new_id
tee: /sys/bus/usb/drivers/rtl8xxxu/new_id: そのようなファイルやディレクトリはありません
056E 4009

new_id なるファイルは無いと言われてしまった…。rtl8192cu だったら存在するファイルということだろうか。

githubからドライバをDLしてインストール。 :

rtl8xxxu を使うのは一旦諦めて、以下のドライバをインストールしてみた。

_pvaret/rtl8192cu-fixes: Realtek 8192 chipset driver, ported to kernel 3.11.
sudo apt-get update
sudo apt-get install git linux-headers-generic build-essential dkms
git clone https://github.com/pvaret/rtl8192cu-fixes.git
sudo dkms add ./rtl8192cu-fixes
sudo dkms install 8192cu/1.11
sudo depmod -a
sudo cp ./rtl8192cu-fixes/blacklist-native-rtl8192.conf /etc/modprobe.d/

再起動したけれど、反映されない。WDC-300SU2S はサポートしてない製品だから当たり前だろうか。

ドライバをアンインストール。
sudo rm /etc/modprobe.d/blacklist-native-rtl8192.conf
sudo dkms status
sudo dkms remove -m 8192cu/1.11 --all

rtl8192cuを使ってみる。 :

ダメ元で rtl8192cu を使ってみる。

/etc/modprobe.d/blacklist.conf を編集して、先ほど行った rtl8192cu のブラックリスト登録をコメントアウト(行頭に「#」をつける)。
sudo vi /etc/modprobe.d/blacklist.conf

rtl8192cu のロード。
sudo modprobe rtl8192cu
$ ls -al /sys/bus/usb/drivers/rtl8192cu/
合計 0
drwxr-xr-x 2 root root    0  8月  9 12:06 .
drwxr-xr-x 6 root root    0  8月  9  2022 ..
--w------- 1 root root 4096  8月  9 12:07 bind
lrwxrwxrwx 1 root root    0  8月  9 12:07 module -> ../../../../module/rtl8192cu
-rw-r--r-- 1 root root 4096  8月  9 12:07 new_id
-rw-r--r-- 1 root root 4096  8月  9 12:07 remove_id
--w------- 1 root root 4096  8月  9 12:06 uevent
--w------- 1 root root 4096  8月  9 12:07 unbind

rtl8192cu を使う分には、new_id というファイルがちゃんと存在している模様。

ベンダーIDとプロダクトIDを追記。
echo "056E 4009" | sudo tee /sys/bus/usb/drivers/rtl8192cu/new_id

iwconfig を使って、無線LAN関係の状態を確認。
$ iwconfig
enp3s0    no wireless extensions.

lo        no wireless extensions.

wlxXXXXXXXXXXXX  IEEE 802.11  ESSID:off/any
          Mode:Managed  Access Point: Not-Associated   Tx-Power=20 dBm
          Retry short limit:7   RTS thr=2347 B   Fragment thr:off
          Power Management:off

無線LANが使えるようになった。たしかに、rtl8192cu ドライバを使えば、ELECOM WDC-300SU2S は使えるっぽい。

ただ、この状態では再起度すると元に戻ってしまうそうで。

以下のやり取りでは、/etc/rc.local を利用して、OS起動時に、ベンダIDとプロダクトIDを new_id に書き込むことで対処する方法が紹介されている。

_wireless - Compile and install rtl8192cu driver - Ask Ubuntu

それと…。たぶん、やっぱり、rtl8192cu は不安定じゃないのかと…。どの情報を見ても、「rtl8192cu ドライバは不安定」「rtl8xxxu を使うべし」と書いてあるし…。

rtl8xxxu と new_id。 :

rtl8xxxu で new_id というファイルに書き込んで対応製品を増やすのは難しいっぽい。driver_info というのを見て処理してるらしいけど、カーネルモジュールのソースを修正してビルドする、という話になっていくようで…。

_rtl8xxxu 4.4.5(from f23): I get a panic adding a new device to the driver - Patchwork
_jetson nanoでWiFiドングルElecom WDC-433DU2HBK を使う
_Raspberry PiでWDC-433DU2H2-Bを動かす - Qiita

再度gtihubのドライバに挑戦。 :

再度、github版のドライバを使ってみることにする。ソースを編集して、WDC-300SU2S に対応させることができるんじゃないのかなと閃いたので…。

_pvaret/rtl8192cu-fixes: Realtek 8192 chipset driver, ported to kernel 3.11.

sudo apt-get update
sudo apt-get install git linux-headers-generic build-essential dkms
git clone https://github.com/pvaret/rtl8192cu-fixes.git

rtl8192cu-fixes/os_dep/linux/usb_intf.c を編集して、WDC-300SU2S のベンダIDとプロダクトIDを追加してみる。

_rtl8192cu-fixes/usb_intf.c at master - pvaret/rtl8192cu-fixes

93行目あたりに記述を追加。行頭に「+」がついている行がソレ。「+」は省く。

$ diff -u rtl8192cu-fixes/os_dep/linux/usb_intf.c.orig rtl8192cu-fixes/os_dep/linux/usb_intf.c
--- rtl8192cu-fixes/os_dep/linux/usb_intf.c.orig        2022-08-09 21:11:39.962416889 +0900
+++ rtl8192cu-fixes/os_dep/linux/usb_intf.c     2022-08-09 21:12:58.022374097 +0900
@@ -91,6 +91,7 @@
        {USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817C)},/* 8192CE-VAU USB minCard */ \
        {USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8191)},/* 8192CU 2*2 */ \
        {USB_DEVICE(0x1058, 0x0631)},/* Alpha, 8192CU */ \
+       {USB_DEVICE(0x056E, 0x4009)},/* ELECOM WDC-300SU2S */ \
        /*=== Customer ID ===*/  \
        /****** 8188CUS Dongle ********/ \
        {USB_DEVICE(0x2019, 0xED17)},/* PCI - Edimax */ \

github の readme.md に従って作業を続行。
sudo dkms add ./rtl8192cu-fixes
sudo dkms install 8192cu/1.11
sudo depmod -a
sudo cp ./rtl8192cu-fixes/blacklist-native-rtl8192.conf /etc/modprobe.d/
sudo cp ./rtl8192cu-fixes/8192cu-disable-power-management.conf /etc/modprobe.d/
sudo reboot

OSを再起動したら、WDC-300SU2S を自動認識してくれるようになった。

$ lsmod | grep 81
snd_hda_codec_generic    81920  1 snd_hda_codec_realtek
8192cu                573440  0
cfg80211              708608  1 8192cu
r8168                 548864  0

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:1f:d0:cf:6c:96 brd ff:ff:ff:ff:ff:ff
3: wlxXXXXXXXXXXXX: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff

wlx* というのが増えている。これが無線LAN子機経由の接続。

_ワイヤレス設定 - ArchWiki

ちなみに、iw というツールも状態確認に使えるっぽい。
$ iw dev
phy#0
        Interface wlxXXXXXXXXXXXX
                ifindex 3
                wdev 0x1
                addr xx:xx:xx:xx:xx:xx
                ssid hoge-router
                type managed
                txpower 12.00 dBm
$ iw dev wlxXXXXXXXXXXXX link
Connected to yy:yy:yy:yy:yy:yy (on wlxXXXXXXXXXXXX)
        SSID: hoge-router
        freq: 2457
        signal: -45 dBm
        tx bitrate: 144.4 MBit/s

使えるようになったものの、安定性は…これはまだちょっとよく分からない。

ちなみに、WDC-300SU2S は USB2.0ポートに繋ぐと不安定になって、USB3.0ポートに繋ぐと安定するという話も見かけたけれど。今回実験に使った M/B、GIGABYTE GA-G31M-ES2L (rev. 1.x) にはUSB2.0ポートしかないので、USB2.0ポートに繋ぐしかなく…。でもまあ、それでも一応動いてるように見える。

速度測定。 :

速度の測定には、iperf が使える。
sudo apt install iperf iperf3
パッケージ名 iperf が 2.x.x系。iperf3 が 3.x.x系。

$ iperf --version
iperf version 2.0.13 (21 Jan 2019) pthreads

Ubuntu Linux側で iperf -s を実行。
iperf -s
iperf -s の終了は Ctrl + C。

別PC(Windows10機)で、iperf -c IPアドレス、を打てば速度が出てくる。
> iperf -c 192.168.1.6
------------------------------------------------------------
Client connecting to 192.168.1.6, TCP port 5001
TCP window size: 64.0 KByte (default)
------------------------------------------------------------
[452] local 192.168.1.9 port 52252 connected with 192.168.1.6 port 5001
[ ID] Interval       Transfer     Bandwidth
[452]  0.0-10.0 sec  61.8 MBytes  51.8 Mbits/sec

50Mbps は出てるっぽい。遅いような気もするけれど、IEEE802.11b/g/n対応製品ならこんなものかも…。

2022/08/10(水) [n年前の日記]

#1 [python][windows] Pythonスクリプトをexe化したい

Windows10 x64 21H2 + Python 3.8.10 x64 上で、Pythonスクリプトをexeファイルにしたい。pygame を使ってWindows用のスクリーンセーバを作ってみたいのだけど、そのためには Pythonスクリプトファイルをexe化しないといけないので…。

今回は virtualenv を使って、Python 3.8 が動作する環境を作った。と言うのも、各exe化ツールは動作に不要なファイルまで含めてexe化してしまう時があるそうで。故に、virtualenv や venv 等を使って、動作に必要な最低限のモジュールだけがインストールされている環境を用意して、ソレをexe化するのが望ましいのだとか。

Pythonスクリプトをexe化するツールは色々あるけれど、今回は PyInstaller と py2exe を試用してみた。

virtualenv で環境を作った場合、PyInstaller、py2exe も、その環境にインストールして、その環境内で実行しないといけない。そうしないと、システム側にインストールした PyInstaller や py2exe を使ってしまって、動作に不要なモジュールまで入ってしまう。

> env38\Scripts\activate

(env38) ... > python -V
Python 3.8.10

(env38) ... > python -m pip install pyinstaller -U
(env38) ... > python -m pip install py2exe  -U

(env38) ... > python -m pip list | grep -e pyinstaller -e py2exe
py2exe                    0.11.1.1
pyinstaller               5.3
pyinstaller-hooks-contrib 2022.8

(env38) ... > deactivate

ちなみに、昔の py2exe は Python 3.4 までしか対応してなかったらしいけど、今現在の py2exe 0.11.1.1 は Python 3.7 - 3.10 まで対応している。

_py2exe - PyPI

PyInstallerでexe化。 :

Python 3.8.10 + PyInstaller 5.3 を使って、先日作成した、tkinterのウインドウ内にpygameのウインドウを埋め込むスクリプト、01_embed_pygamewindow.py をexe化してみた。

_01_embed_pygamewindow.py

以下を打てば、exe化される。
pyinstaller --onefile --noconsole --noupx 01_embed_pygamewindow.py
  • --onefile : 1つのexeファイルにする。
  • --noconsole : exe実行時にコンソール(DOS窓)を開かない。
  • --noupx : 実行ファイル圧縮ツール UPX を使わない。

distディレクトリ以下に、01_embed_pygamewindow.exe が作成された。実行したらちゃんと動作した。

ただ、ウインドウが開くまでちょっと時間がかかる。

PyInstaller で作成したexeは、実行すると C:\Users\USERNAME\AppData\Local\Temp\ 以下に仮ディレクトリを作成して、その中に実行に必要なファイルを全て解凍して、それらのファイルを実行しているらしい。また、アプリを終了すると、その仮ディレクトリも削除される。毎回実行するたびに、圧縮ファイルを解凍する処理が行われるので、起動が遅いのも当然だろうか。

ちなみに、一度 exeファイルに変換すると、.specファイルが作られる。その .specファイルの内容を修正して、pyinstaller hoge.spec とすれば、その .specファイルの内容に従って exeファイルが作成される。ただし、.spec を使わないで exe を作成すると、既に存在していた .spec は上書きされてしまうので注意。

余談。今回、実行ファイルを圧縮してファイルサイズを小さくしてくれる、UPX というツールを使わないように、--noupx を指定してみたのだけど。生成された exeファイルは、--noupx を指定しない場合と変わらないファイルサイズだった…。このオプション、実は機能していないのでは…?

py2exeでexe化。 :

Python 3.8.10 + py2exe 0.11.1.1 で exe化してみる。

変換したい .py と同じ階層に setup.py を作成。この setup.py の記述で、以下のような指定ができるらしい。
  • どのスクリプトを exe化するか。
  • 1つのファイルにするか、複数のファイルにするか。
  • 動作にはどんなモジュールが必要か。

_setup.py
from distutils.core import setup
import py2exe
option = {
    "packages": ["pygame", "tkinter", "win32gui"],
    "compressed": 1,
    "optimize": 2,
    "bundle_files": 1,
}
setup(
    options = {
        'py2exe': option,
    },
    console = [
        {'script': "01_embed_pygamewindow.py"}
    ],
    zipfile = None,
)

python setup.py py2exe と打てば、distディレクトリ以下にexeファイルが生成される。実行したところ、ちゃんと動作してくれた。なんとなくだけど、PyInstaller と比べると起動は若干速いような気がした。

ただ、今回、"bundle_files": 1, を指定して、1つのexeファイルにするように指定したはずが、そうはなってくれなかった。
  • distディレクトリ内には、pygame の動作に必要になるSDL関連dllファイルが9ファイルほどコピーされた。
  • dist/libディレクトリも作成されて、その中には tkinter の動作に必要なのであろう tcl と tk というフォルダがあり、中には1000ファイルほど入っていた。

> dir
...
2022/08/08  05:16    <DIR>          .
2022/08/08  05:16    <DIR>          ..
2022/08/08  05:16         9,367,824 01_embed_pygamewindow.exe
2022/08/08  05:16    <DIR>          lib
2021/05/03  11:54         3,406,016 libcrypto-1_1.dll
2021/05/03  11:54            32,792 libffi-7.dll
2021/05/03  11:54           690,368 libssl-1_1.dll
2022/08/08  01:48           300,544 SDL.dll
2022/08/08  01:48         2,326,016 SDL_image.dll
2022/08/08  01:48           165,888 SDL_mixer.dll
2022/08/08  01:48           623,616 SDL_ttf.dll
2021/05/03  11:54         1,705,120 tcl86t.dll
2021/05/03  11:54         1,468,064 tk86t.dll
              10 個のファイル          20,086,248 バイト
               3 個のディレクトリ  88,550,535,168 バイトの空き領域

> dir lib
...
2022/08/08  05:16    <DIR>          .
2022/08/08  05:16    <DIR>          ..
2021/05/08  06:29    <DIR>          tcl
2021/05/08  06:29    <DIR>          tk
               0 個のファイル                   0 バイト
               4 個のディレクトリ  88,550,535,168 バイトの空き領域

どうやら、py2exe を使って1つのexeファイルにするのは難しい模様…。となると、pygame でスクリーンセーバを作るとしたら、PyInstaller 一択のようだなと。

#2 [python][windows] Windows用スクリーンセーバの呼び出し動作をPythonで確認

Windows用スクリーンセーバを、Python を使って作ってみたい。

環境は、Windows10 x64 21H2 + Python 3.8.10 x64。

まずは、Windows用スクリーンセーバが呼び出される時、どんなオプションが渡されているのかを念のために調べておきたい。そこで、渡されたオプションを、デスクトップ上のテキストファイルに追記していくだけの Pythonスクリプトを書いてみた。

_01_scrsavchk.py
import os
import sys
import datetime
import time


def main():
    desktop_dir = os.path.expanduser('~/Desktop')
    log_file = os.path.join(desktop_dir, "temp.log.txt")
    dt_now = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")

    print("Screensaver option check")
    print("%s" % dt_now)
    print("look %s" % log_file)

    kind = "none"
    hwnd = 0
    if len(sys.argv) >= 2:
        s = sys.argv[1][0:2].lower()
        if s == "/s":
            # full screen
            kind = "fullscreen"
        elif s == "/c":
            # config / settings
            kind = "config"
        elif s == "/p":
            # preview
            kind = "preview"
            hwnd = int(sys.argv[2])

    with open(log_file, 'a') as f:
        f.write("[%s] %s , [%s]\n" % (dt_now, kind, " ".join(sys.argv)))

    time.sleep(2)


if __name__ == '__main__':
    main()

このスクリプトファイルを、PyInstaller を使ってexe化する。
pyinstaller --onefile --noupx 01_scrsavchk.py
distディレクトリに、01_scrsavchk.exe というexeファイルが作られた。

01_scrsavchk.exe を、01_scrsavchk.scr にリネーム。Windows用スクリーンセーバの実行ファイル、.scr の実体は、拡張子を .scr にリネームした .exeファイルなので…。

今回、Python 3.8.10 x64 (64bit版) でexe化したので…。
ということで、01_scrsavchk.scr を、C:\Windows\System32\ 以下にコピーした。

スクリーンセーバとして選んでみる。
スクリーンセーバ一覧の中に、01_scrsavchk という種類が増えている。これを選ぶと、DOS窓が開いて、2秒ほど経ったら閉じる。「設定」「プレビュー」ボタン等も押してみて様子を伺う。

デスクトップに、temp_log.txt というテキストファイルができているはず。中身を確認。
[2022/08/10 19:51:13] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114]
[2022/08/10 19:51:21] config , [C:\WINDOWS\system32\01_scrsavchk.scr /c:330168]
[2022/08/10 19:51:24] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114]
[2022/08/10 19:51:29] fullscreen , [C:\WINDOWS\system32\01_scrsavchk.scr /s]
[2022/08/10 19:51:32] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114]
[2022/08/10 19:51:41] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114]
これで、Windows10 がどんなオプションをつけてスクリーンセーバを呼び出しているのかが分かる。

そんなわけで、巷の解説通り、Windows用スクリーンセーバは、以下の3種類のオプションを渡した状態で実行されることが分かった。
また、「設定」「プレビュー」ボタンを押して実行・終了した直後、すぐさま /p xxxxx を渡して実行されていることも分かった。

Windows用のスクリーンセーバは多重起動を禁止するように作るべしと言われているけど、このあたりの呼び出しタイミングがシビア、ということだろうか…。それとも、既にフルスクリーンで起動してるのに、再度実行されてしまう場面もあり得るのだろうか…。

#3 [nitijyou] 日記ページをアップロード

2022/07/13を最後に日記をアップロードしてなかったのでアップロード。

2022/08/11(木) [n年前の日記]

#1 [gimp] GIMP 2.10で利用できるフォントフォーマットが減っていた

Windows10 x64 21H2 + GIMP 2.10.32 x64 Portable samj版を使っていて、いくつかのフォントが使えなくなっていることに気づいた。フォント名は選べるのだけど、フォントの形状が反映されない…。

gimp_font_ss01.png


問題を起こすフォントは、Type1フォント (.pfb) 群。GIMP 2.4 や 2.6 時代に追加フォントとして配布されていた多数の個性的なフォント達。

_GIMP Type1フリーフォント一覧


GIMP 2.8.22 Portable を起動して確認してみたところ、そちらはちゃんとフォントの形状が反映される。

gimp_font_ss02.png


samj版の問題だろうかと、PortableApps版の GIMP 2.10.32 Portable をインストールして確認してみたものの、そちらもフォントが反映されない。

_GIMP Portable (image editor) | PortableApps.com

gimp_font_ss03.png

どうやら GIMP 2.10.xx は、Type1フォントが利用できなくなった気配がする。

pangoのバージョンが新しくなったせいらしい。 :

色々ググってみたら、GIMP が使っている pango というライブラリが、数年前にType1フォントやビットマップフォントのサポートを打ち切っていたそうで。

_Font Changing - GIMP Chat
_Pango dropped support for Bitmap fonts with version 1.44 (was: Bitmap fonts are broken) (#4411) - Issues - GNOME / GIMP - GitLab
_Pango 1.44 and the removal of support for bitmap fonts (#386) - Issues - GNOME / pango - GitLab

最近の GIMP 2.10 は、その新しくなった pango を使うようになったので、Type1フォントやビットマップフォントが使えなくなったのだろう…。

数年前からそういう不具合というか、機能のダウングレードが起きてたのに、自分、今頃になって気づくとは…。

使えるバージョンを探してみた。 :

GIMP 2.10 は、必ず Type1フォント(.pfb)やビットマップフォントが使えない…というわけでもなく。ある時期までは使えていたらしい。

どのバージョンなら使えるのか、PortableApps の GIMP 2.10.xx Portable版を片っ端からインストールして調べてみた。Portable版は異なるバージョンも共存できるから、こういう動作確認もできるのでありがたい。

_PortableApps.com - Browse /GIMP Portable at SourceForge.net

  • GIMPPortable_2.10.12-3.paf.exe : Pass
  • GIMPPortable_2.10.14-1.paf.exe : Pass
  • GIMPPortable_2.10.18-2.paf.exe : Pass
  • GIMPPortable_2.10.20-1.paf.exe : Pass
  • GIMPPortable_2.10.22_Rev_2.paf.exe : Fail

つまり、GIMP Portable版の場合…。
  • GIMP 2.10.20-1 Portable までは、Type1フォントやビットマップフォントも使える。
  • GIMP 2.10.22 Rev2 Portable から、Type1フォントやビットマップフォントが使えなくなった。

今後サポートが復活する可能性は、望み薄だろうな…。

とりあえず、Type1フォントが使える最終版、GIMP 2.10.20-1 Portable をインストールして、フォントを使った画像を作成したい時はソレを利用することにしよう…。

いやまあ、自分の環境には GIMP 2.8.22 Portable もインストール済みなので、そちらを使う手もあるけれど。GIMP 2.8 は起動がとんでもなく遅いので…。自分の環境だと起動するまで数分かかる…。HDDからSSDに移しても起動時間の遅さは変わらなかった…。

pfbをttfに変換できないものか。 :

Type1フォント(pfbフォント)を、ttfフォントに変換できないものだろうか。それができれば、GIMP 2.10.22以降も従来のフォントが利用できるのだろうけど…。いやまあ、フォントのライセンス的にその変換は許されるのでしょうかという疑問もあるのだけど。

ライセンスの問題はともかくとして、FontForge あたりで変換できないものかな。

余談。 :

ググってたら以下のページに遭遇。

_DTPの2023年問題、知ってますか? 終末の日が近いType 1フォントの“遺産”にどう対応すべきか(1/3 ページ) - ITmedia NEWS

Adobe のせいか…。Flash もそうだけど、Adobe が何かを決めると皆で右往左往しちゃうな…。

2022/08/12追記。 :

Linu Mint 19.3上では、GIMP 2.10.18 の時点でType1フォントが使えなくなっているという話を見かけた。

_Removing fonts from GIMP 2.10.18 - Linux Mint Forums

Ubuntu 20.04 + GIMP 2.10.30 では /usr/share/fonts/type1/urw-base35/ 内の Type1フォントが全滅という話も。ただ、AppImage版の GIMP 2.10.14 はType1フォントが使えているらしい。

_Broken fonts after Ubuntu upgrade to 20.04
_PFB fonts are broken in certain applications and missing in others (Kubuntu 20.04) - Ask Ubuntu

Windows版、Linux版、Portable版等々でサポート状況は違ってるようで…。このバージョンなら大丈夫というわけでもないっぽい…。

#2 [gimp] GIMP 2.10 Portable は Python-Fu が使えなかった

GIMP 2.10.32 Portable は Type1フォント(.pfb) やビットマップフォントが使えなくなっていたので、そのあたりのフォントが利用できる GIMP 2.10.20-1 Portable (GIMPPortable_2.10.20-1.paf.exe) を Windows10 x64 21H2上でインストールしたのだけど。

_PortableApps.com - Browse /GIMP Portable at SourceForge.net

少し触っていたら、GIMP 2.10.20-1 Portable は Python-Fu が動かないことに気が付いた…。

原因。 :

原因を調べたら、pygimp.interp の記述がおかしかった。
(Gimp-Portableインストールフォルダ)\App\gimp\lib\gimp\2.0\interpreters\pygimp.interp
  • GIMP 2.10 は GIMP 2.8 とフォルダ構成が変わっているのに、pygimp.interp 内では GIMP 2.8 時代のフォルダ構成が指定されている。それでは動くわけがない。
  • 加えて、改行コードをLFにしておかないといけないのに、CRLF になってる。それでは GIMPが pygimp.interp の解析時に失敗してしまう。

やっかいなのは、この pygimp.interp、毎回GIMPの起動時に、GIMPPortable.ini 内の指定により上書きされてしまうこと。

パス指定だけ間違っているなら、GIMPPortable.ini 内のパス指定を修正すれば済むのだけど…。改行コードを LF で上書きしてほしいのに CRLF にされてしまうので、GIMPPortable.ini の修正だけでは対応できない。

対策。 :

そんなわけで、まずは GIMPPortable.ini を修正。関連してる行をコメントアウトして、pygimp.interp の上書きをさせないようにする。

(Gimp-Portableインストールフォルダ)\App\AppInfo\Launcher\GIMPPortable.ini

「pygimp」で検索して、関連がありそうな行の先頭に「; 」をつけてコメントアウト。
; [FileWrite15]
; Type=ConfigWrite
; File=%PAL:AppDir%\gimp\lib\gimp\2.0\interpreters\pygimp.interp
; Entry=python=
; Value=%PAL:AppDir%\gimp\Python\pythonw.exe
;
; [FileWrite16]
; Type=ConfigWrite
; File=%PAL:AppDir%\gimp\lib\gimp\2.0\interpreters\pygimp.interp
; Entry=/usr/bin/python=
; Value=%PAL:AppDir%\gimp\Python\pythonw.exe
;
; [FileWrite17]
; Type=ConfigWrite
; File=%PAL:AppDir%\gimp\lib\gimp\2.0\interpreters\pygimp.interp
; Entry=:Python:E::py:
; Value=:python:

次に、pygimp.interp を修正。
(Gimp-Portableインストールフォルダ)\App\gimp\lib\gimp\2.0\interpreters\pygimp.interp

例えば、GIMP 2.10.20-1 Portable のインストールフォルダが D:\GIMP_INSTALL_DIR\なら、pygimp.interp の内容は以下になる。
python=D:\GIMP_INSTALL_DIR\App\gimp\bin\pythonw.exe
python2=D:\GIMP_INSTALL_DIR\App\gimp\bin\pythonw.exe
/usr/bin/python=D:\GIMP_INSTALL_DIR\App\gimp\bin\pythonw.exe
/usr/bin/python2=D:\GIMP_INSTALL_DIR\App\gimp\bin\pythonw.exe
:Python:E::py::\GIMP_INSTALL_DIR\App\gimp\bin\pythonw.exe:

また、改行コードは LF にしておくこと。デフォルト状態では CRLF になっていて、ファイル内容の解析時に問題が起きる。

修正が終わったら、GIMPインストールフォルダでDOS窓を開いて、以下を打って GIMP Portable を起動。GIMP起動時の情報が別のDOS窓で表示される。
GIMPPortable.exe --verbose --console-messages

最初のほう、pygimp.interp を読み込んだあたりで「警告」等のメッセージが出ていなければ、修正は上手く行ってる。

Python-Fuコンソールが動作しない問題。 :

上記の修正をすることで、Python-Fuスクリプト群は動作して、メニューにも出てくるようになったけれど。相変わらず、Python-Fuコンソールが ―― フィルタ → Python-Fu → コンソール、がエラーを出して起動しない。

以下のファイルを修正する。
(GIMP Portableインストールフォルダ)\App\gimp\lib\gimp\2.0\plug-ins\python-console\python-console.py

最初の行のシバン行(shebang、#! から始まる行のこと)が「#!/usr/bin/env python2」になってるのがよろしくないのだろう。*NIXならともかく、Windowsに /usr/bin/env というプログラムなんて存在しないので、エラーになるのではなかろうか。

なので、「#!/usr/bin/python2」もしくは「#!/usr/bin/python」に書き換えてやれば、pygimp.interp に記述した「/usr/bin/python2=」「/usr/bin/python=」の指定が反映されて動いてくれると思う。たぶん。少なくとも自分の手元の環境では、こういった修正で、Python-Fuコンソールが動いてくれた。

関連情報。 :

_2018/08/10 - GIMP 2.10.4 Portable をインストール
_2018/08/11 - GIMP 2.10.4 Portable の pygimp.interp がおかしい

GIMP 2.10.4 Portable の頃の不具合が、GIMP 2.10.20 Portable になっても修正されてなかったということだろうけど…。これひょっとして、「誰も消防車を呼んでいないのである!」案件だったりするのだろうか…。もっとも、そもそもどこに連絡すればいいのかも分からんのだけど。さすがに最新版は直ってるのだろうか。どうなんだろ。

2022/08/12(金) [n年前の日記]

#1 [pygame][windows] スクリーンセーバと多重起動抑止

Windows10 x64 21H2上で、Python + pygame を使ってスクリーンセーバ(.scr)を作れないものかと実験しているのだけど、なかなか難しいことがちょっとだけ分かってきた。多重起動を抑止する処理が重要になってくるんだなと…。

プレビューモードの問題。 :

Windows用のスクリーンセーバは、スクリーンセーバ設定画面のプレビュー窓の中に表示させるために、コマンドラインオプションとして「/p xxxx」を指定して実行される場合があるのだけど。この呼び出しがとにかく多い。

  • スクリーンセーバの設定画面を開いて、そのスクリーンセーバが選択済みなら、/p xxxx 付きで呼ばれる。
  • リストから選択した際も、/p xxxx で呼ばれる。
  • 「設定」ボタンをクリックして、設定ダイアログを表示した後にも呼ばれる。
  • 「プレビュー」ボタンをクリックしてフルスクリーン表示をして、そこから抜けた際にも呼ばれる。

とにかく、何かしらちょっと操作する度に、Windows はスクリーンセーバを /p xxxx 付きで実行するので…。もし、多重起動を抑止する処理を何も入れてないと、そのスクリーンセーバのプロセスが3つも4つも大量に走っている状態になる…。

例えばそのスクリーンセーバが、プレビュー窓に対して、何かしらのアニメーションを延々描画し続けるような作りだと…。

各プロセスが、「俺がこの窓に描き込むぞ」「いや、俺が描き込む」「いやいや、俺が」「いや待て。ここは拙者が」「僕も描くよ」「私だって」てな状態になって…。プレビュー窓内のアニメーションが、それはもうグチャグチャな状態に…。動作確認中、その奪い合いぶりを目にしてかなり慌ててしまった。見るからにヤバイ。

そんなわけで、/p xxxx 付きで実行された場合は、多重起動抑止処理が絶対に必要になるのだなと分かってきた…。

ただ、この件については、プレビュー窓内でアニメーションをさせようとするからおかしなことになるような気もする。

例えば、/p xxxx で実行された際は、プレビュー窓内に画像1枚だけを描画して即座に終了する作りにすれば、プロセスが大量に発生して残り続けることも避けられるのではなかろうか。そういう作りなら、多重起動抑止の処理も不要かなと想像しているのだけど、さてどうだろう。

待ち時間の問題。 :

もう一つ問題がある。Windows用スクリーンセーバの作成方法について書いてある記事を眺めてたら、ちょっと怖いことが書いてあった。

例えば、スクリーンセーバの待ち時間を10分に設定してあって、かつ、何の操作もしてない状態がずっと続いたら…。Windowsは、スクリーンセーバが実行されていようといまいと、10分置きにスクリーンセーバの実行を指示するそうで。

その話が本当ならヤバイ。試しに、先日作成した動作チェック用の .scr を使って、待ち時間を1分にして様子を伺ってみたら、たしかに1分間隔でスクリーンセーバが何度も何度も実行されていた…。

_Windows用スクリーンセーバの呼び出し動作をPythonで確認 - mieki256's diary

[2022/08/12 00:03:28] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 262910]
[2022/08/12 00:04:40] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:05:43] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:06:46] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:07:49] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:08:53] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:09:56] fullscreen , [C:\WINDOWS\system32\01_SCR~1.SCR /s]
[2022/08/12 00:10:04] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 1049354]

そんなわけで、/s をつけてフルスクリーン表示を要求された際も、多重起動抑止処理が絶対に必要になりそうだなと。

ちなみに、自分が今まで作成したスクリーンセーバモドキは、そういう処理を一切入れてなかったので…。これは非常にマズイなと今頃になって気づかされた…。あくまで、モドキでしかなかった…。

多重起動抑止の種類。 :

多重起動の抑止処理は、スクリーンセーバ1つにつき1種類で十分、というわけでもないっぽい。

というのも、スクリーンセーバ設定画面のプレビュー窓にスクリーンセーバが表示されている状態で、フルスクリーン表示のスクリーンセーバが走る場面もあるそうで…。

その場合、既にプレビュー窓でプロセスが走っているから、フルスクリーン表示用のプロセスは走ってくれないことになってしまう。「プレビュー」ボタンをいくらクリックしても、何の変化も起きない状態になる。

つまり、プレビューモード用と、フルスクリーン表示用で、別々に多重起動抑止処理を入れておかないといけないようだなと…。

多重起動抑止の方法。 :

多重起動抑止処理はどうやって作ればいいのか少しググったけれど、Windowsの場合は Mutex なるものが使えるらしい。

他にも、ロックファイルを作成して、ロックファイルが存在するなら既にプロセスが走ってるから後続は起動させないというやり方もあるらしいけど。もし、ロックファイルを作ったプロセスが異常終了してしまうと、ロックファイルが残り続けて、その後ずっとプロセスが走らなくなる問題もあるそうで。

そんなわけで、Mutex なるソレを Pythonから利用する方法について調べ始めているところ。ctypes というモジュールを使えば実現できるっぽいけど…。

2022/08/13() [n年前の日記]

#1 [gimp] pfbフォントをttfフォントに変換してみたい

最近の GIMP 2.10 は Type1フォント(.pfb)を扱えなくなっていたわけだけど。

_GIMP 2.10で利用できるフォントフォーマットが減っていた

.pfb を .ttf に変換できないものだろうかと疑問が湧いた。FontForge でどうにかできないものか。

FontForgeで変換。 :

ちょっとググってみたところ、FontForge のチュートリアルページに、そのものズバリのスクリプトが紹介されてた。

_FontForge スクリプトのチュートリアル
_fontforgeでotfからttfに変換する - 俺の外付けHDD
_fontforgeによるotf->ttf変換 - Qiita

試しに、Ubuntu Linux 20.04 LTS上で作業してみた。ちなみに、Ubuntu 20.04 は Windows10 x64 21H2 + VMware Player上で動かしている。

まず、.pfbファイル群が置いてある場所で、conv.pe というスクリプトを作成する。
vi conv.pe

内容は以下。
#!/usr/bin/fontforge
i=1
while ( i<$argc )
  Open($argv[i])
  Generate($argv[i]:r+".ttf")
  i = i+1
endloop

chmod +x conv.pe で実行権限をつける。

以下で、.pfbファイル群に対して、まとめて変換。
./conv.pe *.pfb

.pfb と同じ場所に、.ttf が生成された。

Windows10 x64 21H2 側で、各ttf を右クリック → プレビュー、を選んだところ、それらしく変換されているように見えた。

まあ、フォントのライセンスの問題で、.pfb から変換した .ttf を使ってしまっていいのかどうかは、ちょっと分からないのだけど…。

gimp-freefonts の入手先。 :

GIMP に追加できる Type1フォントの一覧は以下で確認できるけど。

_GIMP Type1フリーフォント一覧

紹介されているこれらのフォントは、元々どこから入手できるのか気になった。その入手先に行けば、ライセンスも分かるのではないかと…。

色々ググってみたら、今も GIMP のダウンロードページから入手できなくもないらしい。

_Index of /mirror/pub/gimp/fonts

freefonts-0.10.tar.gz が元ファイル。DLして解凍してみたら、各フォントに対して .license というファイルも同梱されていた。

ちなみに、同梱されていた .license、.tgz等々を、GIMPのフォントフォルダに .pfb と一緒に入れておいたら、GIMP がフォントを読み込む際にエラーが発生してしまった。.pfb 以外は別のフォルダに移動しておいたほうが良さそう。削除してしまうと後からライセンスが分からなくなるので…。

ちなみに、前述のダウンロード先には、urw-fonts.tar.gz というファイルもあった。中を眺めたら、Ghostscript関係で使われることが多い、URW base35 と呼ばれるフォント群だった。また、.pfb の他に、.afm、.pfm も入っていた。

余談。各 .licenseファイルを眺めてみたところ、一応、商業利用しない限りは配布は自由と書いてあるものの…。
  • 「フリーウェアだよ。好きにしていいよ」
  • 「フリーウェアだけど君の故郷の絵葉書を送ってくれ」
  • 「何か黒い絵葉書を送ってほしい」
  • 「こまめに部屋の掃除と洗濯をしてるなら使うことを許そう」
  • 「使ったら○○大学に寄付をせよ」
  • 「シェアウェアだぞ。$25払え」
  • 「$15払えばカーニングを整えたフォントを送るで」
1995年頃に配布されていたフォントファイルらしく、ライセンス内容がてんでバラバラだった。

URW-base35の入手先。 :

URW-base35 は本来どこから入手できるのだろう。

「Ghostscript fonts」でググれば辿り着けそう。

_Ghostscript fonts - Browse /gs-fonts at SourceForge.net
_Ghostscript fonts Japanese Information - OSDN

ただ、gnu-gs-fonts-std-6.0.tar.gz をダウンロードして解凍してみたところ、中には .afm、.pfb、.pfm しか入ってなかった。

.ttf は無いのだろうか…。

ググっていたら、以下に辿り着いた。

_Index of /download

std35ttf.zip というのが .ttf版らしい。

以下のページも見かけた。

_ArtifexSoftware/urw-base35-fonts: Repo for URW++ base 35 font set
_urw-base35-fonts/fonts at master - ArtifexSoftware/urw-base35-fonts

.ttf も並んでいる。ただ、ダウンロードして確認してみたところ、フォント名が std35ttf.zip のソレと違っているように見えた。

HDDの中を探してみたら、昔は urw-gs35-tt.zip というファイルもWebから入手できたらしい。.ttf をまとめて .ttc にしてある模様。ただ、今はもう配布先のblogが消滅していて入手できない。WebArchive にもblogのトップページしか残ってなかった。

_Recycler (WebArchive)

.zip の中を覗いたら、GPL云々と書いてあった。であれば、そっくりそのままの状態なら配布しても良さそうな気がする。ということで一応バックアップを兼ねて、 _ココ に置いときます。

2022/08/14() [n年前の日記]

#1 [python] Pythonスクリプトの多重起動を抑止したい

Windows10 x64 21H2 + Python 3.8.10、3.9.13 上で、Pythonスクリプトの多重起動を抑止したい。

参考ページ。 :

以下のページを参考にして試してみた。

_Creating a single instance application < Python recipes < ActiveState Code
_Ptyhonでプロセス間排他を試す(Windows限定) | The only neEt thing to do.
_windowsで実行中のプロセスのフルパスを、pythonから調べたい
_多重起動禁止処理 for Windows
_benhoyt/namedmutex: namedmutex.py, a simple ctypes wrapper for Win32 named mutexes
_Windows上で多重起動を防止する方法
_Module win32event

Windowsの場合、Mutex なるものを使うと多重起動してるかどうかを判別することができる模様。キーワードとして「python CreateMutex」でググれば情報に辿り着けそう。

その Mutex を利用する方法として、以下の2つがあるようで。
  • win32event、win32api、winerror等を経由して呼び出す。
  • ctypes モジュールで呼び出す。

ソースその1。win32event版。 :

まずは win32event等を経由して使う方法を試してみた。動作には pywin32 のインストールが必要。今回は pywin32 304 がインストールされた。

pip install pywin32

_01_mutex.py
import win32event
import win32api
import winerror
import win32security
import time
import sys

MUTEXNAME = "python_mutex_sample_01"


def main():
    sa = win32security.SECURITY_ATTRIBUTES()
    sa.SECURITY_DESCRIPTOR.SetSecurityDescriptorDacl(True, None, False)

    mtx = win32event.CreateMutex(sa, False, MUTEXNAME)
    err = win32api.GetLastError()
    if not mtx or err == winerror.ERROR_ALREADY_EXISTS:
        # Process exists
        print("%s already exists" % MUTEXNAME)
        sys.exit(0)
    else:
        # New process
        print("New process.")
        for i in range(10):
            print(i)
            time.sleep(1)

    if mtx:
        # win32event.ReleaseMutex(mtx)
        # win32api.CloseHandle(mtx)
        mtx.Close()


if __name__ == '__main__':
    main()
  • Mutex を作る際は、他のプロセスと被らないような独自の文字列を渡す。
  • CreateMutex() を呼んだ直後にエラー情報を調べることで、その Mutex が既にあるかどうかが分かる模様。
  • 処理が終わったら、mtx.Close() を呼んでハンドルをクローズする。

python 01_mutex.py で実行すると、1秒毎に、0 から 9 までの数字を出す。

DOS窓を複数開いておいて、どこかのDOS窓でスクリプトを動かしてから、すかさず別のDOS窓で同じスクリプトを動かしてみると、後から実行したほうは「〜 already exists」と出力して即座に終了してくれた。たしかに、多重起動を禁止するスクリプトになってくれた模様。

ちょっとよく分からなかったのが、win32event.ReleaseMutex(mtx) を呼ぶと必ずエラーになること。これは呼ばなくてもいいのだろうか…? クローズ処理として win32api.CloseHandle(mtx) か mtx.Close() は呼んでおくらしいけど…。巷のサンプルを眺めても、win32event.ReleaseMutex(mtx) を呼んでる事例は見かけなかったので、呼ばなくてもいいのかもしれない。

ソースその2。ctypes版。 :

ctypesを使って呼び出す方法も試してみた。

_02_mutex_ctypes.py
import ctypes
import time

MUTEXNAME = "python_mutex_sample_02"


def main():
    knl32 = ctypes.windll.Kernel32
    mtx = knl32.CreateMutexA(0, 1, MUTEXNAME)
    result = knl32.WaitForSingleObject(mtx, 0)
    if result != 0:
        print("%s already exists" % MUTEXNAME)
    else:
        print("New process.")
        for i in range(10):
            print(i)
            time.sleep(1)

    knl32.ReleaseMutex(mtx)
    knl32.CloseHandle(mtx)


if __name__ == '__main__':
    main()

インポートするモジュール数は少なくなってくれた気がする。ただ、ソースが少し分かりづらく…。いや、あまり違いはないか…。

ctypes 経由で呼び出す版は、最後に .ReleaseMutex() と .CloseHandle() を呼び出してもエラーにならなかった。

#2 [python][ptgame][windows] pygameでWindows用スクリーンセーバを作る

Windows10 x64 21H2 + Python 3.8.10 64bit + pygame 1.9.6 を使って、Windows用のスクリーンセーバが作れそうかどうか試してみた。

動作画面は以下のような感じ。解像度が低過ぎてアレだけど、雰囲気ぐらいは分かるかなと。また、かなり後で話が出てくるけれど、プレビューが表示されるまで妙に待たされてることも分かるかと思う。

制限事項。 :

pygame でWindows用スクリーンセーバを作成するにあたって、いくつか制限事項(?)がある。
  • pygame は 1.9.x であること。pygame 2.x.x は、環境変数 SDL_WINDOWID が反映されないので作れない。(pygame 1.x.x はSDL1.xを、pygame 2.x.x はSDL2を使っていて、SLD2 は件の環境変数が反映されない。)
  • pygame 1.9.x の最終バージョン、pygame 1.9.6 は、Python 3.8 までの対応なので、pygame 1.9.6 を使いたいなら、Python 2.7 - 3.8 のどれかで作ることになる。

Windows用スクリーンセーバについてのおさらい。 :

Windows用スクリーンセーバ(.scr)はどんな仕様を要求されるのか、念のために再度列挙してみる。

  • Windows用スクリーンセーバは、.exe を .scr にリネームしたもの。

コマンドラインオプションについて。
  • .scr は、3種類のコマンドラインオプション、「/s」「/c:xxxx」「/p xxxx」のどれかが渡されて実行される。
  • .scrファイルを右クリックして出てくるメニューから、「テスト」「構成」等を選んだ場合、コマンドラインオプションがつかない状態で実行される場合もある。
  • /s、/c、/p は、大文字だったり小文字だったりするので、プログラム側で大文字化、または小文字化をしてから判別したほうが良い。
  • /s はフルスクリーン表示モード。キーボード操作やマウス操作を検出して、操作があったら自分で終了するように作っておく。
  • /c:xxxx は、そのスクリーンセーバの設定ダイアログ表示モード。本来は設定内容を .ini やレジストリ等に記録することになるけれど、特に設定項目が無いなら、スクリーンセーバ名を表示するダイアログが出るだけで十分。
  • /p xxxx は、スクリーンセーバ設定画面のプレビュー窓の中に表示するモード。xxxx はプレビュー窓のウインドウハンドル。

「/p xxxx」にも対応させることを考えると、以下の条件を満たすプログラミング言語/ライブラリ/フレームワークなら、Windows用スクリーンセーバを作れることになる。
  • 与えられたウインドウハンドルからウインドウサイズを取得できて、
  • そのウインドウハンドルのウインドウに対して描画ができる。

多重起動禁止処理について。
  • スクリーンセーバ設定画面で何かしら操作をすると、その都度 /p xxxx つきでスクリーンセーバが何度も何度も実行されるので、特に何もしないと同じ処理をするプロセスが2つも3つも走ってしまう。多重起動を抑止する処理が必要になる。
  • スクリーンセーバが実行されるまでの時間の間隔で、スクリーンセーバが何度も実行される可能性がある。フルスクリーン表示時も、一応、多重起動の禁止処理を入れておいたほうが安心。

インストール場所について。
  • Windows が64bit版、スクリーンセーバが32bit版のプログラムなら、C:\Windows\SysWOW64\ 以下に .scr をコピーする。
  • Windows が64bit版、スクリーンセーバが64bit版のプログラムなら、C:\Windows\System32\ 以下に .scr をコピーする。
  • Windows が32bit版なら、C:\Windows\System32\ 以下に .scr をコピーする。

開発環境。 :

今回は、Windows10 x64 21H2上で Python 3.9.13 64bit版や 3.8.10 64bit版をインストールしてある状態で、virtualenv を使って Python 3.8 64bit の開発環境を用意してから作業した。

後々、PyInstaller や Nuitka を使って、.py ファイルをexe化するわけだけど。不要なモジュールまで exe に含められるのは困るので、必要最低限のモジュールしか入っていないクリーン(?)な環境を用意できたほうが exe化するには都合がいい。まあ、virtualenv ではなく、venv 等を使っても同じことはできると思うけど…。

virtualenv のインストールと、Python 3.8 の開発環境を作成。
python -m pip install -U setuptools virtualenv
virtualenv -p python3.8 venv38

開発環境の有効化。
.\env38\Scripts\activate

Pythonのバージョンを確認。
python -V
(venv38) ... > python -V
Python 3.8.10

ちなみに、開発環境の無効化は以下。
deactivate

必要なモジュールをインストール。 :

スクリーンセーバを作る際に必要になるモジュールをインストール。pygame、pywin32 と、exe化するための何かしらが入っていれば済む。
python -m pip install pygame==1.9.6
python -m pip install pywin32
python -m pip install pyinstaller
python -m pip install nuitka zstandard orderedset

手元の開発環境では、以下が入っている。余計なモジュールも入ってるけど…。(Pythonスクリプトの整形ツール、autopep8 とか…。)
(venv38) ... > python -m pip list

Package                   Version
------------------------- ---------
altgraph                  0.17.2
autopep8                  1.7.0
cachetools                5.2.0
future                    0.18.2
Nuitka                    1.0.3
orderedset                2.0.3
pefile                    2022.5.30
pip                       22.2.2
pycodestyle               2.9.1
pygame                    1.9.6
pyinstaller               5.3
pyinstaller-hooks-contrib 2022.8
pywin32                   304
pywin32-ctypes            0.2.0
setuptools                63.4.1
toml                      0.10.2
wheel                     0.37.1
zstandard                 0.18.0

ソース。 :

Python 3.8 + pygame 1.9.6 + pywin32 で作成した、Windows用スクリーンセーバのソースは以下。画面の中を赤いボールが跳ね回るだけの、いつもの処理内容。

_scrsavpygame.py
import ctypes
import datetime
import math
import os
import sys
import time
import win32api
import win32event
import win32gui
import win32security
import winerror

DBG = False

IMG_NAME = "resources/ball_64x64.png"

PREVIEW_BG_IMG = "resources/preview_bg.png"
PREVIEW_LOGO_IMG = "resources/preview_logo.png"

APPLI_NAME = "screensaver sample by using pygame"
VER_NUM = "0.0.1"

MUTEX_NAME_FILLSCR = "screensaver_pygame_fullscr"
MUTEX_NAME_CONFIG = "screensaver_pygame_config"
MUTEX_NAME_PREVIEW = "screensaver_pygame_preview"


def resource_path(filename):
    """Get resource file path."""
    if hasattr(sys, "_MEIPASS"):
        # use PyInstaller
        base_dir = sys._MEIPASS
    else:
        base_dir = os.path.abspath(".")

    return os.path.join(base_dir, filename)


def full_screen(screen):
    """drawing pygame window."""

    # invisivle mouse cursor
    pygame.mouse.set_visible(False)

    # load image
    img = pygame.image.load(resource_path(IMG_NAME)).convert()
    img.set_colorkey(img.get_at((0, 0)), pygame.RLEACCEL)

    # initialize work
    scrw, scrh = screen.get_width(), screen.get_height()
    x, y = scrw / 2, scrh / 2
    dx = float(scrw) / (60 * 2)
    dy = float(scrh) / (60 * 3)

    clock = pygame.time.Clock()
    counter = 0
    looping = True

    # main loop
    while looping:

        # check key, mouse
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                looping = False
            if event.type == pygame.KEYUP:
                # any key up
                looping = False
            if event.type == pygame.MOUSEMOTION:
                # move mouse
                if counter >= 120:
                    looping = False

        if not looping:
            break

        # update sprite position
        w, h = img.get_width(), img.get_height()
        x += dx
        y += dy
        if x <= (w / 2) or x >= scrw - (w / 2):
            dx *= -1
        if y <= (h / 2) or y >= scrh - (h / 2):
            dy *= -1

        # clear bg
        screen.fill((16, 16, 16))

        # draw sprite
        px, py = int(x - (w / 2)), int(y - (h / 2))
        screen.blit(img, (px, py))

        counter += 1

        pygame.display.flip()
        clock.tick(60)

    # visible mouse cursor
    pygame.mouse.set_visible(True)


def preview(screen):
    frames = 360 * 2

    bgimg = pygame.image.load(resource_path(PREVIEW_BG_IMG)).convert()
    logoimg = pygame.image.load(resource_path(PREVIEW_LOGO_IMG)).convert()
    logoimg.set_colorkey(logoimg.get_at((0, 0)), pygame.RLEACCEL)

    clock = pygame.time.Clock()
    ang = 0
    looping = True
    for i in range(frames):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                looping = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    looping = False

        if not looping:
            break

        scrw, scrh = screen.get_width(), screen.get_height()

        screen.fill((100, 150, 200))
        iw, ih = bgimg.get_width(), bgimg.get_height()
        px = int((scrw - iw) / 2)
        py = int((scrh - ih) / 2)
        screen.blit(bgimg, (px, py))

        iw, ih = logoimg.get_width(), logoimg.get_height()
        px = int((scrw - iw) / 2)
        py = int((scrh * 0.15) * math.sin(math.radians(ang)) + (scrh / 2) - (ih / 2))
        screen.blit(logoimg, (px, py))

        ang += 3

        pygame.display.flip()
        clock.tick(60)


def get_window_size(hwnd):
    """Get window size."""
    rect = win32gui.GetWindowRect(hwnd)
    x0, y0, x1, y1 = rect[0], rect[1], rect[2], rect[3]
    return ((x1 - x0), (y1 - y0))


def msg_box(msg, title):
    """Display MessageBox by Windows API."""
    user32 = ctypes.WinDLL("user32")

    # user32.MessageBoxW.restype = ctypes.c_int32
    # user32.MessageBoxW.argtypes = (ctypes.c_void_p, ctypes.c_wchar_p,
    #                                ctypes.c_wchar_p, ctypes.c_uint32)

    user32.MessageBoxW(0, msg, title, 0x00000040)


def write_log(s):
    global DBG
    if DBG:
        desktop_dir = os.path.expanduser('~/Desktop')
        log_file = os.path.join(desktop_dir, "temp.log.txt")
        dt_now = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
        with open(log_file, 'a') as f:
            f.write("[%s] %s\n" % (dt_now, s))


def get_mutex(name):
    sa = win32security.SECURITY_ATTRIBUTES()
    sa.SECURITY_DESCRIPTOR.SetSecurityDescriptorDacl(True, None, False)
    mtx = win32event.CreateMutex(sa, False, name)
    err = win32api.GetLastError()
    if not mtx or err == winerror.ERROR_ALREADY_EXISTS:
        return (mtx, False)
    return (mtx, True)


def close_mutex(mtx):
    global cmdopt
    if mtx:
        # win32event.ReleaseMutex(mtx)
        # win32api.CloseHandle(mtx)
        mtx.Close()
        write_log("%s : Close mutex. Exit." % cmdopt)


# ----------------------------------------
# parse commandline option
kind = "None"
hwnd = 0
if len(sys.argv) >= 2:
    s = sys.argv[1][0:2].lower()
    if s == "/p":
        kind = "preview"
        hwnd = int(sys.argv[2])
    elif s == "/c":
        kind = "config"
    elif s == "/s":
        kind = "fullscreen"

cmdopt = " ".join(sys.argv)

if kind == "config":
    # ----------------------------------------
    # config / setting
    mtx, success = get_mutex(MUTEX_NAME_CONFIG)
    if not success:
        # Process exists
        write_log("%s : %s already exists. Exit." % (cmdopt, MUTEX_NAME_CONFIG))
        sys.exit(0)

    # new process
    write_log("%s : New process." % cmdopt)
    msg_box("%s\nver.%s" % (APPLI_NAME, VER_NUM), "Information")
    close_mutex(mtx)
    sys.exit()

if kind == "preview":
    # ----------------------------------------
    # preview
    mtx, success = get_mutex(MUTEX_NAME_PREVIEW)
    if not success:
        write_log("%s : %s already exists. Exit." % (cmdopt, MUTEX_NAME_PREVIEW))
        sys.exit(0)

    os.environ['SDL_VIDEODRIVER'] = 'windib'

    if hwnd != 0:
        # set SDL_WINDOWID
        os.environ['SDL_WINDOWID'] = str(hwnd)
        wdw_size = get_window_size(hwnd)
        write_log("Windows size: %d x %d" % (wdw_size))
    else:
        wdw_size = (152, 112)

    write_log("%s : New process. hwnd = %d , Wdw size = %d x %d" %
              (cmdopt, hwnd, wdw_size[0], wdw_size[1]))

    import pygame  # nopep8

    pygame.init()
    screen = pygame.display.set_mode(wdw_size)
    preview(screen)
    pygame.quit()
    close_mutex(mtx)
    sys.exit()

# ----------------------------------------
# full screen
mtx, success = get_mutex(MUTEX_NAME_FILLSCR)
if not success:
    # Process exists
    write_log("%s : %s already exists. Exit." % (cmdopt, MUTEX_NAME_FILLSCR))
    sys.exit(0)

# New process
write_log("%s : New process." % cmdopt)

import pygame  # nopep8

pygame.init()
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
full_screen(screen)
pygame.quit()
close_mutex(mtx)
sys.exit()

使用画像は以下。scrsavpygame.py と同階層に、resources というディレクトリを作成して、その中に入れておく。

_ball_64x64.png
_preview_bg.png
_preview_logo.png
(venv38) ... > tree /A /f

D:.
|   scrsavpygame.py
|
+---resources
        ball_64x64.png
        preview_bg.png
        preview_logo.png

ソースについて少し説明。 :

/p xxxx を渡して実行した時(プレビューモード)の動作は、一定時間アニメーションを表示して、その後終了する作りにしてみた。

当初、与えられたウインドウハンドルに対して何かを描画して即座に終了する処理で十分だろうと思っていたのだけど。その方法を実際に試してみたら、描画内容が全く反映されず。どうやら数秒ほど延々と描画を続けてみないと、見た目が反映されないようだなと…。何故かは分からないけれど…。

しかし、そのままいつまでもアニメーションを描画し続けるわけにもいかなくて。スクリーンセーバ設定画面が閉じられた際、スクリーンセーバも終了しなければいけないはずだけど、どうすればスクリーンセーバ設定画面の終了を検出できるのか、そこが分からない。仕方なく、一定時間が経過したら問答無用で終了してしまうことにしてお茶を濁した。このあたり、解決策はあるのだろうか…?


多重起動抑止については、以下を参照のこと。

_Pythonスクリプトの多重起動を抑止したい


環境変数 SDL_WINDOWID を使って、pygame のウインドウを埋め込む処理については、以下を参照のこと。

_tkinterの中にpygameを埋め込む

Pythonスクリプトとして動作確認。 :

動作確認は以下。
python scrsavpygame.py /s
python scrsavpygame.py /c
python scrsavpygame.py /p 0
python scrsavpygame.py

「/p 0」を渡したときは、仮で小さなウインドウを生成して、その中にプレビュー窓用の内容をテスト描画するようにしてみた。本番では、例えば「/p 1049354」のように、何かしらの大きな数値(実在するウインドウハンドル)が渡されるはず。

exeに変換。 :

Windows用スクリーンセーバとして利用するためには、ファイルの拡張子が .scr でなければならず、その .scr は .exe をリネームして作られるわけで…。つまり、この Pythonスクリプトをexe化しないと、スクリーンセーバとして利用できない。

今回は PyInstaller 5.3 を使ってexe化してみた。
python -m pip install pyinstaller -U

まずは、以下を打って変換する。
pyinstaller --onefile --noconsole --noupx scrsavpygame.py

distディレクトリが作られて、その中に scrsavpygame.exe というファイルが出来上がった。また、scrsavpygame.spec というファイルも生成された。

ただ、この状態では、動作に必要な画像ファイル群が ―― resources/*.png が同梱されていない。

scrsavpygame.spec を編集して、画像ファイル群も同梱させる指定をする。

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,

↓

exe = EXE(
    pyz,
    Tree('resources',prefix='resources'),
    a.scripts,
    a.binaries,

「Tree('resources',prefix='resources'),」という行を挿入することで、「resourcesディレクトリも同梱せよ」と指示してる。

今度は、.specファイルを指定してexe化する。
pyinstaller scrsavpygame.spec

dist/scrsavpygame.exe を実行して動作確認をしておく。
scrsavpygame.exe /s
scrsavpygame.exe /c
scrsavpygame.exe /p 0
scrsavpygame.exe

動作したら、.scr にリネームコピー。
copy scrsavpygame.exe scrsavpygame.scr

出来上がった .scr を、既定の場所にコピーしてインストールする。
  • Windows が64bit版、スクリーンセーバが32bit版のプログラムなら、C:\Windows\SysWOW64\ 以下に .scr をコピーする。
  • Windows が64bit版、スクリーンセーバが64bit版のプログラムなら、C:\Windows\System32\ 以下に .scr をコピーする。
  • Windows が32bit版なら、C:\Windows\System32\ 以下に .scr をコピーする。

スクリーンセーバとして動作確認。 :

スクリーンセーバの設定画面を呼び出して、「scrsavpygame」が増えているか確認する。Windows10における、スクリーンセーバ設定画面の呼び出し方は以下。
  • デスクトップの何もないところを右クリック → 個人用設定 → ロック画面 → スクリーンセーバー設定。
  • あるいは、Windows10の左下の検索欄に「スクリーンセーバー」と打てば、「スクリーンセーバーの変更」という項目が出てくるので、それを選んでもいい。

スクリーンセーバ設定画面が表示されるまで、妙に待たされた気もするけれど、それでも一応動作してくれた。

そんなわけで、Python 3.8 + pygame 1.9.6 + pywin32 を使ってWindows用スクリーンセーバを作れそうだ、と分かった。

問題点。 :

一応それらしく動いたものの。現状では、ちょっと問題が…。

スクリーンセーバの設定画面で「scrsavpygame」を選ぶと、プレビュー窓の中に何かが表示されるまで随分待たされる…。おそらくだけど、以下が原因なのではなかろうか。
  • PyInstaller で作成された .exe は、毎回一時フォルダに解凍する処理が走るので、起動がとにかく遅い。
  • 毎回、Windows Defender によるウイルスチェックで時間がかかっているのかもしれない。

更に、出来上がった .scr に対して、Windows Defender が「これはウイルスだ!」と誤判定してきた。

PyInstaller を使って生成した .exe はウイルスとして誤判定されやすいらしい。巷のウイルスが PyInstaller等を使って作られているせいで、ウイルス対策ソフトが「PyInstaller で作られた .exe はウイルス」と決めつけてる節があるそうで。

もちろん、こうして自分で作ったプログラムだから、ウイルスなわけもなく…。Windows Defender に「このファイルはウイルス判定を除外せよ」と設定して使うしかなさそう。

ただ、出来上がった .scr を配布したいと思った場合はフツーに困る…。ダウンロードした人達が「これ、ウイルスじゃん!」と慌てちゃうだろうな…。

Nuitkaでexe化してみる。 :

Pythonスクリプトをexe化するツールは、PyInstaller、py2exe、cx_Freeze など色々あるけれど。Pythonスクリプトを一旦C言語のソースに変換して、gccでコンパイルしてexeを作ってくれる Nuitka なるツールがあるそうで。PyInstaller で作ったexeより起動が速く、ファイルサイズも小さくなる傾向があるとのこと。興味が湧いた。試してみる。

インストールは以下。
python -m pip install nuitka zstandard orderedset

自分の環境では、orderedset というモジュールもインストールしないと、処理が先に進まなかった。

Nuitka用に修正したソースは以下。

_scrsavpygame.py

変更点は2ヵ所。
  • 一行目のシバン(shebang)を、「#!python」から「#!/usr/bin/python3.8」に変更。この変更をしないと、「Pythonのバージョンが違う」と文句を言ってきて処理が進まなかった。
  • 画像ファイル(リソースファイル)を読み込む際の、ファイルパスの求め方を修正。

リソースファイルパスを求める部分は以下になる。
def resource_path(filename):
    """Get resource file path."""
    if False:
        if hasattr(sys, "_MEIPASS"):
            # use PyInstaller
            base_dir = sys._MEIPASS
        else:
            base_dir = os.path.abspath(".")
    else:
        base_dir = os.path.dirname(__file__)
    return os.path.join(base_dir, filename)
「if False:」で無効化してあるところが、PyInstaller、あるいは通常のPythonスクリプトとして実行する際の処理。Nuitka用は、os.path.dirname(__file__) で基準ディレクトリ(?)を求めることになる模様。

exeへの変換は以下。
python -m nuitka --mingw64 --windows-disable-console --include-data-file=".\\resources\\*.png=.\\resources\\" --onefile scrsavpygame.py
  • python -m nuika : nuika の呼び出し。
  • --mingw64 : mingw64 (gcc) を使うことを指定。
  • --windows-disable-console : exe実行時にコンソールを非表示にする。
  • --include-data-file=".\\resources\\*.png=.\\resources\\" : リソースファイル(*.png)を同梱させる。
  • --onefile : リソースファイルも含めて1つのexeファイルにする。
  • scrsavpygame.py : exe化したいPythonスクリプト名。
ちなみに、リソースファイルは、--include-data-file=".\\resources\\*.png=.\\resources\\" で指定しているけれど。最初、パス区切り文字を「/」にしたら上手く行かなくて、試行錯誤中に「\\」にしたら何故か上手く行った。ただ、これは、スクリプト側の、リソースファイルパスを求める処理がおかしかった可能性がある。

上記を実行したところ、途中で「gccその他が見つからないがダウンロードするか?」と尋ねてきた。「Yes」を入力してダウンロードすることにした。それぞれ、 C:\Users\(USERNAME)\AppData\Local\Nuitka\ 以下にキャッシュとしてダウンロードされる模様。ファイル数を調べてみたら、7923ファイル、全体で1.14GBが入っていた。まあ、Nuitka が要求する gccのバージョン等が固定されている可能性もあるので、このくらいは目を瞑ったほうが面倒臭くないのかもしれない。

数分かかって、.py と同階層に scrsavpygame.exe が作成された。ファイルサイズは 8.59MB。PyInstaller で生成した .exe は 13.06MB だったので、若干ファイルサイズも小さくなっている。

別ディレクトリに .exe をコピーして実行してみたところ、動作してくれた。
scrsavpygame.exe /s
scrsavpygame.exe /c
scrsavpygame.exe /p 0
scrsavpygame.exe

なんとなくだけど、PyInstaller で作った .exe より起動がちょっと速いような気もする。

スクリーンセーバとして利用できるか動作確認。.exe を .scr にリネームコピーして、スクリーンセーバの既定の置き場所にコピー。スクリーンセーバ設定画面を出してリストから選んだ。これも一応動いてくれた。

ただ、相変わらず、プレビュー窓内が表示されるまで結構待たされる…。こうなると、一時ディレクトリを作成して、動作に必要なファイルを全て展開してから実行するという、その仕組み自体が厳しいのかもしれない…。

ちなみに、--onefile ではなく --standalone を指定すれば、1つのファイルにまとめずに、*.dist というディレクトリに動作に必要なファイルを全てコピーした状態で .exe を生成してくれる。ファイル数は106ファイル、27.52MB だった。

つまり、1つの .exe にした場合、実行時は毎回106ファイルを一時ディレクトリに展開しているわけで…。それなりに起動が遅くて当然ですよね、という気もしてきた。

参考ページ。 :


2022/08/15(月) [n年前の日記]

#1 [ruby][windows] Ruby/SDLでスクリーンセーバを作れないものか

Python + pygame を使ってWindows用スクリーンセーバを作れそうなことは分かったけれど。

_pygameでWindows用スクリーンセーバを作る

Ruby/SDL でも似たようなことができないものかと疑問が湧いた。

Ruby/SDL も SDL1.x を使っていた記憶があるし、ドキュメントを眺めると SDL_WINDOWID という環境変数名が書かれていたので、コマンドラインオプション経由で得られたウインドウハンドルを使って、そのウインドウに Ruby/SDL のウインドウを埋め込むことができそうだなと。

_Ruby/SDL Reference Manual (2.x)

そんなわけで関連情報を色々ググって調べてた。

余談。Ruby/SDL 2.x のリファレンスマニュアルは Unicode (UTF-8) で書かれているっぽいのだけど、何故かhtmlソースの最初で euc-jp が指定されていて文字化けしてしまう…。Google Chrome なら「テキストエンコーディング」という拡張機能をインストールすれば文字コードを手動で選べる。

_Chromeでサイトを見ると文字化けするときの対処法 | シゴトハカドル

Firefox なら、Altキーを押して、表示 → テキストエンコーディングを修復、で文字化けが直ると思う。たぶん。

Rubyのバージョン。 :

まず、Windows上で Ruby/SDL を使えるRubyのバージョンを調べた。Ruby 1.8 とRuby 1.9 なら動きそう。手元の環境、Windows10 x64 21H2上では、以下の2つが利用できた。
  • ActiveScriptRuby 1.8.7 p330 i386-mswin32 (ActiveRuby.msi)
  • Ruby 1.9.3 p551 i386-mingw32 (rubyinstaller-1.9.3-p551.exe)
_ActiveScriptRuby and Other packages
_Download Archives

どちらも32bit版であること。
どちらも32bit版であること。

大事なことなので2回言いました。Ruby/SDL のセットアップ用ファイルに含まれている各種バイナリが32bit版なので、64bit版 Ruby と組み合わせて動かそうとしてもダメだったはず。

ちなみに、Windows上でRubyのバージョンを切り替えたい時は、pik 0.2.8 もしくは uru 0.8.5 というツールが使える。

_vertiginous/pik: Ruby version manager for Windows
_jonforums / uru - Bitbucket
_uruでRubyを切り替えできるようにする - Qiita
_WindowsのRubyのバージョンをuruで切り替える方法 | TechAcademyマガジン

Ruby/SDLの入手。 :

Ruby/SDL のインストールは、昔メモしてあった。

_Ruby/SDLをインストールしようとしてハマった

GetWindowRect や CreateMutex について。 :

スクリーンセーバを作るには、ウインドウサイズを取得したり、多重起動を抑止したりしないといけない。
  • ウインドウハンドルを元にしてウインドウサイズを取得するなら、USER32.DLL の GetWindowRect() を使えばいい。
  • 多重起動を抑止するためには、KERNEL32.DLL の CreateMutex() を使えばいい。

となると、Ruby から .dll を使う何かしらが必要になるわけだけど…。ググったところ、win32api とか、DL とか、fiddle とか、ffi とか、そのあたりを使うことになりそうだなと分かった。

ただ、Ruby 1.8時代は win32api が使われていたけれど、Ruby 1.9 になると DL を使うことになって、Ruby 2.0以降は fiddle を使うことになっているようで…。

_DLとWin32APIとFiddle - mirichiの日記

win32api も DL も fiddle も互換性が無いそうで、Ruby 1.8 + win32api を勉強しても、Ruby 1.9 + DL を勉強しても、後々一切何の役にも立たないあたりがちょっともやもやする。

もしかして、ffi を使えば、互換性云々で悩まなくて済んだりしないのでは…? Ruby 1.9.3 p551 で使える ffi のバージョンは… ffi 1.9.14 が最終版だろうか。

_ffi | RubyGems.org
gem install ffi -v "1.9.14"

一応、上記の指定で、ffi-1.9.14-x86-mingw32.gem が落ちてきてインストールはできた。実際に動くかどうかは試してないけど…。

ただ、Ruby 1.8.7 p330 i386-mswin32版で同じことをしたら、cl.exe が無いと言われてビルドに失敗した。x86-mingw32 のバイナリを利用するわけにはいかないのかな…。

以下を打ったらインストールできたように見えるけど、果たして使えるのかどうか。
gem install ffi -v "1.9.14" --platform x86-mingw32

exe化について。 :

exe化は、ocra を使うことになるだろうか。Ruby 1.8.7 や Ruby 1.9.3 で使えるのかどうか疑問だったけど、調べた感じでは使えそうな感じだった。Ruby 1.8 / 1.9 のどちらも、以下でインストールができた。
gem install ocra

Ruby 1.8 限定だけど、exerb も使えるかもしれない。ただ、画像ファイルや *.so 等を1つの .exe にまとめる方法がよく分からなかった…。

※ 2022/08/19追記。Ruby 1.8.7 は ocra 1.3.1 が、Ruby 1.9.3 は ocra 1.3.10 が利用できる最終版の模様。

余談。gemについて。 :

Ruby 1.8 / 1.9 は、インストールした直後から gem が使える状態になっていたのかどうか、ちょっと記憶が怪しい…。

Ruby 2.3.3 の時は、Rubygems をアップデートしないと gem が使えなかった、とメモしてあったので、ひょっとすると昔の自分は、何かそのへんやっていたかもしれないなと。

_Windows10上で Ruby 2.3.3 をインストールしようとして少しハマった
_Download RubyGems | RubyGems.org

ただ、手元の環境では gem のバージョンが古くても一応使えているように見える…。gem -v と打ってバージョンを確認したところ以下が入っていた。
  • ActiveScriptRuby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32] : gem 1.3.7
  • Ruby 1.9.3 p551 (2014-11-13) [i386-mingw32] : gem 1.8.29

まあ、gem を使って何かしらをインストールしようとしてエラーが出たら、その時に Rubygems のアップデートを検討すればいいだけの話かも。

余談。Ruby 2.x.x + Ruby/SDL について。 :

Windows10 + Ruby 2.x.x 上で Ruby/SDL をインストールする方法は自分も分からないです。

たしかどこかで、DevKit(MSYS2)内にSDL関連パッケージ群を事前にインストールした上で作業すればビルドできるかも、みたいな話を見かけた記憶もあるけど。それどうやってインストールするんや…てな状態で。一応、以下のようなパッケージがあるらしいけど…。

_Base Package: mingw-w64-SDL - MSYS2 Packages
_Base Package: mingw-w64-SDL_image - MSYS2 Packages
_Base Package: mingw-w64-SDL_gfx - MSYS2 Packages
_Base Package: mingw-w64-SDL_net - MSYS2 Packages
_Base Package: mingw-w64-SDL_ttf - MSYS2 Packages
_Base Package: mingw-w64-SDL_mixer - MSYS2 Packages

これが Debian Linux や Ubuntu Linux なら、ruby-sdl というパッケージが用意されているので、sudo apt install ruby-sdl であっさりインストールできるのだけど。

_Ubuntu - jammy の ruby-sdl パッケージに関する詳細
_Debian -- bullseye の ruby-sdl パッケージに関する詳細

ちなみに、gosu という、これまた SDL を使ったゲーム制作用ライブラリは、Windows10 x64 21H2 + Ruby 2.6.10 p210 i386-mingw32 (32bit) 上でも、gem install gosu で gosu-1.4.3.gem が落ちてきて、ビルドしてインストールできてるのですが…。

2022/08/16(火) [n年前の日記]

#1 [ruby] Ruby + ffi の動作確認をした

Windows10 x64 21H2上で、Ruby + ffi の動作確認をしてみた。ffi というのは、Ruby から .dll や .so の中にある関数を呼び出せるようになる外部ライブラリ。という説明で合ってるのかどうか…。

_ffi | RubyGems.org
_ffiの全バージョン履歴 | RubyGems.org

動作確認に使ったソース。 :

以下で紹介されていたソースを使わせてもらって動作確認した。user32.dll と MessageBoxA() を使って、メッセージボックスを表示するだけのスクリプト。

_winapi - What are the Ruby Win32API Parameters | How do I pass a null pointer? - Stack Overflow

_02_messagebox_ffi.rb
require "rubygems"
require 'ffi'

module Win32
  extend FFI::Library
  ffi_lib "user32"
  attach_function :messageBox, :MessageBoxA, [:pointer, :string, :string, :long], :int
end

rc = Win32.messageBox(nil, "Hello World", "MessageBoxA by FFI", 0x40)
puts rc

動作確認環境とインストール手順。 :

Ruby 2.6.10 p210 i386-mingw32 の場合。
gem install ffi
ffi 1.15.5 x86-mingw32 がインストールされた。動作した。

Ruby 1.9.3 p551 i386-mingw32 の場合。
gem install ffi -v 1.9.14
Ruby 1.8/1.9 に対応している ffi の最終版は 1.9.14 らしい。-v 1.9.14 でバージョンを指定してインストール。x86-mingw32版がインストールされた。動作した。

Ruby 1.8.7 p330 i386-mswin32、Ruby 1.8.7 p330 i386-mingw32 の場合。
gem install ffi -v 1.9.14 --platform x86-mingw32
動作した。ただ、注意点がある。require "ffi" をする前に、require "rubygems" を記述すること。

Ruby 1.8 までは require "rubygems" を記述しないと、rubygems でインストールしたライブラリを理由できなかったけれど。 Ruby 1.9 以降は require "rubygems" を記述しなくても動くことを忘れていて、「動かない」「動かない」と何時間も悩んでしまった…。

試行錯誤の記録。 :

Ruby 1.8.7 p330 i386-mswin32 に以下でインストールしてみたが、これは動かなかった。
gem install ffi -v 1.9.14 --platform x86-mingw32
02_messagebox_ffi.rb:12:in `require': no such file to load -- ffi (LoadError)
        from 02_messagebox_ffi.rb:12

ビルドしないとダメなのかと、RubyInstaller版の Ruby 1.8.7 p330 i386-mingw32 + DevKit をインストールして、以下を打ったらビルドが通ったが、しかしこれも動かない。
gem install ffi -v 1.9.14 --platform ruby

動作していたらしきバージョンをググって探した。

_Windows XPにwatir-webdriverをインストールする | メモログ

1.0.9 なら動いていたらしい…?
gem install ffi -v 1.0.9
動かなかった。

_Version 1.0.7 doesn't work with ruby versions > 1.8.7 - Issue #341 - ffi/ffi

1.0.7 も動かないと言われてるな…。

ふと、突然思い出した。Ruby 1.8 時代は、rubygems でインストールしたライブラリを使う場合、何かおまじないが必要だった気がする。

ググってみた。require "rubygems" を最初に書いておかないとダメだったのでは。

require "ffi" の前に追加。
require "rubygems"
require "ffi"

あっさり動いた…。
Ruby 1.8.7 p330 mingw32 + ffi 1.9.14 x86-mingw32 も動いた。
Ruby 1.8.7 p330 mswin32 + ffi 1.9.14 x86-mingw32 も動いた。

そういうオチか…。トホホ。

余談。win32apiバージョン。 :

Windows10 x64 21H2 + Ruby 1.8.7 p330 mswin32版上で、win32api を使ってメッセージボックスを表示する例も試してみた。

_01_messagebox_win32api.rb
require "Win32API"

msgbox = Win32API.new('user32', 'MessageBoxA', %w(p p p i), 'i')
msgbox.call(0, "Hello World.", "MessageBoxA", 0x40)

これも動作してくれた。

2022/08/17(水) [n年前の日記]

#1 [ruby][windows] Rubyスクリプトの多重起動を抑止する処理

Windows10 x64 21H2 + Ruby 1.8 / 1.9 / 2.6 で、Rubyスクリプトの多重起動を抑止できないか試してみた。

Windowsの場合、kernel32.dll の CreateMutex() を使えば、多重起動してるかどうかを判別できるらしい。

動作確認に使った Ruby のバージョンは以下。

win32apiを利用して実現。 :

Ruby 1.8 時代に標準で添付されていた win32api を使って試してみた。

環境は以下。
  • Windows10 x64 21H2
  • Ruby 1.8.7 p330 i386-mswin32
  • Ruby 1.9.3 p551 i386-mingw32
Ruby 1.8 / 1.9 の両方で動作した。

処理内容は、1秒毎に数値を 0 〜 9 まで出力する。また、起動時に、既にどこかで同じスクリプトが動いてたら即座に終了する。

_03_mutex_win32api.rb
require "Win32API"

create_mutex = Win32API.new("kernel32", "CreateMutex", "llp", "l")
release_mutex = Win32API.new("kernel32", "ReleaseMutex", "l", "l")
close_handle = Win32API.new("kernel32", "CloseHandle", "l", "l")
get_last_error = Win32API.new("kernel32", "GetLastError", "", "l")

MUTEX_NAME = "ruby_win32api_mutex_sample"
ERROR_ALREADY_EXISTS = 183

mutex = create_mutex.call(0, 1, MUTEX_NAME)
err = get_last_error.call()
puts "Mutex : #{mutex}"
puts "GetLastError : #{err}"

if mutex == 0 or err == ERROR_ALREADY_EXISTS
  puts "already exists"
  exit
else
  10.times do |i|
    puts i
    sleep 1
  end
end

if mutex != 0
  release_mutex.call(mutex)
  close_handle.call(mutex)
end
  • CreateMutex() を呼んだ直後に、GetLastError() でエラーコードを調べて、ERROR_ALREADY_EXISTS = 183 が出ていたら既にスクリプトがどこかで起動してる。
  • Mutex が確保できていたら、処理終了時に ReleaseMutex() と CloseHandle() を呼んでおく。

ffiを利用して実現。 :

外部ライブラリ ffi を利用して同じことが実現できないか試してみた。ffi のインストール方法は昨日のメモを参照のこと。

_Ruby + ffi の動作確認をした

動作した環境は、Windows10 x64 21H2 + Ruby 2.6.10 p210 i386-mingw32 + ffi 1.15.5 x86-mingw32。

残念ながら、Ruby 1.8 / 1.9 (+ ffi 1.9.14)では動作しなかった。

_04_mutex_ffi.rb
require "rubygems"
require "ffi"

module WinKernel
  extend FFI::Library
  ffi_lib :kernel32
  ffi_convention :stdcall

  attach_function :CreateMutexW, [:long, :bool, :pointer], :ulong
  attach_function :ReleaseMutex, [:ulong], :bool
  attach_function :CloseHandle, [:ulong], :long
  # attach_function :GetLastError, [], :ulong
end

mutex_name = "rubyffimutexsample"
ERROR_ALREADY_EXISTS = 183

mutex = WinKernel.CreateMutexW(0, false, mutex_name.encode("UTF-16LE"))

# Add API.last_error #55 - Github Lab
# https://githublab.com/repository/issues/cosmo0920/win32-api/55
#
# GetLastError() is reset when calling Win32 API from Ruby.
# FFI::LastError.winapi_error" is a good choice.

begin
  # err = WinKernel.GetLastError
  # err = FFI::LastError.error
  err = FFI::LastError.winapi_error
rescue => e
  puts "Error : This version of ffi does not support FFI::LastError.winapi_error."
  if mutex != 0
    WinKernel.ReleaseMutex(mutex)
    WininKernel.CloseHandle(mutex)
  end
  exit
end

puts "Mutex : #{mutex}"
puts "GetLastError : #{err}"

if mutex == 0 or err == ERROR_ALREADY_EXISTS
  puts "already exists"
  exit
else
  10.times do |i|
    puts i
    sleep 1
  end
end

if mutex != 0
  WinKernel.ReleaseMutex(mutex)
  WinKernel.CloseHandle(mutex)
end

動作する状態にするまで、かなりハマった…。

問題その1。ffi を使った場合、何故か CreateMutex() が呼べなかった。指定しても「そんな関数は無い」と言われてしまう。CreateMutexA() か CreateMutexW() にしたら呼び出すことができた。ちなみに、CreateMutexW() の呼び出し方は、win32-mutex のソースを参考にさせてもらった。

_win32-mutex/mutex.rb at main - chef/win32-mutex

問題その2.kernel32.dll の GetLastError() を呼んでも、それらしい値が全く返ってこなくて悩んでしまった…。何度試しても、どんな状況でも、必ず 0 になってしまう。

そのあたり、以下のページにヒントがあった。

_Add API.last_error #55 - Github Lab

Ruby から Win32 API を呼ぶと、GetLastError() の値がリセットされてしまう場合があるそうで。そんな時のために、ffi は FFI::LastError.winapi_error というものを用意してあって、そこに GetLastError() の本来の値が格納されているらしい。

_Method: FFI::LastError.winapi_error - Documentation for ffi/ffi (master)

そんなわけで、FFI::LastError.winapi_error を使ってエラーコードを取得したら期待通りの動作になった。

ただ、Ruby 2.6 にインストールした ffi 1.15.5 には、FFI::LastError.winapi_error が用意されていたのだけど。Ruby 1.8 / 1.9 にインストールした ffi 1.9.14 にはメソッドが用意されてなくてエラーになってしまう。

昔は winapi_error ではなく win_error という名前だった、という情報にも辿り着いたのだけど。win_error に書き換えても、やはりエラーが出る。

_Rename to winapi_error - ffi/ffi@4ba55c5

どうやらどこかのバージョンの時点で win_error が追加されたけど、それは Ruby 1.8 / 1.9 でも動作してくれる ffi 1.9.14 には実装されていないようだなと…。そんなわけで、Ruby 1.8 / 1.9 + ffi 1.9.14 では、CreateMutex() を使う方法は分からなかった。

DL と fiddle を利用して実現。 :

Ruby 1.9.3 限定の記述になるけれど、当時標準で添付されていた DL と fiddle を使って実現してみた。

動作確認環境は、Windows10 x64 21H2 + Ruby 1.9.3 p551 i386-mingw32。Ruby 1.8 や Ruby 2.6 では、「DL なんて無い」と言われて動かなかった。

_05_mutex_dl.rb
require "dl"
require "fiddle"

include Fiddle

libc = DL.dlopen("kernel32.dll")
create_mutex = Fiddle::Function.new(libc["CreateMutex"], [TYPE_LONG, TYPE_LONG, TYPE_VOIDP], TYPE_LONG)
release_mutex = Fiddle::Function.new(libc["ReleaseMutex"], [TYPE_LONG], TYPE_LONG)
close_handle = Fiddle::Function.new(libc["CloseHandle"], [TYPE_LONG], TYPE_LONG)
get_last_error = Fiddle::Function.new(libc["GetLastError"], [], TYPE_LONG)

mutex_name = "rubydlfiddlemutexsample"
ERROR_ALREADY_EXISTS = 183

mutex = create_mutex.call(0, 1, mutex_name)

# err = Fiddle::win32_last_error
err = get_last_error.call()

puts "Mutex : #{mutex}"
puts "GetLastError : #{err}"

if mutex == 0 or err == ERROR_ALREADY_EXISTS
  puts "already exists"
  exit
else
  10.times do |i|
    puts i
    sleep 1
  end
end

if mutex != 0
  release_mutex.call(mutex)
  close_handle.call(mutex)
end

Ruby 1.9.3 の fiddle にも、ffi と同様に、Fiddle::win32_last_error というそれらしいメソッドがあるのだけれど。何故か DL+ fiddle の組み合わせの場合は、kernel32.dll の GetLastError() を呼ぶほうが正しい値が返るようだった。

また、この場合は、CreateMutexA() や CreateMutexW() ではなく、CreateMutex() を呼んでも通る模様。

ハマりそうなポイントのまとめ。 :

  • CreateMutex の名前で呼べる場合と、CreateMutexA か CreateMutexW を呼ばないといけない場合がある。
  • GetLastError() を呼んで正常動作する場合と、ffi や fiddle で別途用意されたエラーコード取得メソッドを呼ばないと正常動作しない場合がある。

とりあえず、Ruby 1.8 / 1.9 の場合は win32api を使っても警告が出なかったので、Ruby 1.8 / 1.9 なら win32api を使ってこの手の処理を書けばよさそうだなと…。

また、ffi を使えば Ruby 1.8 - 2.x でも同じ記述ができるのではと期待していたけれど、そんなことはなかった。最近の ffi じゃないと記述が通らない・正常動作しない場合もあるのだなと。そうなると、Ruby 1.8 / 1.9 で、あえて ffi を使う理由は無さそうな気がする。win32api を使ったほうが悩まなくて済む。

参考ページ。 :

#2 [ruby][windows] Rubyスクリプトでウインドウサイズを取得したい

Windows10 x64 21H2 + Ruby 1.8.7 p330 i386-mswin32 で、Windows上のウインドウのサイズを取得したい。

win32api を利用して試してみた。

_06_getwindowrect_win32api.rb
require "Win32API"

if ARGV.size != 1
  fn = File.basename(__FILE__)
  puts "Usage:\n    ruby #{fn} WINDOWHANDLE"
  exit
end

hwnd = ARGV[0].to_i
puts "Window Handle = #{hwnd}"

get_window_rect = Win32API.new("user32", "GetWindowRect", "lp", "i")
buf = "\0" * 256
get_window_rect.call(hwnd, buf)
x0, y0, x1, y1 = buf.unpack("l4")

w = x1 - x0
h = y1 - y0

puts "(x0, y0) = (#{x0}, #{y0})"
puts "(x1, y1) = (#{x1}, #{y1})"
puts "(w, h) = #{w}, #{h}"

ruby 06_getwindowrect_win32api.rb ウインドウハンドル、で実行できる。ウインドウハンドルは10進数で記述。

ウインドウハンドルを調べるツールについて。 :

ウインドウハンドルを調べる作業は、以下のツールを使わせてもらった。

_WinExplorer v1.30

Windows10 x64 21H2上で、WinExplorer 1.30 を利用できた。

あるいは、Spy++ を利用することもできそう。Visual Studio 2019 と一緒に入ってる場合もあるらしい。

_spy++を使ってクラス名を調べる方法 | 自動マクロプログラミング
_【Visual Studio 2019】Spy++の場所 - buralog

#3 [comic] 漫画を読んでた

弟が帰省した際に持ってきてくれた漫画単行本を消化。

「ジョジョリオン」荒木飛呂彦著。全27巻を読み終えた。よく分からないけど、これはパラレルワールド作品と言う認識でいいのだろうか…。それにしても、27巻は長い…。疲れた…。

ところどころ読み辛いコマ割りがあって、結構読み飛ばしてしまった気もする。設定も意味不明というか、なんでそうなるのと首を捻る部分がいくつかあって…。でもまあ、元々そういう作家性だし…。

2022/08/18(木) [n年前の日記]

#1 [ruby] exerbと格闘中

Ruby 1.8.7 p330 と Ruby/SDL (rubysdl) 1.3.1.1、2.1.1.1 を使って、Windows用のスクリーンセーバを作れないものかと実験しているところだけど、exe化するあたりでハマっているところ。

一応、ocra 1.3.1 を使って exe化はできたけど、ocra は動作に必要なファイルを一時ディレクトリに解凍して動く仕組みなので、予想はしていたけど結構起動が遅いわけで。そこでふと、exerb ならどうだろうと手を出してみたのだけど、これが上手く行かない…。

ちなみに、puts "Hello World" だけの Rubyスクリプトを exerb でexe化したら、ちゃんと動作する exe になった。Ruby の基本機能だけを使ったスクリプトならそこそこ動くということなのかな…。

実験環境は以下。

分かったこと。 :

  • Ruby/SDL を使ったスクリプトを exerb で exe にする場合、.exy にSDL関連の .dll を追記してやる必要がある。
  • rubysdl 2.1.1.1 は、どうやらdllのファイル名が長過ぎるようで、exerb ではexe化できなかった。
  • rubysdl 1.3.1.1 は、dllファイル名の長さの問題は回避できそうだが、pngの読み込みができない exe になった。
  • rubysdl 1.3.1.1 を利用して、かつ、画像は bmp のみに限定したら、動作する exe が生成された。
  • しかし、exe を他のPCに持っていったら動かなかった…。

試行錯誤の記録。その1。 :

まず、Ruby 1.8.7 p330 mswin32 + rubysdl 2.1.1 の環境で、exerb 5.3.0 を使って exeファイルを作成してみた。
exerb rbsdlsav.rb
.exe が生成された。

実行したところ、SDL関連のdllが読めないと言ってきた。

exerb にファイル構成等を指示するために、.exyファイルを作成してみる。
mkexy rbsdlsav.rb

.exy内には、SDL関連dllが記述されてなかった。

ocra を使った時の出力結果を頼りにして、SDl関連dll を .exy に追記してみる。ちなみに、自分の環境では、Ruby 1.8.7 を C:/Ruby/Ruby187p330mswin32/ にインストールしてある。
> ocra --version
Ocra 1.3.1

> ocra --windows scrsavrubysdl.rb res
=== Loading script to check dependencies
=== Building scrsavrubysdl.exe
=== Adding user-supplied source files
=== Adding ruby executable rubyw.exe
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/SDL_image.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/SDL.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/SDL_mixer.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/SGE.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/smpeg.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/SDL_ttf.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/libfreetype-6.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/zlib1.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mswin32/bin/libpng12-0.dll
=== Adding library files
=== Compressing 3601505 bytes
=== Finished building scrsavrubysdl.exe (1187787 bytes)

.exy の一番最後(fileブロック内)に以下を追記。
  sdl.so:
    file: C:/Ruby/Ruby187p330mswin32/lib/ruby/site_ruby/1.8/i386-msvcrt/sdl.so
    type: extension-library
  SDL_image.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/SDL_image.dll
    type: dynamic-library
  SDL.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/SDL.dll
    type: dynamic-library
  SDL_mixer.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/SDL_mixer.dll
    type: dynamic-library
  SGE.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/SGE.dll
    type: dynamic-library
  smpeg.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/smpeg.dll
    type: dynamic-library
  SDL_ttf.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/SDL_ttf.dll
    type: dynamic-library
  libfreetype-6.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/libfreetype-6.dll
    type: dynamic-library
  zlib1.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/zlib1.dll
    type: dynamic-library
  libpng12-0.dll:
    file: C:/Ruby/Ruby187p330mswin32/bin/libpng12-0.dll
    type: dynamic-library

exerb に .exy を指定してexeを生成。
exerb rbsdlsav.exy

しかし、出来上がった exeファイルを実行すると、以下のエラーが出る。
> rbsdlsav.exe
sdl.rb:17:in `require': Couldn't modify DLL's name in the import table. The name of the executable file is too long. (LoadError)
        from sdl.rb:17

以下のスレッドが関係してそうだけど…。

_Re: exerb fails (exerb-eng:0059) - Exerb - OSDN

以下も関係あるだろうか。

_Exerb Project
> Q12. The error message "Fail to modify the import table. exe file name is too long." has appeared.
> A12. This message means that the exe file name must be written in 17 characters.

何故かは分からないけど、exeファイル名は17文字までという制限があるらしい。一応、生成exeファイル名は17文字以下にしてみたのだけど、もしかして利用するdllファイル名も17文字以下でなければならないということだろうか。だとしたら、打つ手は無さそう…。

試行錯誤の記録。その2。 :

Ruby/SDL (rubysdl) 2.1.1.1 を使っているスクリプトを、exerb を使って exe化するのは、ファイル名文字数の関係でどうやら難しい気配がしてきたわけだけど。

これが Ruby/SDL (rubysdl) 1.3.1 ならどうだろうか。rubysdl 1.3.1 なら、多少は dllファイル名が違っているはず。

Ruby 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1 で試してみた。

ocra 1.3.1 の出力結果は以下。
> ruby -v
ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mingw32]

> ruby -e "require 'sdl'; puts SDL::VERSION"
1.3.1

> ocra --version
Ocra 1.3.1

> ocra --windows rbsdlsav.rb res
=== Loading script to check dependencies
=== Building rbsdlsav.exe
=== Adding user-supplied source files
=== Adding ruby executable rubyw.exe
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/libiconv-2.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/SDL_mixer.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/SDL.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/SGE.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/SDL_image.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/SDL_ttf.dll
=== Adding detected DLL C:/Ruby/Ruby187p330mingw32/bin/smpeg.dll
=== Adding library files
=== Compressing 4309809 bytes
=== Finished building rbsdlsav.exe (1644661 bytes)

.exy を作成。
mkexy rbsdlsav2.rb

生成された .exy を修正。

core を cui から gui に変更。
  core: gui

SDL関連dllの記述を追記。
  zlib.so:
    file: C:/Ruby/Ruby187p330mingw32/lib/ruby/1.8/i386-mingw32/zlib.so
    type: extension-library
  SDL.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/SDL.dll
    type: dynamic-library
  SGE.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/SGE.dll
    type: dynamic-library
  SDL_mixer.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/SDL_mixer.dll
    type: dynamic-library
  SDL_image.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/SDL_image.dll
    type: dynamic-library
  SDL_ttf.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/SDL_ttf.dll
    type: dynamic-library
  smpeg.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/smpeg.dll
    type: dynamic-library
  zlib1.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/zlib1.dll
    type: dynamic-library
  libpng12.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/libpng12.dll
    type: dynamic-library
  libiconv-2.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/libiconv-2.dll
    type: dynamic-library
  libtiff.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/libtiff.dll
    type: dynamic-library
  jpeg.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/jpeg.dll
    type: dynamic-library
  libcharset-1.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/libcharset-1.dll
    type: dynamic-library
  ogg.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/ogg.dll
    type: dynamic-library
  vorbis.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/vorbis.dll
    type: dynamic-library
  vorbisfile.dll:
    file: C:/Ruby/Ruby187p330mingw32/bin/vorbisfile.dll
    type: dynamic-library

exerb でexe化。
exerb rbsdlsav2.exy

exeファイルが生成された。しかし、exe を実行すると、png関連のモジュールが見つからないと言ってきた。
> rbsdlsav2.exe
rbsdlsav2.rb:197:in `loadFromIO': Couldn't load image from IO: Failed loading png_create_info_struct: 指定されたモジュールが見つかりません。
 (SDL::Error)
        from rbsdlsav2.rb:197:in `get_surface_image_from_base64'
        from rbsdlsav2.rb:75:in `draw_fullscreen'
        from rbsdlsav2.rb:235

Ruby/SDL でpng画像を利用しようとすると、このエラーに遭遇してしまうのだろう…。であればと、png画像を利用することを諦めて、bmp画像を使うことにしたら、上記のエラーが出てこなくなって、ようやく動作する exe が生成された。

と思ったがダメだった。他のPCに exe をコピーして実行すると例外が発生する。
プログラムで例外が発生しました。詳細は下記の通りです。

タイプ: ExerbRuntime::Error

メッセージ: Win32API Error #126 --- 指定されたモジュールが見つかりません。

バックトーレス:
sdl.rb:17:in 'require'
sdl.rb:17
sdl.rb の 17行目には、require 'sdl.so' と書いてある。sdl.so を読み込むことができていないということだろうか…。

exerb で変換作業をしたPC上なら動くが、他のPCでは動かないexeになってしまった。何故。

※ 2022/08/19追記。他のPCでexeを動かした際のエラーダイアログも載せておく。

ss_exerb_error.png

試しに、Ruby 1.8.7 p330 i386-mswin32 (ActiveScriptRuby) + rubysdl 1.3.1 にしてexeに変換してみたけれど、症状変わらず。やはり他のPCにexeを持っていくとエラーが出る。mingw32版 Ruby ではなく、mswin32版 Ruby なら動いてくれるかと期待したけどダメだった。

exerb に渡した .exy は以下。

_rbsdlsav2.exy

余談。exerb と DATA。 :

exerb で exe化した exe を動かしたところ、以下のエラーが出た。
> rbsdlsav.exe
rbsdlsav.rb:150:in `get_surface_image_from_base64': uninitialized constant DATA (NameError)
        from rbsdlsav.rb:35:in `draw_fullscreen'
        from rbsdlsav.rb:192

スクリプトの最後につけた base64文字列を読み取れずにエラーを出している模様。

_DATAオブジェクトがきかない (exerb-dev:0477) - Exerb - OSDN
_Re: DATAオブジェクトがきかない(再送) (exerb-dev:0481) - Exerb - OSDN
_Re: DATAオブジェクトがきかない(再送) (exerb-dev:0485) - Exerb - OSDN

exerb は DATA云々に対応してなかったというオチらしい。

exerb はデータファイルを同梱させるのが難しそうなので、わざわざ画像ファイルのデータもスクリプト内に収めようと手を入れてみたのだけど、こんな罠があるとは…。

スクリプトの最後、__END__ 以下に記述していた base64文字列を、スクリプトの途中で配列定数として用意して利用するように書き換えたところ、一応動いてくれた。

2022/08/19(金) [n年前の日記]

#1 [ruby][windows] Ruby/SDLでスクリーンセーバを作成

Windows10 x64 21H2上で、Ruby 1.8.7, 1.9.3 + Ruby/SDL (rubysdl) 2.1.1.1 を使って、Windows用のスクリーンセーバ(の雛形)を作成してみた。

動作させている様子は以下。解像度が低いけど雰囲気ぐらいは伝わるかと。




Windows用スクリーンセーバに求められる仕様については、pygame でスクリーンセーバを作った際にメモしたソレが多少は参考になるかと。

_Windows用スクリーンセーバについてのおさらい。

動作確認環境。 :

動作確認環境は以下。
  • Windows10 x64 21H2
  • RubyInstaller 1.9.3 p551 i386-mingw32 + rubysdl 2.1.1.1 + ocra 1.3.10
  • ActiveScriptRuby 1.8.7 p330 i386-mswin32 + rubysdl 2.1.1.1 + ocra 1.3.1
  • RyubyInstaller 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1.1 + ocra 1.3.1

セットアップファイル名も一応列挙。
  • rubyinstaller-1.9.3-p551.exe
  • rubysdl-2.1.1.1-mswin32-1.9.1-p243.zip
  • ActiveRuby.msi
  • rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip
  • rubyinstaller-1.8.7-p330.exe
  • rubysdl-1.3.1.1-mswin32-1.8.6-p36.zip

_ActiveScriptRuby and Other packages
_Download Archives - RubyInstaller

Ruby は32bit版を利用。
Ruby は32bit版を利用。
大事なことなので2回言いました。
Ruby/SDLのバイナリが32bitなので、64bit版のRubyに入れて使おうとしても動かないです。たぶん。

ちなみに、Windows用のRuby/SDLは Ruby 1.8 / 1.9用しか存在しないので、Ruby 2.0以降でこういったことはできないと思われます。

利用できるRubyのバージョンや、Ruby/SDL のインストール方法は、以下を参照のこと。

_Ruby/SDLでスクリーンセーバを作れないものか
_Ruby/SDLをインストールしようとしてハマった

ソース。 :

ソースは以下。処理内容は、画面内をRubyのロゴ画像が跳ね回るだけの処理。

_rbsdlsav.rb
require "sdl"
require "Win32API"

APPLI_NAME = "Screensaver sample by using Ruby/SDL"
APPLIT_VER = "0.0.1"

IMG_FILE = "res/ruby_logo.png"

MUTEX_NAME = {
  "None" => "screensaver_rubysdl_sample_fullscreen",
  "fullscreen" => "screensaver_rubysdl_sample_fullscreen",
  "config" => "screensaver_rubysdl_sample_config",
  "preview" => "screensaver_rubysdl_sample_preview",
}

ERROR_ALREADY_EXISTS = 183

# Draw fullscreen
def draw_fullscreen
  SDL.init(SDL::INIT_VIDEO)
  screen = SDL::Screen.open(0, 0, 0, SDL::FULLSCREEN | SDL::HWSURFACE | SDL::DOUBLEBUF)

  img = SDL::Surface.load(get_resource_path(IMG_FILE)).display_format

  x, y = screen.w / 2, screen.h / 2
  dx, dy = screen.w / (60.0 * 3), screen.h / (60.0 * 4)
  count = 0
  looping = true

  SDL::Mouse.hide  # hide mouse cursor

  # main loop
  while looping
    while event = SDL::Event2.poll
      case event
      when SDL::Event2::Quit
        looping = false
      when SDL::Event2::KeyUp
        looping = false
      when SDL::Event2::MouseMotion
        if count >= 120
          rx = event.xrel
          ry = event.yrel
          r = 10
          looping = false if (rx * rx + ry * ry >= r * r)
        end
      end
    end

    # update sprite position
    x += dx
    y += dy
    dx *= -1 if (x <= 0 or x >= screen.w - img.w)
    dy *= -1 if (y <= 0 or y >= screen.h - img.h)

    count += 1

    screen.fill_rect(0, 0, screen.w, screen.h, [0, 0, 0])  # fill bg
    screen.put(img, x, y)  # draw sprite

    screen.flip  # update screen
    SDL.delay(16)  # wait
  end

  SDL::Mouse.show  # show mouse cursor
end

# Draw preview
def draw_preview(hwnd)
  if hwnd > 0
    # /p xxxxxx
    scrw, scrh = get_window_size(hwnd)
    SDL.putenv("SDL_VIDEODRIVER=windib")
    SDL.putenv("SDL_WINDOWID=#{hwnd}")
  else
    # /p 0
    scrw, scrh = 152, 112
  end

  SDL.init(SDL::INIT_VIDEO)
  screen = SDL::Screen.open(scrw, scrh, 0, SDL::SWSURFACE)
  img = SDL::Surface.load(get_resource_path(IMG_FILE)).display_format

  # main loop
  frames = 180 * 5
  frames.times do |t|
    while event = SDL::Event2.poll
      case event
      when SDL::Event2::Quit
        return
      when SDL::Event2::KeyDown
        return if event.sym == SDL::Key::ESCAPE
      end
    end

    x = (screen.w - img.w) / 2
    y = 12 * Math.sin((t * 4) * Math::PI / 180.0) + ((screen.h - img.h) / 2)
    screen.fill_rect(0, 0, screen.w, screen.h, [235, 108, 0])  # fill bg
    screen.fill_rect(8, 8, screen.w - 16, screen.h - 16, [255, 128, 0])  # fill bg
    screen.put(img, x, y)
    screen.flip
    SDL.delay(16)
  end
end

def draw_config
  s = "#{APPLI_NAME}\nVer. #{APPLIT_VER}"
  title = "Information"
  msg_box(s, title)
end

def get_resource_path(filepath)
  # return File.dirname(__FILE__) + "/" + filepath
  return File.join(File.dirname(__FILE__), filepath)
end

# Display messagebox dialog
def msg_box(msg, title)
  msgbox = Win32API.new("user32", "MessageBoxA", %w(p p p i), "i")
  msgbox.call(0, msg, title, 0x40)
end

# Get window size
def get_window_size(hwnd)
  get_window_rect = Win32API.new("user32", "GetWindowRect", "lp", "i")
  buf = "\0" * 256
  get_window_rect.call(hwnd, buf)
  x0, y0, x1, y1 = buf.unpack("l4")
  w, h = (x1 - x0), (y1 - y0)
  return w, h
end

# parse command line option
kind = "None"
hwnd = 0
if ARGV.size >= 1
  s = ARGV[0].downcase.slice(0..1)
  case s
  when "/s"
    kind = "fullscreen"
  when "/c"
    kind = "config"
  when "/p"
    kind = "preview"
    hwnd = ARGV[1].to_i
  end
end

# Mutex
create_mutex = Win32API.new("kernel32", "CreateMutex", "llp", "l")
get_last_error = Win32API.new("kernel32", "GetLastError", "", "l")
release_mutex = Win32API.new("kernel32", "ReleaseMutex", "l", "l")
close_handle = Win32API.new("kernel32", "CloseHandle", "l", "l")

mutex = create_mutex.call(0, 1, MUTEX_NAME[kind])
err = get_last_error.call()
exit if (mutex == 0 or err == ERROR_ALREADY_EXISTS)

# main job
case kind
when "config"
  draw_config
when "preview"
  draw_preview(hwnd)
else
  # fullscreen
  draw_fullscreen
end

if mutex != 0
  release_mutex.call(mutex)
  close_handle.call(mutex)
end

exit
  • draw_fullscreen() が、フルスクリーン表示モードの処理。
  • draw_config() が、設定画面モードの処理。
  • draw_preview() が、プレビューモードの処理。
  • Mutex 云々が、多重起動抑止処理。

使用画像は以下。.rb と同じ階層に res というフォルダを作って、その中に入れておく。

_ruby_logo.png

テストで実行。 :

実行は以下。
ruby rbsdlsav.rb /s
ruby rbsdlsav.rb /c
ruby rbsdlsav.rb /p 0
ruby rbsdlsav.rb
  • /s : フルスクリーン表示で実行。
  • /c : 設定画面を表示。ここではアプリ名をメッセージボックスで表示するだけ。
  • /p 0 : プレビューモード。スクリーンセーバ設定画面を表示した際に、プレビュー窓内に表示されるモード。ウインドウハンドルとして 0 を指定すると、小さいウインドウを表示してテスト描画する。本番では0より大きい数値(10進数)が渡されるはず。
  • コマンドラインオプション無し : フルスクリーン表示で実行するようにしておいた。

exe化する。 :

Windows用スクリーンセーバのファイル拡張子は .scr だけど、実体は .exe ファイル。.exe を .scr にリネームして、スクリーンセーバとして利用する。

今回、Rubyスクリプトのexe化には ocra を利用した。
  • Ruby 1.8.7 の場合、ocra 1.3.1 が利用できる。ocra 1.3.2 以降はエラーが出る。
  • RUby 1.9.3 の場合、ocra 1.3.10 が利用できる。ocra 1.3.11 はエラーが出る。

ocra のインストールは以下。
# Ruby 1.8.7
gem install ocra -v 1.3.1

# Ruby 1.9.3
gem install ocra -v 1.3.10

以下で exe ファイルが生成される。
ocra --windows rbsdlsav.rb res
  • --windows : GUIアプリとして実行する指定。
  • *.rb : 変換したいスクリプトファイル名。
  • res : 画像ファイル等が入ってるフォルダ。*.rb と一緒にフォルダ名を列挙すると exe に同梱される。

スクリーンセーバとして利用する。 :

.exe が生成されたら、.scr にリネームする。以下の場所に .scr をコピーすれば、スクリーンセーバとして利用できるようになる。
  • Windows が64bit版、スクリーンセーバが32bit版のプログラムなら、C:\Windows\SysWOW64\ 以下に .scr をコピーする。
  • Windows が64bit版、スクリーンセーバが64bit版のプログラムなら、C:\Windows\System32\ 以下に .scr をコピーする。
  • Windows が32bit版なら、C:\Windows\System32\ 以下に .scr をコピーする。

今回作ったスクリーンセーバは、32bit版 Ruby で作ったので、C:\Windows\SysWOW64\ 以下にコピーして動作確認した。

exerbではexe化できない。 :

exerb 5.3.0 でもexe化ができないか試していたのだけど、どうやら無理っぽいことが分かってきた。

ちょっと長くなりそうなので _別記事 に分けてメモしておく。

#2 [ruby][windows] exerbでRuby/SDLのスクリプトをexe化したかったけど無理だった

Ruby/SDL 1.3.1.1 を使ったRubyスクリプトを exerb 5.3.0 でexeに変換したかったけど、ちょっと無理っぽい気配になってきた。いや、無理。これたぶん無理。

環境は、Windows10 x64 21H2 + Ruby 1.8.7 p330 i386-mingw32 + rubysdl 1.3.1.1 + exerb 5.3.0。ちなみに、動作確認に使ってる別PCも Windows10 x64 21H2。

症状。 :

exerbで生成されたexeは、変換作業に用いたPC上では動作するけれど、別PC上では動作しなかった。

ss_exerb_error.png

検証に使ったソース。 :

以下は、exerb で exe化できないか試した際のRubyスクリプトソース。及び、exerb に渡す .exyファイル。Ruby/SDLを使ったWindows用スクリーンセーバ(の雛形)で、画面の中をRubyロゴ画像が跳ね回る処理をしている。

_rbsdlsav2.rb
_rbsdlsav2.exy

ちなみに、上記のスクリプト rbsdlsav2.rb は画面に画像を描画しているけれど、画像ファイルをbase64に変換してスクリプト内に含めてある版なので、別途画像ファイルを必要としない。

また、中に含めた画像ファイルは png ではなく bmp にしてある。png にすると、exe実行時、何故か以下のエラーが出てしまうので…。
> rbsdlsav2.exe
rbsdlsav2.rb:197:in `loadFromIO': Couldn't load image from IO: Failed loading png_create_info_struct: 指定されたモジュールが見つかりません。
 (SDL::Error)
        from rbsdlsav2.rb:197:in `get_surface_image_from_base64'
        from rbsdlsav2.rb:75:in `draw_fullscreen'
        from rbsdlsav2.rb:235

作業手順その他については、以下も参照のこと。

_試行錯誤の記録。その2 - exerbと格闘中 - mieki256's diary

調査に使ったツール。 :

ググったところ、Dependencies というツールを使えば、*.so や *.dll が必要としている *.dll 等を調べることができると知った。

_lucasg/Dependencies: A rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.

このツールを導入して、sdl.so や sdl*.dll を開いて、どの .dll が要求されているのか調べていった。

原因その1。 :

不具合が発生した原因は2点ある。

原因その1。変換作業に使ったWindows10機には、C:\Windows\SysWOW64\ 以下に、何故かSDL関連のDLLがごっそりほとんど入っていた。おそらく、何かしらのアプリをインストールした際、知らない間に、SysWOW64\以下にSDL関連DLLがコピーされてしまったのだろう…。

そのため、Rubyスクリプトをexe化したソレを実行した際、そのファイルに足りてないDLLがあったとしても、SysWOW64\ 以下から足りてないDLLを呼び出してしまって、故に exe が動いてしまっていた。

しかし、別PCの SysWOW64\ 以下には、SDL関連DLLが全く入ってなかったので、足りてないDLLに遭遇したらどこからも呼び出すことができず、「指定されたモジュールが見つかりません」とエラーを出していた。

原因その2。 :

原因その2.今回足りてなかったファイルは、libfreetype-6.dll だった。sdl.so が SDL_ttf.dll を要求して、その SDL_ttf.dll が libfreetype-6.dll を要求していた。

ところが、そもそも、Ruby/SDL 1.3.1.1 (rubysdl-1.3.1.1-mswin32-1.8.6-p36.zip) には、libfreetype-6.dll が同梱されていなかった…。当然、Ruby 1.8.7 のインストールフォルダにもコピーされていないので、フォルダを眺めてSDL関連DLLを全て列挙したつもりが、libfreetype-6.dll だけ指定が抜けている状態になってしまった。

これがまた困ったことに、libfreetype-6.dll について .exy 内で記述を追記して exe を生成しても、正常に動作する exe にはならない。

と言うのも、exerb には、「各種ファイル名は17文字まで」という謎の制限があるようで、libfreetype-6.dll はその制限に引っ掛かってしまう。

生成した exe を実行すると、「Couldn't modify DLL's name in the import table. The name of the executable file is too long.」とエラーメッセージを出す。「ファイル名が長過ぎて扱えない」と言われているのだと思う。

余談。libfreetype-6.dll は、Ruby/SDL 2.1.1.1 の zip (rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip) の中にあるものを利用してみた。

まあ、Ruby/SDL 1.3.1.1 と 2.1.1.1 のどちらを使うかと言えば、フツーは 2.1.1.1 を使うだろうから…。「Ruby/SDL 1.3.1.1 の zip はファイルが不足している」という罠にハマることは無いだろうなと…。

対策。 :

exerb に .exy を通じて、「各DLLを内包しろ」と指定すると、17文字を超えるファイル名を扱えなくてエラーを出すわけだけど。

しかし、それらのDLLが、exeファイルとは別ファイルとして同梱されている状態なら、17文字制限は絡んでこないので、exeもちゃんと動作してくれる。

つまり、対策としては…。
  • mkexy で生成した .exy に対して、DLL関連を追記して、どうにか1つのexeにしよう、などと考えてはいけない。
  • 必要な DLL は、exe と同階層にまとめて置いておくことを心掛ける。
こうすれば、一応は動作してくれる exe を、exerb を用いて作れなくもない。

ただ、今回は、スクリーンセーバを作りたいという話なので…。できれば、1つのexeファイルで動いてくれるほうがありがたいわけで…。

結論。 :

他の用途ならともかく、Ruby/SDLを使ってWindows用スクリーンセーバを作りたいなら、exerbは諦めてocraを使いましょう。という話になるのかなと。ocraで作ったexeは起動が遅いけど、その分簡単にexe化できるメリットもあるし…。

そもそも、exerb は Ruby 1.8 までしか対応してないので…。今時使われる Ruby は、Ruby 2.x、もしかすると Ruby 3.x だったりするはず。フツーは exerb を使いたくても使えないだろうから、今更な話ではあるなと…。今回、Ruby/SDL を Windows上で使いたいが故に、あえて Ruby 1.8 を使っただけなので…。「せっかく Ruby 1.8 を使うなら exerb もイケるかな」と欲を出しちゃって大変なことになったという…。

2022/08/20追記。 :

Windows上で Ruby/SDL 1.3.1.1 を利用する際に必要になるdllを一応列挙しておきます。
jpeg.dll
libcharset-1.dll
libfreetype-6.dll
libiconv-2.dll
libpng12.dll
libtiff.dll
ogg.dll
SDL.dll
SDL_image.dll
SDL_mixer.dll
SDL_ttf.dll
SGE.dll
smpeg.dll
vorbis.dll
vorbisfile.dll
zlib1.dll

Ruby 2.1.1.1 の場合は、またちょっと違ってくるかと…。rubysdl-2.1.1.1-mswin32-1.8.7-p174.zip 内の dll/ の中には以下が入っているようだけど…。
jpeg.dll
libcharset-1.dll
libfreetype-6.dll
libiconv-2.dll
libogg-0.dll
libpng12-0.dll
libtiff-3.dll
libvorbis-0.dll
libvorbisfile-3.dll
mikmod.dll
SDL.dll
SDL_image.dll
SDL_mixer.dll
SDL_ttf.dll
SGE.dll
smpeg.dll
zlib1.dll

Ruby/SDL 1.3.1.1 と比べて、以下のファイルがリネーム or 追加されている模様。
libogg-0.dll
libpng12-0.dll
libtiff-3.dll
libvorbis-0.dll
libvorbisfile-3.dll
mikmod.dll

2022/08/20() [n年前の日記]

#1 [ruby] Rubyスクリプトに画像ファイルを含める

Ruby/SDL を使ったスクリプト内に、png画像を含めてしまいたい。

exerbを使って Rubyスクリプトをexe化する際、画像ファイルもRubyスクリプト内に含めてしまったほうが都合がいいのかなと思ってしまったので、そういうことができるかどうか試してみた。

まあ、Ruby/SDLを使用しているスクリプトを、exerbでexe化するのは _色々問題があって無理だった のだけど。せっかく実験したので、一応メモしておく。

動作確認環境は以下。
昔、似たようなことをやった記憶がある。日記を検索してみたら、DXRuby を使ってそういうことをしていた。

_Rubyスクリプトの中に画像を仕込むソレ

要するに、png画像を base64文字列に変換してスクリプト内に記述しておけば目的は果たせそう。

_Base64 - Wikipedia

base64への変換スクリプト。 :

png画像等をbase64に変換するスクリプトは以下。

_image2base64.rb
infile = ARGV[0]
bin_data = File.binread(infile)
base64_text = [bin_data].pack("m")
puts base64_text

注意点。Ruby 1.9.3 なら動いたけれど、Ruby 1.8.7 では動かなかった。Ruby 1.8.7 には File.binread() が無かった。

実行は以下。hoge.png を読み込んで、tmp.txt に base64 の形で出力する例。
ruby image2base64.rb hoge.png > tmp.txt

これで得られた base64文字列を、Rubyスクリプトのどこかにコピペしてしまえばいい。

base64を読み出して使うスクリプト。 :

base64文字列を読み出して、Ruby/SDLのウインドウに描画してみる。

_03_imgdisp_rubysdl.rb
require "sdl"
require "stringio"

SCRW, SCRH = 512, 288

# SDL init
SDL.init(SDL::INIT_EVERYTHING)
screen = SDL::Screen.open(SCRW, SCRH, 0, SDL::SWSURFACE)

# read base64 text
base64_text = ""
DATA.each { |l| base64_text += l.chomp }
bin_data_io = StringIO.new(base64_text.unpack("m")[0], "rb")

# load image from StringIO
img = SDL::Surface.loadFromIO(bin_data_io).display_format

# main loop
loop do
  while event = SDL::Event2.poll
    case event
    when SDL::Event2::Quit
      exit
    when SDL::Event2::KeyDown
      exit if event.sym == SDL::Key::ESCAPE
    end
  end

  screen.fill_rect(0, 0, SCRW, SCRH, [0, 100, 200])  # fill bg
  screen.put(img, 32, 32)  # draw sprite
  screen.update_rect(0, 0, 0, 0)  # update screen
  SDL.delay(16.6)  # wait
end

__END__
iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAYFBMVEUA/wB3
CQKFBwSPCAGIDgCaCAWkBgGlCQ6dDgC0DACpEAC0Dw+pFhvIFgTJHB+gLSbF
KRzGNC3fLzHFS0bXRz7cX1i8bmvldW/hf3rNlZLnlpHlsa7zwr/v1NP46ej/
//9Nzsg7AAAAAXRSTlMAQObYZgAABZ5JREFUWIW9V9uWmzoMnWCMARPAgRAI
GP7/L7sl2eB0pqs9L0deTNqZ7ItkWcDX1/8Qx3Hs2/qen2OLsHd7L/4TfKfl
iWB43Nv73VZFqbVS/4b223t+ze/Ng+H1HPu2rWxR61J1Kv8H9DqNd4nXtlEO
rq2qqi600pnKsttfrK+v6n7Ga/fveXBVTQQlMsiz2+3PDCjbOlTVXRbHduyL
I/26RAYqy2/ZnwmOY5ttn6ARMzZj6YwpjS61yXJk0P2JIWhRXB6eOxjWTmvd
YA8axQ5+Jjj8ZOsa2d7THIaDYp2Ab5ijQxbdTwz72hG6/s3Dk7ph25Yh4FXG
dfgJ39S1MHw4mA9043uen070wZD9tJN+qVP908P97efn6/kcR+eCPtXgG4Gf
6qhfX/6B77eV0OPYu75H/uiD7IcqEr4U7FUD5hn21+jGHtH2lSP/tx8ciP7p
4J7UcPWCbulnb5Xo/0bgp0L0P2tAMfr13le8bG9tZdXt9o1gX8r6w0H0gM/l
mCBM6MrSspHhA0/6CfqqQ+28d6xv46qFIemDA/tflqKfsMhaDy+6uCr8qyrA
oEFwjZXdd2VZftTg1K8mf0xBtyZsXdQFQqd9tE/Af6uBLIejPABd1cRAaMLz
qU4KoDXwxXc01oJDVFhgBKepE6mRbtlFsHYlM9Snh8hU1xMO8kowaMK34LmT
Mn11AH6FbyQ1ONfgcYx9TnhOPOLBoE6CNcvxyzKxEDzUdQc8TvKkC9KXHCLD
SbDfcpUrcljWSS/Qp1n33SOmgvWBFw+Ez3QkWEgfOZQfHigIv23rus7soPxw
oCPBPrEfzkE8lKxf1mbBNEfM78lorl9SAxUJDk8zFlXQbCHxQPgZd6YXopH6
a3Xu4kWwdFISYYh1KAi/0RTicHH/JCQDJkCFG5pwuVLRg9jo1n0N6Of4dCXr
U6Di8KxPAu+caTgHYgBHXlIVCD+OQ1gyCaN4qEAg2DBrGky5jHeCFlkY/P52
Iy+Ovgz6gUAnBIOzvYlZMIcuJ78vND8lWqzUwC27MgDBCw77jhkkj7Jbjv0N
VAw8ldxNrAAbyBMC/3rCgyv5XsHfmVa6vQGF+UeXpc/iNIB5nKuEYH+/4KF3
TWDolh2kMn/OOYRpdhXglufkwAjByk8vxMB16JA99K/pdy596fMumsIwwfze
V/bg4KFb+PBOpGtF+7x0yP+W5eQAeCEYn34XD8O0eHoKwNk1BU/ASqYYppmp
TClo7AA7AD4QYOTRndv7ndE4vh3tZIEphglKP4WlSPTzXFljLeP7ceZnh32X
k49nAKOlG4owB/mqC6kA62MBzwT9ox89YzfPJ3/F0W2kz2TKnUtf+ghrWyFo
+2YCcnuvcvLnuTFGRwbqqyKETvQTB31fNm/GhpM/oDo6zm/pPsoGeVAHfndQ
Wd0MjJSzPw5sABPyZMh5WBRFfssTB61txUFpbPN8nZPj2VAKwYM4YASR5Ek0
LeO/bN1oa92ImUFPMI/RmbMEcQZlUn9ldOJAB4IKataaUdAP6mjST6so+pk2
xhoVq5A17YMJ6LvWmHZ8PHpao0kchAN44a05HajgAE999ADsCI/o28aEbVSX
fo4JRp2HulkdixAc0LMveeapg2WjAZXqq4LwzID5KZyRgPGIMLlME2uQ4HVQ
l/nUZKmDJjgw+GuP73zgM64/44M+hyOGWycEX4KnvWwZfzZyrL8y9tJ/8HLc
CIFAh5OjWeTagzjAzupFfTA8WrwvNKcDeX5nnybdA3KA6iXeWZ/D4f40hpt7
7Fcw0JT5cHDt3ice0eQuEOD9RWUNyoU5luwBM3DPqRwecUJc6xKGsRniAw4X
m+5LxeXgPEO5Svqf/qc6sOFe94gZIKDRZbg7JnhNO0WXCp8cHV1fP4XMWy14
JW8kcf39HVco8BamGsPvQ/+G4PgFlvOHReRcvJgAAAAASUVORK5CYII=

実行すると以下のようなウインドウが開く。画像を描画できている。
ruby 03_imgdisp_rubysdl.rb

03_imgdisp_rubysdl_rb_ss.png


少し解説。

  • このスクリプトは、「__END__」と書かれた行の下に、画像ファイルを変換したbase64文字列を列挙している。
  • Ruby は、「__END__」と書いてある行より下に書かれた行に対して、「DATA」というシンボルでアクセスすることができる。

base64文字列を読み込んで、バイナリデータに変換して、それを StringIO に渡している部分は以下。
base64_text = ""
DATA.each { |l| base64_text += l.chomp }
bin_data_io = StringIO.new(base64_text.unpack("m")[0], "rb")

StringIO というのは、文字列をファイル(IOオブジェクト)のような感覚で利用できるオブジェクトらしい。

_class StringIO (Ruby 1.8.7)


StringIO を渡して、Ruby/SDL で利用できる画像データ(Surface) に変換している部分は以下。
img = SDL::Surface.loadFromIO(bin_data_io).display_format

一般的に、Ruby/SDL で画像ファイルを読み込むなら SDL::Surface.load(filename) を使うけれど、SDL::Surface.loadFromIO(io) を使えば Ruby のIOオブジェクトから画像を読み込める。

_Ruby/SDL Reference Manual (2.x)

別の書き方。 :

「__END__」以下の行を読み込もうとすると1種類のデータしか読み込めなかったりするし、exerb が DATAシンボルに非対応だったりもするので、ちょっと違う書き方をしてみた。

_04_imgdisp_rubysdl_2.rb
require "sdl"
require "stringio"

SCRW, SCRH = 512, 288

BASE64_STRINGS = [
  "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAYFBMVEUA/wB3",
  "CQKFBwSPCAGIDgCaCAWkBgGlCQ6dDgC0DACpEAC0Dw+pFhvIFgTJHB+gLSbF",
  "KRzGNC3fLzHFS0bXRz7cX1i8bmvldW/hf3rNlZLnlpHlsa7zwr/v1NP46ej/",
  "//9Nzsg7AAAAAXRSTlMAQObYZgAABZ5JREFUWIW9V9uWmzoMnWCMARPAgRAI",
  "GP7/L7sl2eB0pqs9L0deTNqZ7ItkWcDX1/8Qx3Hs2/qen2OLsHd7L/4TfKfl",
  "iWB43Nv73VZFqbVS/4b223t+ze/Ng+H1HPu2rWxR61J1Kv8H9DqNd4nXtlEO",
  "rq2qqi600pnKsttfrK+v6n7Ga/fveXBVTQQlMsiz2+3PDCjbOlTVXRbHduyL",
  "I/26RAYqy2/ZnwmOY5ttn6ARMzZj6YwpjS61yXJk0P2JIWhRXB6eOxjWTmvd",
  "YA8axQ5+Jjj8ZOsa2d7THIaDYp2Ab5ijQxbdTwz72hG6/s3Dk7ph25Yh4FXG",
  "dfgJ39S1MHw4mA9043uen070wZD9tJN+qVP908P97efn6/kcR+eCPtXgG4Gf",
  "6qhfX/6B77eV0OPYu75H/uiD7IcqEr4U7FUD5hn21+jGHtH2lSP/tx8ciP7p",
  "4J7UcPWCbulnb5Xo/0bgp0L0P2tAMfr13le8bG9tZdXt9o1gX8r6w0H0gM/l",
  "mCBM6MrSspHhA0/6CfqqQ+28d6xv46qFIemDA/tflqKfsMhaDy+6uCr8qyrA",
  "oEFwjZXdd2VZftTg1K8mf0xBtyZsXdQFQqd9tE/Af6uBLIejPABd1cRAaMLz",
  "qU4KoDXwxXc01oJDVFhgBKepE6mRbtlFsHYlM9Snh8hU1xMO8kowaMK34LmT",
  "Mn11AH6FbyQ1ONfgcYx9TnhOPOLBoE6CNcvxyzKxEDzUdQc8TvKkC9KXHCLD",
  "SbDfcpUrcljWSS/Qp1n33SOmgvWBFw+Ez3QkWEgfOZQfHigIv23rus7soPxw",
  "oCPBPrEfzkE8lKxf1mbBNEfM78lorl9SAxUJDk8zFlXQbCHxQPgZd6YXopH6",
  "a3Xu4kWwdFISYYh1KAi/0RTicHH/JCQDJkCFG5pwuVLRg9jo1n0N6Of4dCXr",
  "U6Di8KxPAu+caTgHYgBHXlIVCD+OQ1gyCaN4qEAg2DBrGky5jHeCFlkY/P52",
  "Iy+Ovgz6gUAnBIOzvYlZMIcuJ78vND8lWqzUwC27MgDBCw77jhkkj7Jbjv0N",
  "VAw8ldxNrAAbyBMC/3rCgyv5XsHfmVa6vQGF+UeXpc/iNIB5nKuEYH+/4KF3",
  "TWDolh2kMn/OOYRpdhXglufkwAjByk8vxMB16JA99K/pdy596fMumsIwwfze",
  "V/bg4KFb+PBOpGtF+7x0yP+W5eQAeCEYn34XD8O0eHoKwNk1BU/ASqYYppmp",
  "TClo7AA7AD4QYOTRndv7ndE4vh3tZIEphglKP4WlSPTzXFljLeP7ceZnh32X",
  "k49nAKOlG4owB/mqC6kA62MBzwT9ox89YzfPJ3/F0W2kz2TKnUtf+ghrWyFo",
  "+2YCcnuvcvLnuTFGRwbqqyKETvQTB31fNm/GhpM/oDo6zm/pPsoGeVAHfndQ",
  "Wd0MjJSzPw5sABPyZMh5WBRFfssTB61txUFpbPN8nZPj2VAKwYM4YASR5Ek0",
  "LeO/bN1oa92ImUFPMI/RmbMEcQZlUn9ldOJAB4IKataaUdAP6mjST6so+pk2",
  "xhoVq5A17YMJ6LvWmHZ8PHpao0kchAN44a05HajgAE999ADsCI/o28aEbVSX",
  "fo4JRp2HulkdixAc0LMveeapg2WjAZXqq4LwzID5KZyRgPGIMLlME2uQ4HVQ",
  "l/nUZKmDJjgw+GuP73zgM64/44M+hyOGWycEX4KnvWwZfzZyrL8y9tJ/8HLc",
  "CIFAh5OjWeTagzjAzupFfTA8WrwvNKcDeX5nnybdA3KA6iXeWZ/D4f40hpt7",
  "7Fcw0JT5cHDt3ice0eQuEOD9RWUNyoU5luwBM3DPqRwecUJc6xKGsRniAw4X",
  "m+5LxeXgPEO5Svqf/qc6sOFe94gZIKDRZbg7JnhNO0WXCp8cHV1fP4XMWy14",
  "JW8kcf39HVco8BamGsPvQ/+G4PgFlvOHReRcvJgAAAAASUVORK5CYII=",
]

# SDL init
SDL.init(SDL::INIT_EVERYTHING)
screen = SDL::Screen.open(SCRW, SCRH, 0, SDL::SWSURFACE)

# read base64 text
base64_text = BASE64_STRINGS.join
bin_data_io = StringIO.new(base64_text.unpack("m")[0], "rb")

# load image from StringIO
img = SDL::Surface.loadFromIO(bin_data_io).display_format

# main loop
loop do
  while event = SDL::Event2.poll
    case event
    when SDL::Event2::Quit
      exit
    when SDL::Event2::KeyDown
      exit if event.sym == SDL::Key::ESCAPE
    end
  end

  screen.fill_rect(0, 0, SCRW, SCRH, [0, 100, 200])  # fill bg
  screen.put(img, 32, 32)  # draw sprite
  screen.update_rect(0, 0, 0, 0)  # update screen
  SDL.delay(16.6)  # wait
end

base64 をバイナリ化するあたりは、ちょっとだけ記述が減ってくれた。
base64_text = BASE64_STRINGS.join
bin_data_io = StringIO.new(base64_text.unpack("m")[0], "rb")

#2 [zatta] スクリーンセーバ作成に対する憧れ

自分語り。思考メモ。スクリーンセーバにまつわるアレコレを脈絡なくメモ。

ここ最近、スクリーンセーバ作成についてアレコレ実験しているけれど。「ちょwwwおまwww今時スクリーンセーバ作成ってwww」と笑われそうだなと不安になりながら作業メモを書き残してる状態で。

でも、仕方ない。スクリーンセーバ作成というお題は、自分の中ではずっと気になってるお題でして…。

大昔、Windows95 が普及し始めて、CD-ROMに入ったデスクトップアクセサリー云々が店頭販売されてた当時。「スクリーンセーバって面白いな。見ていて楽しい」「自分も作れないものかなあ」と気になったのですが。しかし、作り方がさっぱり分からない。一体何をどうすればいいんだろう。そんなもやもやが、喉に刺さった魚の小骨のように自分の中ではずっと残っていて。プログラマーとしてはフツーに悔しいじゃないですか。作り方の概要すら知らないなんて…。少なくとも自分は、その状態ってちょっと恥ずかしいし情けない状態だなと感じてしまって。そこにはプログラマーとしての敗北感があるわけですよ。

あれから四半世紀。さすがに今なら簡単に作れるようになってるんじゃないかと、時々思い出したように関連情報をググってみたりして。仕様その他がなんとなく分かってきたような気分になってきたので、こうして実験を始めちゃったという。まあ、使った言語やライブラリはアレだけど、一応作れることは分かってきて、多少は気分的にスッキリ…。

いや、スッキリしてない。四半世紀も経ってるのに、まだこんなに作成が難しいのかと、正直ガッカリした面もあるなと。

もっと簡単に作れないものかなあ…。

余談。 :

スクリーンセーバ作成の民主化だの、スクリーンセーバはCGアートだの、普段考えてたことをメモしてたのだけど、ふと日記を検索したらまるっきり同じことを以前メモしてあった…。

_スクリーンセーバの作り方を調べてる

2022/08/21() [n年前の日記]

#1 [pc] 親父さん用PCの無線LAN子機を交換

親父さんから、PCがネットに接続できないと不具合報告。

確認してみたところ、バスパワー(ACアダプタ無し)の4ポートUSB2.0ハブ経由で接続した無線LAN子機、ELECOM WDC-433DU2HBK が反応しない。

_ワイヤレスアダプター 433M - WDC-433DU2HBK

USBハブのLEDは光っているので、その先にUSB機器が接続されていると認識されている状態なのだろうけど。無線LAN子機内のLEDがいつまで経っても光らない。本来はチカチカと青く光るはずなのだけど。

とうとう壊れたのかもしれないなと、自分の部屋で埃を被っていた無線LAN子機、IO-DATA WN-G300UA を発掘して交換してみた。

_WN-G300UA | Wi-Fi(無線LAN)子機 | IODATA アイ・オー・データ機器

特に何もしなくても認識されて、無線LANルータと接続できたのだけど、一応念のためにコントロールパネル経由で WDC-433DU2HBK のドライバをアンインストールしてから、WN-G300UA のドライバ、wng300ua112.exe をインストールしておいた。しばらくこれで様子を見てもらおう…。

懸念事項。 :

ググったところ、各無線LAN子機に使われているチップは以下の模様。
  • IO-DATA WN-G300UA のチップは、Realtek RTL8192CU。
  • ELECOM WDC-433DU2HBK のチップは、Realtek RTL8811AU。
_教えてえらいひと! | tuneoの日記 | スラド
_ELECOM WDC-433DU2H 11ac Wireless Adapter USB Drivers - Download Device Drivers - DriverAgent.com - DriverAgent.com
_帰ってきた、無精ヒゲの一応日記: WDC-433DU2H のドライバ

しかし、ウチでは、RTL8192CU を使った無線LAN子機はどうも不調で…。そのうち親父さんから「ネットに繋がらないぞ」と文句が出るかもしれない。万が一、新しく入手することを考えて、使用チップを把握しつつ候補を探しておくか…。

古い無線LAN子機は生きてた。 :

WDC-433DU2H は壊れたのかと思ったけれど、自分のサブPC(Athlon5350機)に繋いでみたところ、LEDが光った。ドライバ(WDC-433DU2HBK_Win_1030.25.0701.2017.zip)をインストールしたらルータとも接続できた。

どうやら親父さん用PCと接触不良を起こしてただけっぽいな…。親父さんはUSBハブ周りをとにかく雑に扱う人だから…。

それとも電力不足だろうか。と言っても、親父さん用PCのUSBハブには、無線LAN子機の他に、ワイヤレスマウス受信機、プリンタケーブルしか差してないのだけどな…。プリンタケーブルはそれほど電流を流さないだろうし、無線LAN子機 + ワイヤレスマウス受信機なら動きそうなものだけど。

簡易USBスタンドモドキを自作。 :

以前100円ショップで購入したアレコレを利用して、簡易ではあるけれどUSBスタンドモドキを自作した。親父さん用PCが置いてあるPCデスクの柱に貼り付けておいた。これで無線LAN子機の固定と向き調整ができるはず。

材料は以下。
  • ダイソー コードクリップ 4P
  • Seria 超強力マグネット 4P
  • ダイソー ビッグストロー 口径12mm
  • USB延長ケーブル
  • 両面テープ

_ダイソー コードクリップ 4P のパーツレビュー | ヴェルファイア(【ShiN】) | みんカラ
_セリアの超強力マグネット | 100均マグネットの活用術10選
_タピオカ用のストローはどこで買える?これでおうちでもタピ活できちゃう?|mamagirl [ママガール]

※ 2022/09/19追記。

写真を撮っておいたので一応アップロード。

tiny_usb_stand02.jpg

tiny_usb_stand03.jpg

tiny_usb_stand01.jpg

2022/08/22(月) [n年前の日記]

#1 [prog][windows] msys2 + MinGW-w64でスクリーンセーバをビルドしようとしてハマった

以下で紹介されている、MinGW用のスクリーンセーバサンプルをビルドしてみたくなった。環境は Windows10 x64 21H2。

_よしいずの雑記帳 スクリーンセーバーの作り方

以下の2つのファイルをコピペして作成。文字コードは UTF-8N にした。(※ 2022/08/23追記。文字コードはSJISにしたほうが良かったのかもしれない。)

_scrnsave.c
_resource.rc

また、Makefile も作成した。

_Makefile
screensv.scr: screensv.o resource.o
        gcc screensv.o resource.o -lscrnsave -mwindows -o screensv.scr

screensv.o: screensv.c
        gcc -c screensv.c -o screensv.o

resource.o: resource.rc
        windres resource.rc -o resource.o

.PHONY: clean
clean:
        rm -f *.scr
        rm -f *.o

結論を先に書いておくけど、msys2 + MinGW-w64 ではダメ。MinGW でビルドしましょう…。

msys2 + MinGW-w64ではスクリーンセーバをビルドできない :

msys2+ MinGW-w64 でビルドしようとしたら失敗した。screensv.o と resource.o は生成されたけど、リンクエラーが出る。

$ gcc --version
gcc.exe (Rev1, Built by MSYS2 project) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
...

$ windres --version
GNU windres (GNU Binutils) 2.39
Copyright (C) 2022 Free Software Foundation, Inc.
...

$ make --version
GNU Make 4.3
このプログラムは x86_64-pc-msys 用にビルドされました
Copyright (C) 1988-2020 Free Software Foundation, Inc.
...

$ make
gcc -c screensv.c -o screensv.o
windres resource.rc -o resource.o
gcc screensv.o resource.o -lscrnsave -mwindows -o screensv.scr
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: screensv.o:screensv.c:(.text+0x229): undefined reference to `DefScreenSaverProc'
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-crt0_c.o): in function `main':
C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crt0_c.c:18: undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:2: screensv.scr] エラー 1

スクリーンセーバ作成に関係してくるファイルは以下だろうか…。ちなみに自分の環境では msys2 を C:\msys64\ にインストールしてある。
  • C:\msys64\mingw64\include\scrnsave.h
  • C:\msys64\mingw64\lib\libscrnsave.a
  • C:\msys64\mingw64\lib\libscrnsavw.a

scrnsave.h はそれらしい内容が書かれているように見えるけれど。libscrnsav*.a は、どちらもファイルサイズが990Byte と異様に小さい上に、全く同じサイズというのが気になる。

余談。巷の関連記事では、スクリーンセーバの作成には scrnsave.h と scrnsave.lib が必要と書いてあるのだけど、それは Microsoft Visual C++用の話のようで。MinGW の場合は libscrnsave.a か libscrnsavw.a が必要になるらしい。最初、そのことが分からなくて、scrnsave.lib を探して「無いな…MinGWでは作れないのかな…?」と勘違いしてた。

さておき。msys2 + MinGW-w64 でリンクエラーが出る原因についてググっていたら、以下の話を見かけた。

_Re: [Mingw-w64-public] [mingw-w64:discussion] No symbols in libscrnsave.a; source file scrnsave.c c | MinGW-w64 - for 32 and 64 bit Windows
_MinGW-w64 - for 32 and 64 bit Windows / Discussion / Help: No symbols in libscrnsave.a; source file scrnsave.c commented out
_MinGW-w64 - for 32 and 64 bit Windows / Bugs / #351 Scrnsave lib empty

おそらくだけど、「libscrnsav*.a の元になる scrnsave.c が全部コメントアウトされているけれどこれってどういうこと?」と言っているのだろう。ナニソレ、マジかよ。

MinGW-w64のソースコードであろう、mingw-w64-v10.0.0.zip をDLして解凍して中を覗いてみた。

_MinGW-w64 - for 32 and 64 bit Windows download | SourceForge.net

以下がスクリーンセーバ関連のファイルだろうか。
  • mingw-w64-v10.0.0\mingw-w64-crt\libsrc\scrnsave.c
  • mingw-w64-v10.0.0\mingw-w64-headers\include\scrnsave.h

scrnsave.c を開いて確認してみたら、たしかに #if 0 - #endif で全内容がコメントアウトされていた。これではリンクできるわけが無い…。なんでこんなことをしたんだ…。

アレかな。メンテナの方々が「今時スクリーンセーバなんて作るやつ居ねえだろ」と勝手に決めつけてやらかしたのかな…と邪推。まあ、MinGW から fork して MinGW-w64 を作り始めた時に、まずは全体のビルドが通る状態にすることを優先していただろうし。そのまま放置されたのかも…。

そんなわけで、msys2 + MinGW-w64 では、スクリーンセーバをビルドすることはできないと分かった。

※ 2022/08/23追記。github にも scrnsave.c があった。1行目から #if 0 があることが分かるかと。

_mingw-w64/scrnsave.c at master - Alexpux/mingw-w64

MinGWでビルド :

msys2 + MinGW-w64 ではなく、MinGW + msys でビルドを試してみた。

> gcc --version
gcc (MinGW.org GCC Build-2) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
...

> windres --version
GNU windres (GNU Binutils) 2.32
Copyright (C) 2019 Free Software Foundation, Inc.
...

> make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
...

必要なファイルは以下だろうか。ちなみに自分の環境では、MinGW を D:\MinGW\ にインストールしてある。
  • D:\MinGW\include\scrnsave.h
  • D:\MinGW\lib\libscrnsave.a
  • D:\MinGW\lib\libscrnsavw.a

すんなりビルドが通った…。

出来上がった screensv.scr を右クリック → Test、を選んだら、画面一杯に丸が描画され続けた。また、マウスを動かしたら終了した。

MinGW でビルドした .exe や .scr は32bit版だろうから…。Windows10 64bit版を使っているので、C:\Windows\SysWOW64\ 以下に screensv.scr をコピーして動作確認した。正常に動作してくれた。

ただ、スクリーンセーバ設定画面で、「設定」をクリックすると、文字化けしたメッセージが表示されてしまう。

2022/08/23(火) [n年前の日記]

#1 [prog][windows] Windows10にMinGWをインストールし直した

Windows10 x64 21H2上で、MinGW + msys (msys2 + MinGW-w64に非ず)を再インストールしてみた。インストール直後の状態で、スクリーンセーバをビルドできるのかどうかが気になったので…。

MinGW (MinGW が正しい表記らしい) + msys というのは、Windows上で、CコンパイラやC++コンパイラを使えるようにしてくれるツール群、という説明で合っているのだろうか。とにかく、コイツをインストールすれば、gcc という有名なCコンパイラが無料で使えるようになる。

_MinGW - Wikipedia

MinGW は32bit版プログラムの生成しか対応してないので、今時は msys2 + MinGW-w64 という、64bit対応の環境が使われることが多いのだけど。スクリーンセーバの作成をしたいとなると、MinGW-w64 はそのへん対応してないので、MinGW を使うしかなく。 *1

さておき。せっかくなので、2022/08時点でのインストール手順をメモ。そのうち自分も手順を絶対に忘れそうなので…。

インストーラを入手。 :

MinGW の入手先は、昔は SourceForgeだったけど、今は OSDN になっているのだとか。まあ、細かい各パッケージは SourceForge からダウンロードしているっぽいけれど。

_MinGW - Minimalist GNU for Windows Project Top Page - OSDN

mingw-get-setup.exe というファイルをダウンロードする。

インストーラを実行。 :

mingw-get-setup.exe を実行する。以下の画面が出てくる。

mingw_install_ss01.png
  • 「Install」をクリックして先に進む。

MinGW のインストールディレクトリその他を指定。

mingw_install_ss02.png
  • デフォルトでは C:\MinGW になってる。
  • 右のほうの「Change」をクリックすれば、インストール場所を変更できる。自分は D:\MinGW に変更した。
  • 下のほうにあるチェックボックスは、「スタートメニューに登録するか」「デスクトップにショートカットファイルを作るか」といったオプション設定。

その後のインストール作業に必要になるプログラムがいくつかダウンロードされる。

mingw_install_ss03.png
  • ダウンロードが終わったら「Continue」をクリック。

MinGW関係の色々なパッケージを選んでダウンロードできる画面が出てくる。

mingw_install_ss04.png
  • 左のほうで「Basic Setup」を選択。
  • 右のほうで、「mingw-developer-toolkit-bin」の頭のチェックボックスを左クリック。

チェックボックスを左クリックするとメニューが出てくる。

mingw_install_ss05.png
  • 「Mark for Installation」を選んで、インストールを指示。

その他のインストールするジャンルを選択する。

mingw_install_ss06.png
  • おそらくだけど、ada、fortran、objc は、使う場面がほとんど無いと思う。それ以外は全部チェックを入れる感じでいいかと。もちろん、「いいや、俺は (ada|fortran|Objective-C) をバリバリ使うんだ」という人はインストールすればよろしいかと。

チェックをつけ終わったら、変更を指示。

mingw_install_ss07.png
  • 「Installation」→「Apply Chnages」を選択。

パッケージのダウンロード画面が出てくる。

mingw_install_ss08.png
  • 「Apply」をクリック。

色々なパッケージがダウンロードされていく。

mingw_install_ss09.png

ダウンロードが終わると、パッケージが展開される。

mingw_install_ss10.png
  • 「Close」をクリックして閉じる。
  • 左のほうにあるチェックボックスを入れれば、次回からはダウンロードと展開が終わったら自動で閉じてくれるようになるらしい。自分は試してないけれど…。

ちなみに、左のほうで「All Packages」を選べば、全パッケージの一覧が表示される。

mingw_install_ss11.png

こんな感じの流れで、パッケージのインストールは終了。

パスを通す。 :

インストールは終わったけれど、この状態では MinGW の各ツールにパスが通って無いので使えない。

えてしてその手の解説記事では、Windowsの環境変数 PATH に、以下の2つのディレクトリを追加していくけれど…。
  • MinGWインストールフォルダ\bin
  • MinGWインストールフォルダ\msys\1.0\bin

自分も昔はそうしていたけど、他の開発関連ツール群と衝突してハマったことがあるので、それ以来、以下のようなbatファイルを作って、MinGW を使いたい時だけ環境変数を変更して利用している。

_mingw.bat
@echo off
@rem MinGW,MSYS enable

@rem Please set your MinGW install Directory.
set MINGW_PATH=C:\MinGW

set MSYS_PATH=%MINGW_PATH%\\msys\1.0
set MINGWADDPATH=%MINGW_PATH%\bin;%MSYS_PATH%\bin
set PATH=%MINGWADDPATH%;%PATH%
set C_INCLUDE_PATH=%MINGW_PATH%\include
set CPLUS_INCLUDE_PATH=%MINGW_PATH%\include
set LIBRARY_PATH=%MINGW_PATH%\lib

echo MinGW enable.
echo add path %MINGWADDPATH%
  • set MINGW_PATH=C:\MinGW の行を、自分の環境に合わせて変更すること。

やっていることは、環境変数 PATH の一番最初に MinGW と msys のパスを追加したり、C/C++ 関係のヘッダファイルやライブラリファイルの場所を環境変数に指定しているだけ。

例えば、DOS窓を開いて mingw.bat を実行すれば、その後は mingw が使えるようになる。以下を眺めれば、mingw をインストールした場所にパスが通って各ツールが使えているのが分かるかと…。
> mingw
MinGW enable.
add path D:\MinGW\bin;D:\MinGW\\msys\1.0\bin

> which which
"D:\MinGW\msys\1.0\bin\which"
"D:\home\bin\which.cmd"

> which gcc
"D:\MinGW\bin\gcc.exe"

> which make
"D:\MinGW\msys\1.0\bin\make.exe"

> gcc --version
gcc (MinGW.org GCC Build-2) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
...

> make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
...
This program built for i686-pc-msys

コンパイルができるか動作確認。 :

gcc を使ってコンパイルができるか動作確認してみる。

以下のCソースファイルをコンパイルする。「Hello world!」と出力するだけのプログラム。

_hello.c
#include <stdio.h>

int main(void)
{
    printf("Hello world!\n");

    return 0;
}

gcc でコンパイル。
> gcc hello.c

> a.exe
Hello world!

> gcc hello.c -o hello.exe

> hello.exe
Hello world!
  • gcc hoge.c で、hoge.c がコンパイルされて a.exe という実行ファイルができる。
  • gcc hoge.c -o hoge.exe で、hoge.c がコンパイルされて、-o で指定したファイル名、hoge.exe という実行ファイルができる。

ということで、gcc でコンパイルすることができた。MinGW のインストールは成功。

*1: MinGW-w64 の libscrnsave.a、libscrnsavw.a は、ファイルはあるけど中身が空っぽなのです…。

#2 [prog] スクリーンセーバのビルドの実験中

Windows10 x64 21H2 + MinGW (msys2 + MinGW-w64に非ず)で、スクリーンセーバのビルドができるかどうか実験中。

以下のページで紹介されているサンプルを利用させてもらって実験しているところ。ありがたや。

_よしいずの雑記帳 スクリーンセーバーの作り方

動作させてみた様子は以下。

メッセージが文字化けする件。 :

ビルドはできたものの、「設定」ボタンをクリックすると文字化けしているメッセージが表示されてしまう。改善できないかと試していて結構ハマってしまった。

てっきり resource.rc 内のメッセージが表示されているものと思い込んで色々試してみたのだけど、いくら試してもメッセージが変わらない・修正できなくて悩んでしまった。
  • UTF-8Nで保存してた resource.rc をSJISで保存し直したり。
  • テキストを英数字で記述し直したり。
  • Windows10がどこかにリソースをキャッシュして、そっちが表示され続けているのではと疑って、クリーンアップ機能その他を利用してみたり。
  • Resource Hacker というツールを導入して、.scr の中に入っているリソースを確認してみたり。
  • ファイル名を変更してみたり。

_Resource Hacker

原因は自分の勘違いだった。設定ボタンをクリックして表示されるメッセージは、screensv.c 内で記述されていた。resource.rc は関係無かった…。screensv.c 側を変更したら、あっさりダイアログ内のテキストも変わってくれた。しかしそうなると、resource.rc 内の記述はどこで使われるのだろう…。

結局こうなった。 :

手元では、以下の3つのファイルを同じ場所に置いて make と打てば .scr (mgscrsav.scr) が得られる状態になった。

_mgscrsav.c
_resource.rc
_Makefile

ただ、出来上がった mgscrsav.scr を右クリックして「Test」や「構成」を選んでも、以下のダイアログが表示されて実行できなくなってしまった…。

error_ss.png
ファイルを開くことができません
インターネット セキュリティ設定のために、1つ以上のファイルを開くことができませんでした。

どうやら、作業フォルダを、シンボリックリンクを使って、別の場所にあるように見せかけつつ作業してたのがマズかったようだなと…。オリジナルのフォルダに移動してから、.scrを右クリック→「Test」「構成」を選んだら動作してくれた。
  • 「Test」を選ぶ : フルスクリーン表示
  • 「構成」を選ぶ : 設定ダイアログが表示される状態。

32bit版プログラムをWindows10 64bit上で動かしている状態なので、C:\Windows\SysWOW64\ 以下に .scr をコピーして動作確認。動いてくれた。
  • Windows 64bit版、スクリーンセーバが32bit版なら、C:\Windows\SysWOW64\ 以下に .scr をコピー。
  • Windows 64bit版、スクリーンセーバが64bit版なら、C:\Windows\System32\ 以下に .scr をコピー。
  • Windows 32bit版なら、C:\Windows\System32\ 以下に .scr をコピー。

2022/08/24(水) [n年前の日記]

#1 [prog][windows] Borland C++ Compilerを試用

Windows10 x64 21H2上で、Borland C++ Compiler 7.3 と 5.5 (以下、bccと記述)を試用してみた。教育目的、あるいは個人利用の範囲なら無料で利用できるらしい。

セットアップファイルの入手。 :

セットアップファイル名は以下。
  • bcc 7.3 (Borland C++ Compiler 7.3) : BCC102.zip
  • bcc 5.5 (Borland C++ Compiler 5.5) : freecommandlinetools2.exe
今現在、7.3 のほうは公式サイトから入手できるけど、5.5 はもう公開されてないっぽい。ただ、セットアップファイル名でググったら、学校関係の授業のページで入手できなくもない感じだった。

_ホーム - Embarcadero
_C++ Compiler - Free Tool - Embarcadero

7.3 の入手は、公式サイトの上のほうにある「無料版」にマウスカーソルを合わせるとメニューが出てくるので、「C++ Compiler」をクリック。「今すぐダウンロード」をクリックすると入手ページが開かれる。入手にはメールアドレスその他の入力が必要。

インストール。 :

bcc 7.3 (BCC102.zip) は、解凍して任意のフォルダに置いておけば良さそう。

bcc 5.5 (freecommandlinetools2.exe) は、実行するとインストーラが起動する。

bcc 7.3 は、インストールフォルダ\bin\ に PATH を通せば利用できるけど、bcc 5.5 は、2つほどファイルを作成する必要がある模様。bcc32.cfg と ilink32.cfg を作らないといけない。
  • インストールフォルダ\bin\bcc32.cfg
  • インストールフォルダ\bin\ilink32.cfg
自分の環境では、D:\Dev\borland\bcc55\ にインストールしたので、各ファイルの内容は以下になった。

_bcc32.cfg
-I"D:\Dev\borland\bcc55\Include"
-L"D:\Dev\borland\bcc55\Lib"

_ilink32.cfg
-L"D:\Dev\borland\bcc55\Lib"

パスを通す。 :

どちらも、環境変数 PATH に、インストールフォルダ\bin\ を追加して使うことになる。

Windowsの環境変数にあらかじめ追加登録しておいてもいいけれど、自分の場合、以下のようなbatファイルを作成して使うことにした。PATHが通ってる場所に各batを置いておいて、DOS窓で、bcc か bcc55 を実行すれば、PATHの先頭に bccインストールフォルダ\bin が追加されて、bcc が使えるようになる。

_bcc.bat
@echo off
@set BCCPATH=D:\Dev\BCC102\bin

set PATH=%BCCPATH%;%PATH%
@echo enable BCC 7.3 (Borland C/C++ Compiler 7.3)
@echo Add Path [%BCCPATH%]

_bcc55.bat
@echo off
@set BCCPATH=D:\Dev\borland\bcc55\Bin

set PATH=%BCCPATH%;%PATH%
@echo enable BCC 5.5 (Borland C/C++ Compiler 5.5)
@echo Add Path [%BCCPATH%]

コンパイルできるか確認。 :

以下のファイルを作成。

_testhello.c
#include <stdio.h>

int main(void)
{
    printf("Hello.\n");
    return 0;
}

bcc 7.3 なら、以下でコンパイル。
bcc32c testhello.c

bcc 5.5 なら、以下でコンパイル。
bcc32 testhello.c

testhello.exe と testhello.tds というファイルが作成された。testhello.exe を実行すると、「Hello.」と表示された。コンパイルはできた。

参考ページ。 :

2022/08/26追記。 :

上記の作業で初期設定は済んだものと思っていたけど、まだ足りてなかった。

bcc 5.5 のリソースコンパイラ、brc32.exe を利用しようとした際、以下のエラーが出る。ちなみに、bcc32.exeではなくて brc32.exe であることに注意。どこかしらで質問しても、皆さん、bcc32 と見間違えて変な回答をしている…。
        brc32 main.rc scrnsvr.scr
Borland Resource Compiler  Version 5.40
Copyright (c) 1990, 1999 Inprise Corporation.  All rights reserved.

Error main.rc 1 11: Cannot open file: windows.h
「windows.h を開けなかった」と言っている。brc32 は、ヘッダーファイル(*.h)の置いてある場所を知らないのだろう…。

brc32 -h と打ってみると、ヘルプメッセージが出てくる。
> brc32 -h

Syntax: brc32 [options ...] filename
  options marked with a '*' are on by default

  -r                    compile only. Do not bind resources
  -fofilename           set output res filename
  -fefilename           set output exe filename
  -v                    verbose
  -ipath                set include path
  -x                    ignore INCLUDE environment variable
  -dname[=string]       define #define
  -32                *  build 32-bit Windows compatible res/exe files
  -16                   build 16-bit Windows compatible res/exe files
  -Vd.d Mark the .exe file with Windows version provided (4.0 is the default)
  -31  Provided for downward compatibility (build 16-bit res/exe files)
  -w32 Provided for downward compatibility (build 32-bit res/exe files)
  -k                    do not create fastload area (16 bit only)
  -t                    (ignored, for compatibility)
  -? or -h              display this message

-ipath で include のパスを指定できる、と書いてあるが、その下、-x をつけると INCLUDE という環境変数を無効化できる、とも書いてある。

つまり、環境変数 INCLUDE に、ヘッダーファイルのある場所(bcc5.5インストールフォルダ\Include)を指定すればいいのだろう…。
set INCLUDE=(bcc5.5インストールフォルダ)\Include

自分の場合、bcc55.bat というbatファイルを実行すれば bcc 5.5 が使えるようにしてあるので、batファイルを修正して、環境変数 INCLUDE も設定するようにしておいた。

_bcc55.bat
@echo off
set BCCPATH=D:\Dev\borland\bcc55

set BCCBINPATH=%BCCPATH%\Bin
set PATH=%BCCBINPATH%;%PATH%
set INCLUDE=%BCCPATH%\Include

echo enable BCC 5.5 (Borland C/C++ Compiler 5.5)
echo Add Path [%BCCBINPATH%]
echo Set Env INCLUDE=%INCLUDE%

参考ページ。

_BRC32 - リソース シェル - RAD Studio
_■ - Humanity

2022/08/25(木) [n年前の日記]

#1 [xyzzy] xyzzyで行結合

xyzzy 0.2.2.253 で行結合(行連結?)をしたい。ちなみに、環境は Windows10 x64 21H2。

今まで、以下で紹介されてる「セレクションを行結合(join-line)」を便利に使わせてもらっていたのだけど。

_xyzzy の音 - 編集

xyzzy_joinline_ss00.gif

各行を隙間なくピッチリ結合するのがちょっとアレで。各行にスペース1文字を入れつつ結合したい…。

そんなわけで、少しだけ処理を追加して動作を変えた版を、自分用の siteinit.l に追加しておいた、とメモ。

(defun join-line-with-space ()
  (interactive)
  (when (get-selection-type)
    (selection-start-end (start end)
      (narrow-to-region start end)
      (goto-char (point-min))
      (replace-buffer "^[ \t]*\\(.*?\\)[ \t]*" "\\1" :regexp  t)
      (goto-char (point-min))
      (replace-buffer "^\\(.*?\\)\n" " \\1" :regexp  t)
      (goto-char (point-min))
      (replace-buffer "^[ \t]*" "" :regexp  t)
      (widen))))

xyzzy_joinline_ss01.gif

2回目の正規表現置換時に、各行の先頭に空白1文字を入れて、3回目の正規表現で行頭の空白文字を消去してるだけ。

余談。自分の環境では、C-z j と C-z k に割り当てておいた。
(global-set-key '(#\C-z #\j) 'join-line)
(global-set-key '(#\C-z #\k) 'join-line-with-space)

似たような機能が既にあった。 :

上記の内容を ~/.xyzzy or siteinit.l に追加した後で、関連情報をググっていて気が付いた。emacs系エディタには、最初から delete-indentation という機能があるそうで…。M-^ に割り当てられている模様。

現在行を上の行に連結していく機能で、連結時に空白1文字も入れてくれる。キーをポンポンポンと押していくだけで上記と同じ結果を得られてしまう。

xyzzy_joinline_ss02.gif

わざわざ関数を作らなくても良かったのだな…。

サクラエディタの場合。 :


2022/08/26(金) [n年前の日記]

#1 [prog][windows] bccでスクリーンセーバのビルドを試しているところ

bcc 5.5 (Borland C++ Compiler) を使って、スクリーンセーバのサンプルソースのビルドができるか試しているところ。

以下のページで VC++、bcc、lcc でビルドできるサンプルが3つほど紹介されているので、手元の環境でもビルドできるのか確認中。

_Winのスクリーンセーバーを作ってみる

ちなみに、上記ページにも書かれているけれど、bcc には scrnsave.h、scrnsave.lib が付属していないので、そのままだとスクリーンセーバのサンプル群がビルドできない。上記ページには、MinGW版のソースを元にして作成された scrnsave.h、scrnsave.c、scrnsave.lib も紹介されているので、それを利用させてもらうことになる。ありがたや。

ハマった点。brc32がエラーを出す。 :

bcc 5.5 には、brc32.exe というリソースコンパイラが入っていて、ソレを使って .exe や .scr にリソースを埋め込むことになる。以下は例。
brc32 main.rc scrnsvr.scr

ちなみに、bcc32 じゃなくて brc32 であることに注意。brc32 がエラーを出すという質問に対して、bcc32 の設定方法を回答してる事例を一体何度見たことか…。違うんや…。bcc32 じゃなくて brc32 の話や…。

さておき。brc32.exe にヘッダーファイルの場所(〜\Include)を教えてやらないと、「windows.h が開けない」とエラーで出てしまう。
Error main.rc 1 11: Cannot open file: windows.h

解決策としては、brc32 に -i"(bcc5.5インストールフォルダ)\Include" といった感じでヘッダーファイルの場所を伝えてやるか、環境変数 INCLUDE にヘッダーファイルの場所を設定してから brc32 を呼び出す。
set INCLUDE=(bcc5.5インストールフォルダ)\Include

ただ、bcc だけを使ってビルドすることが前提なら、*.rc の中に #include <windows.h> を含めなくても良いという話も見かけた。他のコンパイラを使う場合は、#include <windows.h> が必要になるのだとか。

2022/08/27() [n年前の日記]

#1 [prog][windows] MinGWでWindowsアプリがビルドできそうか実験中

Windows10 x64 21H2 + MinGW (gcc 9.2.0) で、Windowsアプリがビルドできそうか実験中。

以下の解説ページを眺めながら、1つ1つサンプルをビルドできるかチェックしてるところ。

_WIN32 SDK Programming

ちなみに、gcc でコンパイルする際は以下。
gcc hoge.c -o hoge.exe -mwindows
-mwindows をつけることで、Windowsアプリであることを指定してるらしい。これをつけないとリンクエラーになる場面もあった。

2022/08/28() [n年前の日記]

#1 [prog][windows] MinGWでWindowsアプリがビルドできそうか実験中その2

Windows10 x64 21H2 + MinGW (gcc 9.2.0) で、Windowsアプリがビルドできそうか実験中。

昨日と同様に、以下の解説ページを眺めながら、1つ1つサンプルをビルドできるかチェックしてるところ。

_WIN32 SDK Programming

gcc でコンパイルして *.o を作成するのは以下。
gcc -c hoge.c
hoge.o が生成される。

リソースをコンパイルする時は以下。
windres hoge_res.rc hoge_res.o
これで、リソースファイル hoge_res.rc がコンパイルされて、hoge_res.o が生成される。

メインプログラムの *.o と、リソースファイル *.o をリンクして、実行形式を作成する。
gcc hoge.o hoge_res.o -o hoge.exe -mwindows
-mwindows をつけることで、Windowsアプリであることを指定する。

2022/08/29(月) [n年前の日記]

#1 [pc] USBハブが壊れたかもしれない

メインPCに接続している、手持ちのUSB2.0ハブが壊れたっぽい。
裏面には、BUFFALO BSH4A02シリーズと書いてある。USB2.0。4ポート。バスパワー/セルフパワー両対応。個別スイッチ付き。色が白いので、型番は BUFFALO BSH4A02WH だろうと思われる。

裏面の4つのゴム足を剥がすとプラスネジが見えた。ネジを外したら開けることができた。

基板を眺めてみたけれど、これといっておかしなところはないような…。ハンダクラックとやらがあるのではと注視してみたけれど、それらしいものは見当たらない。ただ、小さな電解コンデンサがいくつかついているので、容量抜け?とやらになってる可能性はあるかもしれない。

蓋を閉めて、もう一度PCに接続してみた。USB接続のテンキーを繋いでみたら反応した。単に接触不良だったのか…。

と思ったが、ワイヤレスマウスの受信機を差したら、それは動かなかった。以前は動いていたのに。電流不足だろうか。だとすると、やはりコンデンサ関係…?

何にせよ、不調なUSBハブは怖くて使えないなと。HDDを繋いでファイルコピーや移動に失敗したら目も当てられない。

代替製品が市場から無くなっていた。 :

代替製品をググって探してみたけれど、個別スイッチ付きのUSB2.0ハブという製品ジャンルは、もう市場から無くなっていた。いや、まだELECOMから1製品だけ出ているけれど。昔はBUFFALOやサンワサプライも販売していたのだけどな…。

ヨドバシのサイトで物色したら、ケーブルが50cm以上のUSBハブもほとんど見当たらないことに気づいた。どれも10cmや15cmの短いものばかり。ノートPCに接続することが前提の製品群だろうか。

ひょっとして、USB3.0が主流になっていて、USB2.0ハブが疎まれてるのだろうか…。

考えてみたら、個別スイッチ付きは便利だけど、スイッチを切り替えるたびに、多少はUSBハブに衝撃が加わるはずなので…。場合によってはハンダが剥がれてしまったりするかもしれないと思えてきた。スイッチ付きじゃない製品を使ったほうがいいのだろうか。どうなんだろう。

2022/08/30(火) [n年前の日記]

#1 [prog][windows] Geany 1.38 x64をインストールした

Windows10 x64 21H2上で、Geany 1.38 x64 をインストールした。今までインストールしてたのは Geany 1.36 x86。

_Home | Geany
_Geany - Wikipedia

Geany は、Linux や Windows で利用できるプログラミング向けのエディタ。GTKを利用して作られている。

Geany は、ずっと 32bit版(x86)が提供されていたけれど、1.38 は 64bit版(x64) を提供するように変わったようだなと…。

ところで、Geany Windows版は、1.30 以降、英数字を打ち込むと何故か半角カタカナになる(文字化けする)というバグがあって、そのバグに遭遇しないバージョンを探すのが少々面倒だった。ただ、少なくとも、Geany 1.38 x64 は英数字を打っても半角カタカナにならないように見えた。これなら使えそう。

ちなみに、以前動作確認をした際は、Geany 1.31、1.33、1.37.1 がバグってた。Geany 1.29以前や 1.36 では問題が起きなかった。

_mieki256's diary - Windows10の復旧作業中
_mieki256's diary - Geany 1.36 をインストール
_mieki256's diary - Lua用のエディタとしてGeanyを導入してみたり

何かしらを入力するウィジェット上で、打った文字が半角カタカナになるバグは、GTK 32bit版の特定バージョンで発生するバグだった。これは想像だけど、Geany が 64bit版になって、GTK 64bit版を使うようになったことで、そのバグに遭遇しなくて済むようになったのかもしれない。というのも、GTK を使っている Inkscape や GIMP でも32bt版を使うと同種のバグに遭遇したけれど、64bit版を使うと問題が起きなかったりしたので…。

セットアップファイルその他をメモ。 :

入手して実行したセットアップファイルは以下。
  • geany-1.38_setup.exe
  • geany-plugins-1.38_setup.exe。

同梱されている GTK は、GTK 3.24.30 と表示されていた。

geany138_install_ss01.png

geany138_install_ss02.png

余談。エディタの公式サイトのデザインについて。 :

思考メモ。

Wikipedia日本語版の Geany のページを眺めたら、「Windows上のエディタ、NoteTabやConTEXTと似たような機能を提供している。」という一文が目に入った。なんやそのエディタ。聞いたことないな。

ググってみたら、ちょっと妙な印象を受けた。片方は有償エディタじゃないか…。そして、どちらも、公式サイトのデザインがめっちゃ怪しい…。いかにも商売感丸出しのデザイン…。

フツーこの手のエディタの公式サイトって、もうちょっと古いセンスというか、良く言えば質実剛健、悪く言えばプログラマーらしく色気ゼロなデザインでまとめてあるものでは…。「草原でお姉さんが『サイコー!』と叫んでますよー」「美人なお姉さんやイケメンお兄さんが爽やかな笑顔でオフィスで仕事してますよー」的写真をエディタの公式サイトに載せたりしないだろう…。いやはやコレは胡散臭い。(<偏見)

ちなみに、Wikipedia英語版のGeanyのページには、それらのエディタについての記述はなかった。ますます怪しい。

これ、もしかして、中国企業あたりが宣伝目的でこういう一文をこっそりあちこちに追加してるのではないか…? Wikipediaって結構汚染されてる…? などと一瞬疑ってしまったのだけど。編集履歴を確認したら、2009/07/14に初めてページが作られた時点で、それらのエディタについても最初から記述があった。つい最近追加された記述じゃなかったのね…。開発してるのもスイスの会社っぽいし。単に自分がそれらのエディタについて知らなかっただけか…。関連ページをググったら対応OSが Windows Me と書いてあるページも見かけたので、かなり昔から存在していたエディタだった模様。

それはさておき。どうして自分はあの手の写真を目にすると「怪しい」「胡散臭い」と感じてしまうのだろう…。「だったらどんな写真なら安心するのか」と問われたら…。エディタのスクリーンショットがバーンと載ってると安心するかなあ…。欲を言えば、GIFアニメか動画で、補完がバンバン効いてたり、置換処理がサクサクできる様子を見せられたら、「おっ。これは便利そう」とうっかり興味を持ってしまうかもしれない。そもそも、いかにもモデルさんっぽいお兄さんお姉さん達の写真を見せられたところで、その○○の何が優れているのかさっぱり分からない…。いやまあ、単に売り込みたい層が違うだけだろうけど…。

思考メモです。オチはないです。

#2 [lua][prog] Luaでタートルグラフィック

Geany を触っているうちに、昔、こういった見た目のエディタ上で、Lua だか Python だかを使ってグラフィックっぽいものを描画できるか試していた記憶が蘇ってきた。アレは一体何を使って作業していたのだったか…。何だったっけ…。

色々なエディタ/IDEを起動して確認しているうちに思い出した。Geany じゃなくて、Lua用の軽量IDE、ZeroBrane Studio 1.90 だった。

_ZeroBrane Studio - Lua IDE/editor/debugger for Windows, Mac OSX, and Linux

Python じゃなくて、Lua を使ってタートルグラフィックスを試してたのだな…。

_Drawing trees with turtles - ZeroBrane

日記を検索してもそのあたりメモしてなかったようなので、今回一応メモしとく。

サンプルの動かし方。 :

(ZeroBrane Studioインストールフォルダ)\myprograms\welcome.lua を開くと、各種サンプルを試すことができる。

welcome.lua の中には、Lua言語のコメント行として、Markdown で各種説明や各サンプルへのリンクが書かれている。この Markdown は ZeroBrane Studio が解析して表示してくれるようで、例えば、リンクはリンクとしてちゃんと機能するようになってる。素晴らしい。

welcome.lua の中の、「Demos」の「Turtle graphics demo」をマウスクリックすると、myprograms\turtle-samples\demo.lua が開かれて、Luaを用いたタートルグラフィックを試すことができる。

実行は F6 キー。もしくはツールバー上の「>>」(Execute the current project/file)をクリック。

一応、動作してる様子をキャプチャしてみた。解像度が低いからアレだけど、雰囲気ぐらいは伝わるかと…。




myprograms\turtle-samples\ の中に、タートルグラフィックのサンプルが入っているので、それぞれ開いて実行してみるのもいいかもしれない。

余談。Pythonでタートルグラフィックス。 :

ZeroBrane Studio + Lua とは別の話だけど。Python を使えばタートルグラフィックスを試すこともできる。

_turtle グラフィックスに挑戦してみよう | Python学習講座
_亀に訊け:Pythonの亀グラフィックス
_お絵かきで Python を学ぶ(タートルグラフィックス) - Qiita
_python学習の入り口に。turtle(たーとる)でCGを描こう。

「python turtle」でググれば解説ページがたくさん出てくるので参考になるかと。

2022/08/31(水) [n年前の日記]

#1 [prog][windows] MinGWでWindowsアプリがビルドできそうか実験中その3

Windows10 x64 21H2 + MinGW (gcc 9.2.0) で、Windowsアプリがビルドできそうか実験中。

以下のサイトで紹介されているサンプルを、MinGW (gcc) でビルドできるか試していた。

_図形の描画 | WINAPI入門〜bituse〜
_ペンとブラシの作成 | WINAPI入門〜bituse〜
_ビットマップ画像の表示 | WINAPI入門〜bituse〜
_WINAPI入門 〜bituse〜

Windows APIとやらを使って四角を描いたりするサンプルなら、gcc でもビルドできることは確認できていたのだけど。bitmapファイル(.bmp)をリソースファイルに含めた場合にビルドできるかどうかが不安だったわけで。ただ、試してみたらスンナリとビルドできたし、ウインドウ上で画像が描画された。

ちなみに、gcc を使ってコンパイルする際の指定は以下。

リソースファイルを含まない場合。hoge.c から hoge.exe が生成される。
gcc hoge.c -o hoge.exe -mwindows

リソースファイルを含む場合。hoge.c から hoge.o を、hoge_res.rc から hoge_res.o を生成して、.o 群をリンクして hoge.exe を生成する。
gcc -c hoge.c
windres hoge_res.rc hoge_res.o
gcc hoge.o hoge_res.o -o hoge.exe -mwindows

念のために書いておくけど、hoge とか fuga とか piyo とか foo とか bar ってのは、メタ構文変数というヤツで…。

_知識の枝 hoge foo barとは何か
_メタ構文変数(hoge / foo)とは - 意味をわかりやすく - IT用語辞典 e-Words
_メタ構文変数 - Wikipedia

#2 [pc] USB2.0ハブ UH-2314NWを購入

ヤマダ電機須賀川店でUSB2.0ハブを購入してきた。製品は、ナカバヤシ株式会社 Digio2 UH-2314NW (UH-2314Nシリーズ)。USB2.0、4port、バスパワー。ケーブル長120cm。W22 x D66 x H19mm。パッケージ裏面には、各ポート最大消費電流100mAまで、と書いてある。税込929円。保証期間6ヶ月。

今まで使ってた USB2.0ハブ、BUFFALO BSH4A02WH が不調なので、代替品として買ってきた。ちなみに、BSH4A02WH は、今まで使えていたワイヤレスマウス受信機が使えなくなる不具合が起きていた。今回購入した UH-2314NW に、その受信機を繋いだところ、スンナリ動作してくれた。

以上、31 日分です。

過去ログ表示

Prev - 2022/08 - Next
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project