GNU Smalltalk GTK のダイアログが固まる?

で、気になっていたの「不具合(?)」というのは、GTK のダイアログがうまく出せないということでした。たとえばこんなコード。

#!/usr/bin/env gst

PackageLoader fileInPackage: 'GTK'.

Object subclass: MotherShip [
    | window vbox startBtn |

    delete: aWiget event: aGdkEvent [
        GTK.Gtk mainQuit.
        ^false
    ]

    openMyHeart [
        | dialog |
        dialog := GTK.GtkMessageDialog
            new: window
            flags: GTK.Gtk gtkDialogModal
            type: GTK.Gtk gtkMessageInfo
            buttons: GTK.Gtk gtkButtonsClose
            message: 'Precure Open My Heart!'.
        dialog setTitle: 'Open My Heart!'.
        dialog run; destroy
    ]

    show [
        window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel.
        window setTitle: 'MotherShip'.
        window connectSignal: 'delete_event'
            to: self selector: #delete:event: userData: nil.

        vbox := GTK.GtkVBox new: false spacing: 0.
        window add: vbox.

        startBtn := GTK.GtkButton newWithLabel: 'Show Message!'.
        startBtn connectSignal: 'clicked'
            to: self selector: #openMyHeart userData: nil.
        vbox packStart: startBtn expand: true fill: true padding: 0.

        startBtn show.
        vbox show.
        window show.
    ]
]

Eval [
    mainwindow := MotherShip new.
    mainwindow show.
    GTK.Gtk main
]

openMyHeart で GtkMessageDialog を作って表示させているのですが、これが実行されると母艦もダイアログも固まってしまいます。ダイアログ内に何も描画されない状態になって、どちらのウインドウも反応しなくなるのだけれど、これはセマフォを握り合ってデッドロックしてしまっているような動き。

なので、邪道(本来の、メインルーチンをダイアログの返答まで待たせる・・という意味を台無しにする)ですが、

    openMyHeart [
        | dialog |
        dialog := GTK.GtkMessageDialog
            new: window
            flags: GTK.Gtk gtkDialogModal
            type: GTK.Gtk gtkMessageInfo
            buttons: GTK.Gtk gtkButtonsClose
            message: 'Precure Open My Heart!'.
        dialog setTitle: 'Open My Heart!'.
        [dialog run; destroy] fork
    ]

と、dialog run をフォークして浮かせてあげればダイアログの表示は出きる。だけれども、こんどは ダイアログの [閉じる]ボタンを押すとエラーが出ちゃう

Object: nil error: Invalid argument nil: must be a Boolean
SystemExceptions.MustBeBoolean(Exception)>>signal (ExcHandling.st:254)
SystemExceptions.MustBeBoolean class(SystemExceptions.WrongClass class)>>signalOn:mustBe: (SysExcept.st:776)
SystemExceptions.MustBeBoolean class>>signalOn: (SysExcept.st:852)
UndefinedObject(Object)>>mustBeBoolean (Object.st:1399)
GTK.GtkMessageDialog(GTK.GtkDialog)>>run (GTK.star#VFS.ZipFile/GtkImpl.st:627)
[] in MotherShip>>openMyHeart (msg.st:22)
[] in Process>>onBlock:at:suspend: (Process.st:392)
BlockClosure>>on:do: (BlkClosure.st:193)
[] in Process>>onBlock:at:suspend: (Process.st:393)
BlockClosure>>ensure: (BlkClosure.st:269)
[] in Process>>onBlock:at:suspend: (Process.st:370)
[] in BlockClosure>>asContext: (BlkClosure.st:179)
BlockContext class>>fromClosure:parent: (BlkContext.st:68)

んー、、面倒くさそうなので調べるのパス!(ぉ

run は GNU Smalltalk GTK では珍しく GTK+ の同名関数を cCall してるだけのラッパーじゃなくって、Smalltalk 側でいろいろやってる (具体的には Semaphore を使って親処理に wait させる根回しをゴニョゴニョしてる) ので、そこが原因なのかなぁ。

とりあえずの逃げとしては、GTK そのままの show と、'response' シグナルでの ダイアログ破棄 をコネクトする

    openMyHeart [
        | dialog |
        dialog := GTK.GtkMessageDialog
            new: window
            flags: GTK.Gtk gtkDialogModal
            type: GTK.Gtk gtkMessageInfo
            buttons: GTK.Gtk gtkButtonsClose
            message: 'Precure Open My Heart!'.
        dialog setTitle: 'Open My Heart!'.
"       dialog run; destroy"
        dialog connectSignal: 'response'
               to:[:dialog :integer | dialog destroy] 
               selector: #value:value:.
        dialog show.
    ]

な感じのコードで(多分) OK なので(続きにコードがあったら走っちゃうけど..)、目をつむってしまうのだけど、なんでダメなのかは気になるので、時間のある時にしらべてみようっと。

追記(やっぱり気になってちょっとだけ調べちゃいました)

ダイアログ表示途中で固まるのは、#openMyHeart はボタンの'clicked'シグナルにコネクトされているけれど、これが返らなくなると(例えば Semaphore で待たせるとか)するとダメということみたい。

ダイアログの[閉じる]ボタンがうまく処理されないのは、GTK.GtkDialog >> #run の コード(↓)

run [
    <category: 'services'>

    | signals sema answer modal destroyed |
    sema := Semaphore new.
    modal := self getModal.
    self setModal: true.
    signals := {
        self
            connectSignal: 'response'
            to: [ :dialog :integer | answer := integer. sema signal ]
            selector: #value:value:.

        self
            connectSignal: 'unmap'
            to: sema
            selector: #signal.

        self
            connectSignal: 'delete_event'
            to: [ answer := Gtk gtkResponseDeleteEvent. sema signal. true ]
            selector: #value.

        self
            connectSignal: 'destroy'
            to: [ destroyed := true ]
            selector: #value }.

    self show.
    sema wait.
    destroyed ifFalse: [
        self setModal: modal.
        signals do: [ :each | self disconnectSignal: each ] ].
    ^answer
]

で、destroyed が初期化されてないから。 nil に ifFalse: が送信されちゃうってオチでした(バグじゃん)。

でも、これを直してあげると、今度はGLib のほうで

(gst:26757): GLib-GObject-WARNING **: /build/buildd/glib2.0-2.24.1/gobject/gsignal.c:2390: instance `0x8b99010' has no handler with id `4294967292'

が出ちゃうのですよね。追加した singal群 が消されちゃうからかな(disconnectSignal する部分をごっそり削ると出ない)。

というわけで、destroyed の未初期化の件を除けば、GTK 側と絡む部分がうまく行っていない感じ。うーーむーー。こりは元々のバクなのかしら?それともわたしのGSTのビルドがへっぽこだからなのかしら?