Paepoi » GTK4(Python) Tips » GTK4(Python) Tips | GtkApplication
GTK4(Python) Tips | GtkApplication
# 最終更新日 2022.04.24
UIKit, AppKit や WPF を知っているならお馴染の方法。
GtkApplication は管理するオブジェクトが無くなった時点で終了する参照カウンタ方式。
基本的に GLib::GApplication が元ですから GTK3 の時とほとんど変わっていません。
GtkWidget がデフォルトで表示になった等の違いはありますのでコードは少し書き換えました。
PyGObject にて GTK4 でウインドウを作る最小限のコードを下記に。
win は一見ガベージコレクションの対象になるように見えるかもしれません。
application プロパティの指定により GtkApplication に保持されるのでコレでいいです。
C で作るのに対し何が省かれているかを見れば PyGObject の凄さが解ると思います。
二つ目のアプリケーションを起動しようとすると以前作成した Application に引数が転送されます。
そして転送先 Application 側で activate や open シグナルが発行されます。
これにより複数ウインドウの管理や多重起動の制御を GtkApplication に任せられるようになっています。
startup シグナルは初回にしか発行されませんので注意。
たとえば下記のようにすれば多重起動しないアプリケーションに。
startup シグナルが一番最初に発行されます。
G_APPLICATION_HANDLES_OPEN を GApplication 作成時の flags に指定すると使えます。
ハンドラの引数に GFile のリストで渡ってくるのでそれを処理します。
GFile なので HDD 内だけでなく gvfs を使った URI 経由のファイルも扱うことができます。
sys.argv を使えばいいじゃん、と思うかもしれませんが。
二つ目からの転送をされても転送先の sys.argv は最初のままなのでこういう処理が必要になります。
flags の指定は不要、add_main_option で指定しハンドラを書けば動作します。
このシグナルは二つ目のアプリケーションを起動しようとした場合は転送前に発生します。
-v でバージョンの表示等に使います、-h でヘルプも自動追加される。
G_APPLICATION_HANDLES_COMMAND_LINE を GApplication 作成時の flags に指定すると使えます。
このシグナルは二つ目のアプリケーションを起動しようとした場合は転送先 GApplication で発生します。
注意点はこの指定を行うと activate や open シグナルが発生しない。
Gio.ApplicationFlags
なのでファイル引数処理は自前で、GTK3 から変わっていません。
しかしアプリケーションメニューで使っていた GAction はまだ使えます。
Ctrl+Q で全体終了、Ctrl+N で新規ウインドウ等は Application 側に実装が自然かと。
GtkApplication
GtkApplication はアプリケーションのウインドウ等を一括で管理します。UIKit, AppKit や WPF を知っているならお馴染の方法。
GtkApplication は管理するオブジェクトが無くなった時点で終了する参照カウンタ方式。
基本的に GLib::GApplication が元ですから GTK3 の時とほとんど変わっていません。
GtkWidget がデフォルトで表示になった等の違いはありますのでコードは少し書き換えました。
PyGObject にて GTK4 でウインドウを作る最小限のコードを下記に。
win は一見ガベージコレクションの対象になるように見えるかもしれません。
application プロパティの指定により GtkApplication に保持されるのでコレでいいです。
C で作るのに対し何が省かれているかを見れば PyGObject の凄さが解ると思います。
#!/usr/bin/env python3 import gi, sys gi.require_version('Gtk', '4.0') from gi.repository import Gtk def activate_cb(app): win = Gtk.ApplicationWindow(application=app, title='Hello') win.present() app = Gtk.Application() app.connect('activate', activate_cb) app.run(sys.argv)
application-id
GtkApplication は指定された ID の Application を一つしか作成しません。二つ目のアプリケーションを起動しようとすると以前作成した Application に引数が転送されます。
そして転送先 Application 側で activate や open シグナルが発行されます。
これにより複数ウインドウの管理や多重起動の制御を GtkApplication に任せられるようになっています。
startup シグナルは初回にしか発行されませんので注意。
たとえば下記のようにすれば多重起動しないアプリケーションに。
startup シグナルが一番最初に発行されます。
#!/usr/bin/env python3 import gi, sys gi.require_version('Gtk', '4.0') from gi.repository import Gtk, Gio class App(Gtk.Application): ''' 絶対に多重起動できないアプリケーション もう一つ起動しようとすると初回起動分に転送される ''' def __init__(self): ''' ココで指定した ID の Application は一つしか作れない 他人が絶対に使っていないと思う ID にしよう ''' Gtk.Application.__init__( self, application_id='apps.test.myid', flags=Gio.ApplicationFlags.FLAGS_NONE ) def do_startup(self): ''' 初回起動のみ Gtk.Application.do_startup(self) 呼び出しが必須 ''' Gtk.Application.do_startup(self) win = Gtk.ApplicationWindow(application=self, title='') win.present() def do_activate(self): ''' アクティブなウインドウはプロパティから得られる 呼び出される毎にタイトルバーの文字列が増える ''' self.props.active_window.props.title += '1' app = App() app.run(sys.argv)
open
open はファイル引数を処理するためのシグナルです。G_APPLICATION_HANDLES_OPEN を GApplication 作成時の flags に指定すると使えます。
ハンドラの引数に GFile のリストで渡ってくるのでそれを処理します。
GFile なので HDD 内だけでなく gvfs を使った URI 経由のファイルも扱うことができます。
sys.argv を使えばいいじゃん、と思うかもしれませんが。
二つ目からの転送をされても転送先の sys.argv は最初のままなのでこういう処理が必要になります。
#!/usr/bin/env python3 import gi, sys gi.require_version('Gtk', '4.0') from gi.repository import Gtk, Gio class Win(Gtk.ApplicationWindow): ''' HANDLES_OPEN の実験用 何か引数にファイルを指定して追加起動すると URI が追記される 引数無しで追加起動するとクリアされる ''' def __init__(self, app): Gtk.ApplicationWindow.__init__(self, application=app) self.uri_label = Gtk.Label() self.set_child(self.uri_label) class App(Gtk.Application): def __init__(self): ''' HANDLES_OPEN の場合 open シグナルを発行 ''' Gtk.Application.__init__(self, application_id='org.gnome.test_id', flags=Gio.ApplicationFlags.HANDLES_OPEN ) def do_startup(self): ''' 最初のウインドウはココで作るのが鉄板のようです ''' Gtk.Application.do_startup(self) Win(self) def do_activate(self): ''' 実行時に引数指定無しだとココに来る このシグナルを受け取った時に背面にいた場合 GNOME の通知が入る ''' self.props.active_window.uri_label.props.label = 'クリアしました' self.props.active_window.present() def do_open(self, files, n_file, hint): ''' 実行時に引数を指定するとココに来る 下記のようにすると転送され open シグナルが発行 $ ./test.py a.txt & $ ./test.py b.txt c.txt & $ # クリアする $ ./test.py & ''' s = '' for f in files: s = f'\n{f.get_uri()}' self.props.active_window.uri_label.props.label += s self.props.active_window.present() app = App() app.run(sys.argv)
handle-local-options
handle-local-options は -v 等のオプションを処理するためのシグナルです。flags の指定は不要、add_main_option で指定しハンドラを書けば動作します。
このシグナルは二つ目のアプリケーションを起動しようとした場合は転送前に発生します。
-v でバージョンの表示等に使います、-h でヘルプも自動追加される。
#!/usr/bin/env python3 import sys, gi gi.require_version('Gtk', '4.0') from gi.repository import Gtk, GLib APP_VERSION = 0.0.1 class App(Gtk.Application): def __init__(self): Gtk.Application.__init__(self) # add --version, -v option self.add_main_option( 'version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'Show OM Version', None) def do_handle_local_options(self, options): ''' ゼロを戻すとそのまま終了、-1 なら起動します ''' if options.lookup_value('version', GLib.VariantType.new('b')): print(f'OM {APP_VERSION}') return 0 return -1 def do_startup(self): Gtk.Application.do_startup(self) Gtk.ApplicationWindow(application=self, title='OM') def do_activate(self): self.props.active_window.present() app = App() app.run(sys.argv)
command-line
command-line は --new-window 等のオプションを処理するためのシグナルです。G_APPLICATION_HANDLES_COMMAND_LINE を GApplication 作成時の flags に指定すると使えます。
このシグナルは二つ目のアプリケーションを起動しようとした場合は転送先 GApplication で発生します。
注意点はこの指定を行うと activate や open シグナルが発生しない。
Gio.ApplicationFlags
なのでファイル引数処理は自前で、GTK3 から変わっていません。
#!/usr/bin/env python3 import sys, gi gi.require_version('Gtk', '4.0') from gi.repository import Gtk, GLib, Gio class Win(Gtk.ApplicationWindow): def __init__(self, app): Gtk.ApplicationWindow.__init__(self, application=app, title='Pen') class App(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, application_id='com.olympus.pen', flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) # --version, -v option self.add_main_option( 'version', b'v', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'Show Pen Version', None) # --new-window, -n option self.add_main_option( 'new-window', b'n', GLib.OptionFlags.NONE, GLib.OptionArg.NONE, 'New Pen Window', None) def do_command_line(self, command_line): # --new-window を転送先で処理 options = command_line.get_options_dict() if options.lookup_value('new-window', GLib.VariantType.new('b')): w = Win(self) # ファイル引数を自前で処理 arg = command_line.get_arguments()[1:] for s in arg: # Option ignored if not s.startswith('-'): # URI? (file:///...) if '//' in s: f = Gio.file_new_for_uri(s) else: f = Gio.file_new_for_path(s) if f.query_exists(): self.props.active_window.set_title(s) break self.props.active_window.present() return 0 def do_handle_local_options(self, options): # --version は転送前に処理 if options.lookup_value('version', GLib.VariantType.new('b')): print('Chikubi 0.0.1') return 0 return -1 def do_startup(self): Gtk.Application.do_startup(self) Win(self) ''' 使えるようになるかもしれないので def do_activate(self): self.props.active_window.present() def do_open(self, files, n_file, hint): for f in files: self.props.active_window.set_title(f.get_uri()) break self.props.active_window.present() ''' app = App() app.run(sys.argv)
GAction
GNOME 3.32 でアプリケーションメニューは廃止されました。しかしアプリケーションメニューで使っていた GAction はまだ使えます。
Ctrl+Q で全体終了、Ctrl+N で新規ウインドウ等は Application 側に実装が自然かと。
#!/usr/bin/env python3 import sys, gi gi.require_version('Gtk', '4.0') from gi.repository import Gtk, Gio class Win(Gtk.ApplicationWindow): def __init__(self, app): Gtk.ApplicationWindow.__init__(self, application=app) self.present() class App(Gtk.Application): def __init__(self): Gtk.Application.__init__( self, application_id='suzuki.bike.kakkoii', flags=Gio.ApplicationFlags.FLAGS_NONE ) def do_activate(self): # 何もしなくてもこのハンドラは必要 pass def do_startup(self): Gtk.Application.do_startup(self) # GAction 作成 new_window_action = Gio.SimpleAction(name='new_window_action') quit_action = Gio.SimpleAction(name='quit_action') # 追加 self.add_action(new_window_action) self.add_action(quit_action) # アクセラレーターの指定 self.set_accels_for_action('app.new_window_action', ['<Control>N']) self.set_accels_for_action('app.quit_action', ['<Control>Q']) # シグナル new_window_action.connect('activate', self.on_new_window_action) quit_action.connect('activate', self.on_quit_action) # Window を作る Win(self) def on_new_window_action(self, action, parameter): Win(self) def on_quit_action(self, action, parameter): self.quit() app = App() app.run(sys.argv)
Copyright(C) sasakima-nao All rights reserved 2002 --- 2023.