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

macOS Monterey JXA

macOS Monterey の JXA は NSRect のバグが無くなった。
ということで、今の知識で NSWindow を作ってみたらどうなるか。

いや PyObjC のほうが簡単なんだけど、デフォルトで入っていないのが。
JXA ならどんな Mac でもそのまま動かせるという利点があるので捨て難い。

ただ JXA では PyObjC みたいに class にできないんだよなぁ。
ネットで簡単に見つかる方法では全部グローバル変数にするしかないのが。
そもそも JavaScript の class は他の言語の class とは違うし。
なので GNOME の Gjs もアクロバットな手段で class っぽくしている。

ObjC.registerSubclass を上手く利用してソレっぽくやってみよう。
追加メソッドはどう書けばいいのかな?とか。

#!/usr/bin/osascript

ObjC.import('Cocoa');

//let wins = [];

ObjC.registerSubclass({
    name: 'AppDelegate',
    protocols: ['NSApplicationDelegate'],
    methods: {
        'applicationDidFinishLaunching:': function (notification) {
            let window = $.MyWindow.alloc.initWithContentRectStyleMaskBackingDefer(
                $.NSMakeRect(0, 0, 300, 100),
                $.NSTitledWindowMask
                | $.NSClosableWindowMask
                | $.NSMiniaturizableWindowMask
                | $.NSResizableWindowMask,
                $.NSBackingStoreBuffered,
                false
            );
            window.makeKeyAndOrderFront(window);
            $.NSApp.activateIgnoringOtherApps(true);
            //wins.push(window);
        }
    }
});

ObjC.registerSubclass({
    name: 'WinDelegate',
    protocols: ['NSWindowDelegate'],
    methods: {
        'windowWillClose:': function(notification) {
            console.log('MyApp Close !');
            return $.NSApp.terminate(0);
        }
    }
});

ObjC.registerSubclass({
    name: 'MyWindow',
    superclass: 'NSWindow',
    propertyies: {},
    methods: {
        'initWithContentRect:styleMask:backing:defer:': function
        (contentRect, style, backingStoreType, flag) {
            let _this = ObjC.super(this).initWithContentRectStyleMaskBackingDefer(
               contentRect, style, backingStoreType, flag);
           _this.title = $('JXA NSWindow');
           _this.delegate = $.WinDelegate.new;
           // Button
           let button = $.NSButton.buttonWithTitleTargetAction('button', this, 'onButtonClick:');
           button.setFrame($.NSMakeRect(10, 10, 200, 36));
           _this.contentView.addSubview(button);
           // return
            return _this;
        },
        'onButtonClick:': {
            types: ['void', ['id']],
            implementation: function(sender) {
                sender.setTitle('Clicked!');
            }
        }
    }
});

/**
 * Application
 */
$.NSApplication.sharedApplication;
$.NSApp.setActivationPolicy($.NSApplicationActivationPolicyRegular);
$.NSApp.mainMenu = function() {
    let mainMenu = $.NSMenu.new;
    let itemApp  = $.NSMenuItem.new;
    mainMenu.addItem(itemApp);
    let menuApp  = $.NSMenu.new;
    itemApp.setSubmenu(menuApp);
    // quit menu
    let itemQuit = $.NSMenuItem.new;
    itemQuit.initWithTitleActionKeyEquivalent('Quit App', 'terminate:', 'q');
    menuApp.addItem(itemQuit);
    return mainMenu;
}();
$.NSApp.setDelegate($.AppDelegate.new);
$.NSApp.run;

nswindow

こんな感じになった、
ボタンのハンドラは delegate ではなく this に届くようだ。
オーバーライドは types 指定不要なのね、ふむふむ。
これだけ解れば応用でなんとかなりそう。

ただ PyObjC と違って NSWindow がガベージコレクションされないんだが。
let 指定ならばハンドラを抜けたら破棄されるはずなんだけど、何故だ?
Python とは破棄対象の選定方法が違うんだろう、知らんけど。

ところで Monterey で今頃気がついたんだけど。

power

iOS みたいに充電の保留機能が付いたんだね。
筆者は滅多に持ち歩かないんで意味はないかもだがけど。

GTK4 main loop

GTK4 は PyGObject ばかりやっていたけど。
たまには Gjs もやろうかなって、gir なのは同じなんだけど。

gjs/gtk4-template.js at master ? GNOME/gjs ? GitHub

あれ?
GtkApplication を使わないでイケる方法があったんだ。
同様なはずなので PyGObject で書いてみる。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib

