投稿者「sasakima-nao」のアーカイブ

PyGObject do_key_press_event

PyGObject でのオーバーライドでハマったので覚書。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk

class NoActionWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # connect
        #self.connect("key_press_event", self.on_key_press_event)
        self.show_all()

    def do_key_press_event(self, event):
    #def on_key_press_event(self, widget, event):
        """
            Call the method of the parent
            'gtk_application_set_accels_for_action' does not work.
        """
        # Important!
        Gtk.ApplicationWindow.do_key_press_event(self, event)
        #
        if event.keyval == Gdk.KEY_Escape:
            self.close()
        return False

class NoActionApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        # ApplicationMenu
        menu = Gio.Menu()
        menu.append("_Quit", "app.quit_action")
        self.set_app_menu(menu)
        self.set_accels_for_action("app.quit_action", ["<Control>Q"])
        # Action
        quit_action = Gio.SimpleAction(name="quit_action")
        quit_action.connect("activate", self.on_quit_action)
        self.add_action(quit_action)
        #
        self.window = NoActionWindow(self)

    def on_quit_action(self, action, parameter):
        self.quit()

app = NoActionApplication()
app.run()

上記 Important 部を無効にすると Ctrl+Q で終了できない。
このオーバーライドは親の do_key_press_event を呼ぶ必要がある。
connect メソッドならその必要は無いようだ。

こんなしょーもないことを解決するのに三時間くらい使っちまったぜ。
GtkApplication 側に問題があると思いこんでしまうもの、こういう場合。
この連休でアレを完成させるつもりだったけど無理かも。

United RPMS

Fedora に新しいリポジトリができたんだね。
数はまだ少ないけど。

United RPMS by UnitedRPMs

RPM Fusion から何故か無くなった unrar がこっちにはある。
Atom や Opera やよくワカランのやら、ふむふむ。
mplayer 等のマルチメディア系は RPM Fusion と被りまくっているような。

unitedrpms.github.io/README.md at master ? UnitedRPMs/unitedrpms.github.io ? GitHub

インストールは上記、GPG キーのインストールを忘れずに。
gnome-softwere のソフトウエアソースを確認。

unitedrpms1

うん登録されているね。
これで Atom とかはインストールできるけど被っているものはどうなるの?

gaburi

普通にどちらか一方が入るということのようだ。
これなら導入しても特に問題はなさそうだ。

今後どうなるか解らないけど面白いものが登録されるといいな。
まあ筆者の場合は使うアプリがほぼ固定しちゃってるんで意味ないんですけどね。

Fedora 24 rar, cbr

CBZ ビューアを作っているけど CBR はどうしよう?
筆者自信は cbr ファイルを一つも持っていないし。
まあそりゃ rar 自体が超マイナーだし。

てゆーか Fedora 24 にしてから RPM Fusion で unrar が見つからない。
と思ったけど手持ちにあった rar ファイルを file-roller で開くことができる。
これって自前対応になったってこと?
いや圧縮はできないし、今度ソースでも落として調べてみよう。

自前になったとしても MComix 等は unrar を使うんだよなぁ。
筆者はいらないけど rar で圧縮したい人もいるだろう。

WinRAR archiver, a powerful tool to process RAR and ZIP files

なんだ、RARLAB で普通に Linux x64 バイナリが落とせるじゃん。
せっかくなので現在最新の 5.40 beta3 を落として

sudo make install

rar

あれ、Makefile は先頭を大文字にしなくても使えるのね。
まあいい、バイナリだからこれだけでインストール完了。
早速 Nautilus のコンテキストメニューから[圧縮]をば。

rar_cbr

おお増えた、と思ったら cbr, cbz まで何故か増えた!
これで ZIP 圧縮した後で拡張子を変更する手間が省ける、少し嬉しい。

Python3 yield

前回のコードはダメダメだった、ZIP 展開が遅すぎる!
やはり Python の zipfile は使い物にならないのだろうか。

いやまて、ZIP の展開は非同期にすればよくね?
g_idle_add を使って一枚目以降はバックグラウンド読み込みにすればイケるかも。

そういえば yield ってどう使うんだっけ。
clipoli では for 文で使っていたけど今回の場合はそれじゃダメだし。

class Clipoli(Gtk.Menu):
    def __init__(self, confpath):
        # etc...

    def read_lines(self, filename):
        """
            ini file Read Function
        """
        f = Gio.file_new_for_path(filename)
        fstream = f.read(None)
        dstream = Gio.DataInputStream.new(fstream)
        while 1:
            line, length = dstream.read_line_utf8(None)
            if line == None: break
            if line == "": continue
            if line[0] == ";": continue
            if len(line) > 2 and line[0] =="[" and line[-1] == "]":
                section = line[1:-1]
            elif section == "":
                pass # Nothing
            elif "=" in line:
                pos = line.index("=")
                yield section, line[:pos], line[pos+1:]
        fstream.close(None)

    def load_inifile(self):
        """
            Inifile Read and Create Menu
        """
        for section, key, value in self.read_lines(self.confpath):
            # etc...

