Programming」カテゴリーアーカイブ

get_size, resize

GtkHeaderBar 付きの GtkWindow のリサイズ。
以前こんなことを書いた。

GTK+ 3.20 GtkHeaderBar and CSS | PaePoi

もう少し細かく判った。

#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # Headerbar
        self.headerbar = Gtk.HeaderBar(show_close_button=True)
        self.set_titlebar(self.headerbar)
        # Button
        button = Gtk.Button(label="Click !!!")
        button.connect("clicked", self.on_button_clicked)
        self.add(button)

    def on_button_clicked(self, button):
        print(self.get_allocated_width(), self.get_allocated_height())
        w, h = self.get_size()
        print(w, h)

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

    def do_activate(self):
        w = Win(self)
        w.show_all()

app = App()
app.run()

aloc_size

get_allocated_* と get_size で値が違う。
ちなみに GtkHeaderbar を消すと同一の値になる。

見た目で解るけどクライアント領域のサイズですね。
window.get_size と window.resize が共通サイズになるようだ。
ということでサイズの保存にはコチラを使いましょう。

ついでに、GVariant.deep_unpack は Gjs でないと使えないのね。
Gjs も地味に便利にしているところがあったのね。

Gjs VariantBuilder

しまった、ポケモン GO にどっぷりハマってもーた。

hf_kari

むったんと五十鈴とくるみんと柊の 4M ゲット、ココミンも確定済。
苗ちゃんごめん、無課金ではランキング上位報酬なんて無理です。

ボケはこれくらいにして。

オジサンはポケモンなんてピカチュウとサトシしか知らないけどやっているよ。
と言ったら驚く人がたまにいる、こっちが驚くってばさ。
こんな人達はポケモン自体の人気でフィーバーしていると思っているようだ。
つまり、まだまだ伸びる可能性があるという恐ろしさ。

すでに個人がアプリを作ったって存在に気付いてくれる人はゼロに近い。
16 年前はゆりあるなんてしょーもないモンが万単位だったのにな。
プログラミングは仕事か個人で使うスクリプトだけの時代がもう来ている。

とにかくポケモン GO (と GF) でサボっていたぶんを取り返さなきゃ。

今回はずっと後回しにしてきた GSettings に配列の配列を保存する方法を調べる。
もちろん Gjs、まあ Python でもほとんど同じだけど。

GSettings への保存は基本的に Variant 型で保存する。
配列にするには VariantBuilder というものを使うようだ。
配列の配列だけどタプルの配列として保存するのが効率よさげ。

this.set_size = [[320, 240], [640, 360], [1280, 720]];

//
// Write
//
let settings = new Gio.Settings({schema:"org.sasakima.y901x"});
let xy = new GLib.Variant("(ii)", this.get_size());
settings.set_value("window-size", xy);
// Error
//let builder = new GLib.VariantBuilder({type: new GLib.VariantType("a(ii)")});
//let builder = new GLib.VariantBuilder(GLib.VariantType("a(ii)"));
let builder = GLib.VariantBuilder.new(new GLib.VariantType("a(ii)"));
for (let i=0; i<3; i++) {
    let t = new GLib.Variant("(ii)", [ this.set_size[i][0], this.set_size[i][1] ]);
    builder.add_value(t);
}
settings.set_value("set-size", builder.end());


//
// Read
//
let settings = new Gio.Settings({schema: "org.sasakima.y901x"});
let [w, h] = settings.get_value("window-size").deep_unpack();
this.resize(w, h);
this.set_size = settings.get_value("set-size").deep_unpack();

これでなんとかなった。

dconf

GLib.VariantBuilder
のように new キーワードを使うとエラー。
この構造体はプロパティが無いので JSON では対応できない、うーむ。

保存で詰まりまくったけど読み込みは簡単すぎ。
get_value して deep_unpack だけで配列の配列が完成、唖然とした。
調子に乗ってサイズ保存もタプルに変更したら前のキーが残ってしまった。
これどうやって消すのかな?今度調べる。

Google Chrome 51 Chach

Google Chrome はキャッシュの仕様が変わってしまったね。
FLV や MP4 の動画キャッシュにヘッダが着くようになった。

Nautilus をスクリプトで拡張 – L’Isola di Niente
つまり上記に書いた手段が使えない。
YouTube は随分前から駄目だけどニコニコなんかはまだキャッシュに残るし。

いやまあ強引にダウンロードする手段はいくらでもあるんだけど。
せっかくキャッシュにあるのにもう一回通信なんてアホ臭い。
GHex 等で地味に余計なヘッダを削るなんて面倒臭い。

ならば余計なヘッダを取っ払ってからコピーすればいいじゃない!
Nautilus スクリプトです、Linux じゃない人は参考程度に。

FLV が取得できなかったので小変更

#!/usr/bin/env python3

"""
    chrome_catch (~/.local/share/nautilus/scripts)
    Copy Chrome Cache File to Nautilus Current Directry
    Google Chrome 51 Version
"""

import os
from gi.repository import GLib

FLV_FILE = "FLV".encode("utf-8")
MP4_FILE = "ftyp".encode("utf-8")

# Src Directory is Google Chrome Cache Path
src_path = os.path.expanduser("~/.cache/google-chrome/Default/Cache")
# Copy Directory is Nautilus current directory
dst_path = GLib.filename_from_uri(GLib.getenv("NAUTILUS_SCRIPT_CURRENT_URI"))[0]

ls = os.listdir(src_path)
for f in ls:
    src = os.path.join(src_path, f)
    # size < 1MB is continue:
    if os.path.getsize(src) < 1048576:
        continue
    with open(src, "rb") as o:
        data = o.read()
        data2 = data
        try:
            i = data.find(MP4_FILE)
            o.seek(i-4)
            with open("{0}/{1}.mp4".format(dst_path, f), "wb") as ff:
                ff.write(o.read())
        except Exception as e:
            pass
        #data2 = o.read()
        try:
            i = data2.find(FLV_FILE)
            o.seek(i)
            with open("{0}/{1}.flv".format(dst_path, f), "wb") as ff:
                ff.write(o.read())
        except Exception as e:
            pass

find で見つからないと例外なのでコレでいいと思う。
早速ニコニコでバイク動画を再生して、よしよし拾うことができた。
いやバイク動画目的ですよ、決してえっち(以下略

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 側に問題があると思いこんでしまうもの、こういう場合。
この連休でアレを完成させるつもりだったけど無理かも。

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 ってこんなに便利だったのか、何を今更だが。