class TestWindow(Gtk.Window):
    def __init__(self):
        Gtk.ApplicationWindow.__init__(self)
        btn = Gtk.Button(label='The best camera for wild birds')
        btn.connect('clicked', self.on_button_clicked)
        self.label = Gtk.Label(label='...')
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.append(btn)
        box.append(self.label)
        self.set_child(box)
        #self.connect('delete-event', self.on_close_request) # v3
        self.connect('close-request', self.on_close_request)
        self.present()

    def on_button_clicked(self, widget):
        self.label.set_text('MICRO FOUR THIRDS !')

    def on_close_request(self, widget):
        loop.quit()

loop = GLib.MainLoop.new(None, False)
#GLib.set_prgname('LUMIX')
TestWindow()
loop.run()

mft

GMainLoop でも良かったようです、知らなかった。

でも何故かこの方法ではタイトルバーに何も表示されないね。
アプリ名表示は使っていないはずの GtkApplication になる、なんだかな。
ウインドウを作る前に g_set_prgname すればいいんだけどさ。

GtkApplication を使えば今までどおりファイル名が表示される。
いや application-id を指定するとそちらが優先されるみたい。
g_set_prgname 無指定の場合だけの話ですけど。

close-request は delete-event の名前が変わっただけです。
×ボタンを押したよってシグナル、destroy は破棄されたよってシグナル。
GtkApplication を使う場合はこんな処理いりませんからね。

しかしのんびりしている間に GNOME 41 が出てしまった。

GNOME 41 Release Notes

Multitasking って GSettings で変更できていたジャン。
GUI 設定が付いただけ、いや筆者はデフォルトで使うけど。
それよりランチャを左に戻す設定が欲しい、macOS でも左にしているんだ。
ちなみに普段は隱して三本指スワイプでランチャを出す GNOME3 式で使っている。

The GTK Project

GTK の stable は 4.2.1 か、こっちは今までどおり奇数が開発版なのか。
でもドキュメントは 4.5 かよ、バージョンの関連性がもうよくワカラン。

関係ないけどサンプルコードでカメラネタはやっぱりイマイチだ。
やっぱりスズキのバイクが一番だなって。

GTK4 Clipboard (String)

GTK4 で Clipboard を使うテスト。
文字列のコピペはできた、ただし自分のインスタンス内のみで。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
#gi.require_version('Gdk', '4.0') # not need
from gi.repository import Gtk, Gdk