Pythonのイテレータとジェネレータ – Qiita

ってジェネレーターを作って next() を呼んだら例外だ。
って上記は Python2 だし、Python3 では変わっているのかな?

Pythonのジェネレータ、コルーチン、ネイティブコルーチン、そしてasync/await | プログラミング | POSTD

next はメソッドではなく関数になっているのね。
それさえ解ればなんとかなりそうだ。

#!/usr/bin/env python3

import sys, zipfile, gi
gi.require_version('Gtk', '3.0')
gi.require_version('Clutter', '1.0')
gi.require_version('GtkClutter', '1.0')
from gi.repository import Gtk, Gio, GLib, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl

PATH = "gf(kari).cbz";

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.num = 0
        self.datas = []
        self.is_fullscreen = False
        # Dark Theme
        settings = Gtk.Settings.get_default()
        settings.props.gtk_application_prefer_dark_theme = True
        # DnD
        self.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
            [Gtk.TargetEntry.new("text/uri-list", 0, 0)],
            Gdk.DragAction.MOVE
        )
        # Clutter
        embed = GtkClutter.Embed()
        self.add(embed)
        self.stage = embed.get_stage()
        #
        self.actor1 = Clutter.Actor()
        self.stage.add_child(self.actor1)
        self.image1 = Clutter.Image()
        self.actor1.set_content(self.image1)
        # signal
        self.stage.connect("allocation-changed", self.on_stage_allocation_changed)
        #
        self.show_all()

    def set_uri(self, uri):
        if uri == None:
            return
        # Check CBZ
        self.zip = Gio.file_new_for_uri(uri)
        info = self.zip.query_info("standard::content-type", Gio.FileQueryInfoFlags.NONE)
        if info.get_content_type() == "application/x-cbz":
            self.gen = self.iter_zip()
            GLib.idle_add(self.iter_idle)

    def iter_idle(self):
        try:
            p = next(self.gen)
            #print(p) # check
            self.datas.append(p)
            if len(self.datas) == 1:
                self.set_pixbuf(0)
            return True
        except Exception as e:
            return False

    def iter_zip(self):
        # unzip
        with zipfile.ZipFile(self.zip.get_path()) as o:
            l = o.namelist()
            l.sort()
            self.datas.clear()
            for name in l:
                try:
                    data = o.read(name)
                    stream = Gio.MemoryInputStream.new_from_data(data)
                    p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                    yield p
                except Exception as e:
                    pass

    def set_pixbuf(self, num):
        pixbuf = self.datas[num]
        self.image1.set_data(
            pixbuf.get_pixels(),
            Cogl.PixelFormat.RGB_888,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride()
        )
        self.num = num

    def change_pixbuf(self, bool_next):
        """
            TODO: Spread display
        """
        if bool_next:
            if len(self.datas) > self.num + 1:
                self.set_pixbuf(self.num + 1)
        else:
            if self.num > 0:
                self.set_pixbuf(self.num - 1)

    def on_stage_allocation_changed(self, actor, box, flags):
        """
            TODO: Aspect ratio
        """
        self.actor1.set_size(box.x2 , box.y2)

    def do_key_press_event(self, event):
        """
            eog like Key Bind
        """
        if event.keyval == Gdk.KEY_Down:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_Right:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_space:
            self.change_pixbuf(True)

        elif event.keyval == Gdk.KEY_Up:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_Left:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_BackSpace:
            self.change_pixbuf(False)

        elif event.keyval == Gdk.KEY_F11:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.fullscreen()
                self.is_fullscreen = True
        elif event.keyval == Gdk.KEY_Escape:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.close()

    def do_drag_data_received(self, drag_context, x, y, data, info, time):
        drop = data.get_uris()[0]
        self.set_uri(drop)
        self.present()

class ComipoliApp(Gtk.Application):
    def __init__(self):
        GLib.set_prgname("Comipoli");
        Gtk.Application.__init__(
            self,
            application_id="apps.sasakima.comipoli",
            flags=Gio.ApplicationFlags.FLAGS_NONE )

    def do_activate(self):
        ComipoliWindow(self)

GtkClutter.init();
app = ComipoliApp()
app.run(sys.argv)

GLib.idle_add でムリムリに非同期にして。
yield を利用して self.datas 配列になるべく早く GdkPixbuf を詰め込む。
next() は yield が終ると例外なのを利用して idle 監視を止める。

とやっています、物凄く解り辛いと思うけど。
しかし class 内で使うとガベージコレクション回避で self だらけに。

試す、よし実用で問題ない程度には使えるようになった。
実際は絶望的に遅いんですけどね。
バックグラウンド処理が追いつかない速度でマンガを読む人はいない、かも。

yield ってこんなに便利だったのか、何を今更だが。

PyGObject CBZ Viewer

MComix のデザインや操作性が古すぎて GNOME3 に合わない。
Python2 は諦めるけど PyGtk はもう入れたくない。

キーバインドが eog と同じように使える cbz コミックビューアが欲しい。
特に [→] キーで改ページがやりたい、筆者が eog で一番使うキーである。

Evince でも見開き表示にはできるが左ページから始まるようにしか設定できない。
次ページが [→] キーなのはこのアプリも同じ。

etc…

だったら自分で作ればいいじゃないか!

今から作るなら当然 ClutterImage を使って OpenGL 表示だよね。
昔ながらのノウハウが使えないという意味でもあるけど。

#!/usr/bin/env python3

import sys, zipfile, gi
gi.require_version('Gtk', '3.0')
gi.require_version('Clutter', '1.0')
gi.require_version('GtkClutter', '1.0')
from gi.repository import Gtk, Gio, GLib, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl

PATH = "gf(kari).cbz";

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.num = 0
        self.datas = []
        self.is_fullscreen = False
        # Clutter
        embed = GtkClutter.Embed()
        self.add(embed)
        self.stage = embed.get_stage()
        #
        self.actor1 = Clutter.Actor()
        self.stage.add_child(self.actor1)
        self.image1 = Clutter.Image()
        self.actor1.set_content(self.image1)
        # signal
        self.stage.connect("allocation-changed", self.on_stage_allocation_changed)
        # unzip
        with zipfile.ZipFile(PATH) as f:
            l = f.namelist()
            l.sort()
            for name in l:
                try:
                    data = f.read(name)
                    stream = Gio.MemoryInputStream.new_from_data(data)
                    p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                    self.datas.append(p)
                except Exception as e:
                    pass
        # set
        self.set_pixbuf(0)
        #
        self.show_all()

    def set_pixbuf(self, num):
        pixbuf = self.datas[num]
        self.image1.set_data(
            pixbuf.get_pixels(),
            Cogl.PixelFormat.RGB_888,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride()
        )
        self.num = num

    def change_pixbuf(self, bool_next):
        """
            TODO: Spread display
        """
        if bool_next:
            if len(self.datas) > self.num + 1:
                self.set_pixbuf(self.num + 1)
        else:
            if self.num > 0:
                self.set_pixbuf(self.num - 1)

    def on_stage_allocation_changed(self, actor, box, flags):
        """
            TODO: Aspect ratio
        """
        self.actor1.set_size(box.x2 , box.y2)

    def do_key_press_event(self, event):
        """
            eog like Key Bind
        """
        if event.keyval == Gdk.KEY_Down:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_Right:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_space:
            self.change_pixbuf(True)

        elif event.keyval == Gdk.KEY_Up:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_Left:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_BackSpace:
            self.change_pixbuf(False)

        elif event.keyval == Gdk.KEY_F11:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.fullscreen()
                self.is_fullscreen = True
        elif event.keyval == Gdk.KEY_Escape:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.close()

class ComipoliApp(Gtk.Application):
    def __init__(self):
        GLib.set_prgname("Comipoli");
        Gtk.Application.__init__(
            self,
            application_id="apps.sasakima.comipoli",
            flags=Gio.ApplicationFlags.FLAGS_NONE )

    def do_activate(self):
        ComipoliWindow(self)

#Clutter.init(); @ Error
GtkClutter.init();
app = ComipoliApp()
app.run(sys.argv)

comipoli000

とりあえず読み込みと表示関連はなんとかなった。
後はアスペクト比の保持と見開き表示等々。

実は、キーバインドが eog と同じだと少々問題が。

GANMA! むさむらだけ読んでいる
となりのヤングジャンプ えびなちゃんだけ(同
ガンガンONLINE ぐるぐる(同

と筆者が利用しているマンガサイトはことごとく改ページが [←] キーなのだ。
縦書き文化の国で生まれたマンガなのだからそのほうが自然といえる。

横書きの英語文化で作られたアプリとキーバインドを共通にするべきかどうか。
もう完全に好みの問題、左右キーのみ設定で入れ替えできるようにするのが一番かなと。
タッチパネルでもどちらにフリックかで問題になりそう。