class TestWindow(Gtk.ApplicationWindow):
    '''
        GTK4 Clipboard @ String Copy
        This Code is Instance Only
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.clip = self.props.display.get_primary_clipboard()
        print(self.clip) #=> GdkWaylandPrimary (GdkClipboard)
        '''
        self.clip = self.get_clipboard()
        self.clip = self.props.display.get_clipboard()

        display = Gdk.Display.get_default()
        self.clip = display.get_primary_clipboard()
        self.clip = display.get_clipboard()

        # local is All True...
        print(self.clip.props.local)
        '''
        self.clip.set('スズキのバイクはカッコイイ')
        print(self.clip.get_formats().to_string()) #=> { gchararray, text/plain;charset=utf-8, text/plain }
        #
        #self.clip.set_text('No GIR Binding') # Error!
        #
        self.clip.read_text_async(None, self.read_text_async_cb)
        #
        self.label = Gtk.Label(label='Motor Cycle')
        self.set_child(self.label)
        self.set_default_size(400, 100)
        self.present()

    def read_text_async_cb(self, clip, res):
        text = clip.read_text_finish(res)
        self.label.set_label(text)

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.suzuki.katana')

    def do_activate(self):
        TestWindow(self)

app = TestApplication()
app.run()

clip

何をどうやっても local Property が True になってしまう。
False にしないと Gedit 等の外部インスタンスとはコピペできない。
バグなのか?
他にやらないといけないことがあるのか?

それと set_text 等がバインドされていないのはどうなんだ?
set で文字列を渡したら普通にセットできたけど「ん?」って感じ。
バイナリの転送は GdkContentProvider でいいのかな?

GtkGestureDrag

GtkGesture ラスト、GtkGestureDrag を。
マウスでドラッグすると文字列が着いてくるサンプルコード。

#!/usr/bin/env python3
 
import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        GtkGestureDrag Sample Code
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.offset_x = 50
        self.offset_y = 50
        self.draw_x = 0
        self.draw_y = 0
        # gesture drag
        drag = Gtk.GestureDrag()
        drag.connect('drag-begin', self.on_gesture_drag_begin)
        drag.connect('drag-update', self.on_gesture_drag_update)
        drag.connect('drag-end', self.on_gesture_drag_end)
        self.add_controller(drag)
        # view
        self.view = Gtk.DrawingArea()
        self.view.set_draw_func(self.view_draw_func)
        self.set_child(self.view)
        # resize
        self.set_default_size(400, 300)

    def view_draw_func(self, da, cr, width, height):
        cr.move_to(self.draw_x + self.offset_x, self.draw_y + self.offset_y)
        cr.set_font_size(36)
        cr.show_text('Drag and Drop')

    def on_gesture_drag_begin(self, ges, offset_x, offset_y):
        pass

    def on_gesture_drag_end(self, ges, offset_x, offset_y):
        self.offset_x += offset_x
        self.offset_y += offset_y

    def on_gesture_drag_update(self, ges, offset_x, offset_y):
        self.draw_x = offset_x
        self.draw_y = offset_y
        self.view.queue_draw()

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)

drag

それと残りに GtkGestureStylus なんてのがあるけど。
どう考えてもスタイラスペン用途だよなって。
持っていないし GNOME てか Linux での使い道も思いつかない。
クリスタが Linux 対応するはずがないし、適材適所だよ。
なので無視でいいかなって。

ところでコレはファイルマネージャからのドロップではない。
GTK4 は drag_dest_add_uri_targets が使えないね。

Drag & dropping files with GTK4 – Platform – GNOME Discourse

こんなのを見つけたけど同じ所でクラッシュする。
うーん、今日もゲンナリするくらい進まない。

GtkGestureSwipe on Macbook

GTK4 ページを作ろうとしていますが、進んでいない。
てゆーか GtkGestureSwipe が Macbook で動作しません。

1

そもそも Eye of GNOME にて上記が動作がしません。
ちなみに X11 でログインすると拡大や回転もできなくなります。

2

Macbook Air 2011 でも Wayland がデフォルトになります。
この状態でスワイプ以外は全部動く、やはり X11 ではズームできません。

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        Fedora 34 on Macbook Air 2011
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.mem_pan = -1
        # touch || mouse button
        click = Gtk.GestureClick()
        click.connect('released', self.on_gesture_click_released)
        # long press
        lpress = Gtk.GestureLongPress()
        lpress.connect('pressed', self.on_gesture_long_press_pressed)
        # pan
        pan = Gtk.GesturePan(orientation=Gtk.Orientation.VERTICAL)
        pan.connect('pan', self.on_gesture_pan)
        # swipe (no...)
        swipe = Gtk.GestureSwipe(touch_only=True)
        swipe.connect('swipe', self.on_gesture_swipe_swipe)
        # zoom
        zoom = Gtk.GestureZoom()
        zoom.connect('scale-changed', self.on_gesture_scale_change)
        #
        self.label = Gtk.Label(label='start')
        self.label.add_controller(click)
        self.label.add_controller(lpress)
        self.label.add_controller(pan)
        self.label.add_controller(swipe)
        self.label.add_controller(zoom)
        # resize
        self.set_child(self.label)
        self.set_default_size(600, 600)

    def on_gesture_pan(self, ges, direction, offset):
        if direction != self.mem_pan:
            self.mem_pan = direction
            if direction == Gtk.PanDirection.UP:
                self.label.props.label += '\npan up'
            else:
                self.label.props.label += '\npan down'

    def on_gesture_long_press_pressed(self, ges, x, y):
        self.label.props.label += '\nlong press'

    def on_gesture_click_released(self, ges, n_press, x, y):
        if n_press == 2:
            self.label.props.label += '\ndouble clicked'

    def on_gesture_swipe_swipe(self, ges, x, y):
        self.label.props.label += f'\nswipe x={round(x)} y={round(y)}'

    def on_gesture_scale_change(self, zoom, scale):
        w = round(600 * scale)
        h = round(600 * scale)
        self.set_default_size(w, h)

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)

3

ちなみに Macbook に Fedora 34 を入れた場合。
以下のトラックパッドジェスチャがデフォルト状態の GNOME 40 で可能。

三本指の上スワイプでアクティビティ画面。
もう一度同じ動作でアプリケーションランチャ。
三本指の横スワイプで仮想デスクトップの異動。
Eye of GNOME にて画像の拡大縮小と回転。

探せばジェスチャを追加できるサードパーティ拡張もあるんだけど。
上記のデフォルトとコンフリクトすると思う、試していないけど。
X11 でログインするとやはり何も動作しません。

考えてみれば二本指ジェスチャってスクロール動作と同じだし。
スワイプジェスチャと両立している macOS ってスゲェんだなって。
サーフェスとかは触ったことすら無いので知らない。

しかし本当に思う、やっぱり Macbook は macOS で使うのが一番だよ。
Fedora で使うと command と control キーの違いをどうしても間違えるし。