今頃気が付いた。
PyObjC の AppKit に objc は含まれていた。
#!/usr/bin/env python3 #import objc from AppKit import *
いらなかったんや!!!
PyObjC Tips ページを作っていて気がつく、書いていないのに動いたって。
作っているうちに又気がついたことがあったらコッチに書くわ、ネタがないし。
しかしゼロから作るのはどう書くか迷って中々進まない。
plistlib — Mac OS X .plist ファイルの生成と解析 ? Python 3.7.2 ドキュメント
こんなモジュールが Python3 のデフォルトであったのか!
Info.plist の編集はどうやろうか迷ったけどコイツでいこう。
#!/usr/bin/env python3 import plistlib PATH = 'Comipoli.app/Contents/Info.plist' doctype = dict( CFBundleDocumentTypes = [ dict( CFBundleTypeExtensions = ['cbz'], CFBundleTypeRole = 'Viewer', LSTypeIsPackage = False, NSPersistentStoreTypeKey = 'Binary' ), dict( CFBundleTypeExtensions = ['cbr'], CFBundleTypeRole = 'Viewer', LSTypeIsPackage = False, NSPersistentStoreTypeKey = 'Binary' ), dict( CFBundleTypeExtensions = ['cb7'], CFBundleTypeRole = 'Viewer', LSTypeIsPackage = False, NSPersistentStoreTypeKey = 'Binary' ), dict( CFBundleTypeExtensions = ['pdf'], CFBundleTypeRole = 'Viewer', LSTypeIsPackage = False, NSPersistentStoreTypeKey = 'Binary' ) ] ) plist = None with open(PATH, 'rb') as fp: plist = plistlib.load(fp) plist.update(doctype) with open(PATH, 'wb') as fp: plistlib.dump(plist, fp)
make_plist.py
他のアプリの Info.plist を参考にこれだけ追加してみた。
アイコンを変更することもできるけどまあいいや。
ということで、ビルドスクリプトの最後でコレを実行。
/Applications に移動して pdf で二本指タップしてみる。
Comipoli.app が見事に登録されています。
選択すれば開くことができるしデフォルトアプリにすることもできる。
ところで、0.0.2 は app 化すると引数起動ができなかった。
(旧) Cocoaの日々: アプリ起動時に渡される引数の処理
App にすると application:openFiles: でしか引数を受け付けしないのね。
Window を作るのを applicationWillFinishLaunching: に変更。
application:openFiles: のほうが良さげなのでこっちで処理。
よし日本語でも問題なく引数付き起動できるようになった。
しかし PyObjC だけでここまで作れるとは自分でも思わなかった。
macOS でも Python3 は使うべき。
WebKit バインドは PyObjC に含まれている。
Fedora の gir には WebKit2 が含まれている。
もしかしたら共通コードでイケるんでね?
#!/usr/bin/env python3 import objc from AppKit import * from WebKit import * class WView(WKWebView): def init(self): config = WKWebViewConfiguration.new() objc.super(WView, self).initWithFrame_configuration_(NSZeroRect, config) return self class Win(NSWindow): def init(self): frame = NSMakeRect(100, 400, 900, 600) objc.super(Win, self).initWithContentRect_styleMask_backing_defer_( frame, NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask, NSBackingStoreBuffered, False) self.setTitle_('Web') self.setDelegate_(self) # view self.webview = WView.new() self.webview.setFrameSize_(self.contentView().frame().size) self.contentView().addSubview_(self.webview) # url url = NSURL.URLWithString_('https://www.google.com/') req = NSURLRequest.requestWithURL_(url) self.webview.loadRequest_(req) return self def windowDidResize_(self, sender): self.webview.setFrameSize_(self.contentView().frame().size) class Menu(NSMenu): def init(self): objc.super(Menu, self).init() item_app = NSMenuItem.new() self.addItem_(item_app) menu_app = NSMenu.new() item_app.setSubmenu_(menu_app) item_quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q') menu_app.addItem_(item_quit) return self NSApplication.sharedApplication() window = Win.new() window.makeKeyAndOrderFront_(window) NSApp.setMainMenu_(Menu.new()) NSApp.activateIgnoringOtherApps_(True) NSApp.run()
PyObjC
#!/usr/bin/env python3 import sys, gi gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, GLib, WebKit2 class WebView(WebKit2.WebView): def __init__(self, ctx): WebKit2.WebView.__init__(self, web_context=ctx) class Win(Gtk.ApplicationWindow): def __init__(self, app): Gtk.ApplicationWindow.__init__(self, application=app) # ctx = WebKit2.WebContext() self.webkit = WebView(ctx) self.add(self.webkit) self.webkit.load_uri('https://www.google.com/') # self.resize(900, 600) self.show_all() class App(Gtk.Application): __gtype_name__ = 'App' def __init__(self): GLib.set_prgname('App') 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().run(sys.argv)
PyGObject
全然違ったwwwww
Apple さん、なんで URL は文字列じゃダメなの?
GNOME さん、WebKit2.WebView() ではエラーになるんですけど?
実は我がアプリに本棚機能を付けようかと考えて。
WebKit からローカル HTML でやれば GNOME, macOS 共通にできるかなって。
こりゃ無理だ。
python ファイルの合体スクリプトはわざわざ目印を用意する必要無いと気が付いた。
class から始まっている行から最後までを読み込みすればいい。
一部書き換えが必要だったけど下記に落ち着いた。
#!/usr/bin/env python3 import os HEAD = '''#!/usr/bin/env python3 import objc, os, shutil, zipfile, re, subprocess from AppKit import * from Quartz.CoreGraphics import * PICEXT = '\.(jpe?g|png|gif)$' ''' FOOT = 'main()\n' lines = [HEAD] begin = False for filename in [s for s in os.listdir() if s.startswith('comipoli_')]: with open(filename) as f: lines.append('# {}\n'.format(filename)) for l in f.read().split('\n'): if begin: lines.append(l) elif l.startswith('class'): begin = True lines.append(l) begin = False lines.append(FOOT) with open('Comipoli.py', 'w') as f: f.write('\n'.join(lines))
こうなった。
後は pip3 で py2app を導入、py2applet はセットで付いてくる。
pip3 install -U py2app
ビルドスクリプトの作成。
前回のコードで icns を作る。
上記コードでソースファイルを一つに合体。
後は py2applet にこう渡すだけでアイコン付きの app を作ってくれる。
最後に片付け。
#!/bin/sh # icns python3 make_icns.py iconutil -c icns comipoli.iconset # combine python3 make_combine.py # build py2applet --no-strip Comipoli.py comipoli.icns # clean rm -r comipoli.iconset rm comipoli.icns rm Comipoli.py
ただ
–no-strip オプションを付けているのは
Xcode インストール無しだとこんなダイアログが出るから。
気にせず [今はしない] を選んでも app は作れるけどね。
unrar はフルパス指定にしたらあっさり動いた。
そんなこんなでとっととサイト公開することに。
macOS アプリケーション – L’Isola di Niente
何か忘れている気がするけどまあいいや。
JXA では手抜きをしたけど今回はキチンと ICON を作ろう。
こんなに沢山の画像を作るなんて面倒だよ。
これも PyObjC で一つの画像からリサイズでやってしまおう。
画像のリサイズ保存方法は検索で簡単に見つかる。
しかし、Retina Display の mac では勝手に 144dpi になってしまう!
逆にそれを利用して 144dpi を作る、72dpi を自力で作る手段を探す。
なるほど、丸パクさせていただきます。
PyObjC では下記のように。
API Notes: Quartz frameworks ? PyObjC ? the Python ? Objective-C bridge
ということで。
#!/usr/bin/env python3 # make_icns.py # This Program is Retina Display Mac Only import os from AppKit import * from Quartz.CoreGraphics import * # Preference PNGFILE = 'icon.png' ICONSET = 'comipoli.iconset' src_image = NSImage.alloc().initWithContentsOfFile_(PNGFILE) os.mkdir(ICONSET) os.chdir(ICONSET) def create_png(img, name): bmp = NSBitmapImageRep.imageRepWithData_(img.TIFFRepresentation()) data = bmp.representationUsingType_properties_(NSBitmapImageFileTypePNG, {}) data.writeToFile_atomically_(name, True) for n in [512, 256, 128, 32, 16]: # 72dpi img = NSBitmapImageRep.imageRepWithData_(src_image.TIFFRepresentation()).CGImage() ctx = CGBitmapContextCreate(None, n, n, 8, 4 * n, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast) CGContextDrawImage(ctx, CGRectMake(0, 0, n, n), img) imgref = CGBitmapContextCreateImage(ctx) image72dpi = NSImage.alloc().initWithCGImage_size_(imgref, (n,n)) create_png(image72dpi, 'icon_{0}x{0}.png'.format(n)) # 144dpi image144dpi = NSImage.alloc().initWithSize_(NSMakeSize(n, n)) image144dpi.lockFocus() NSGraphicsContext.saveGraphicsState() NSGraphicsContext.currentContext().setImageInterpolation_(NSImageInterpolationHigh) src_image.drawInRect_(NSMakeRect(0, 0, n, n)) NSGraphicsContext.restoreGraphicsState() image144dpi.unlockFocus() create_png(image144dpi, 'icon_{0}x{0}@2x.png'.format(n))
で。
よし使える。
実はもう app 化は完成しているんだけど。
app にすると unrar にパスを通しても unrar を認識しない問題が出た。
そりゃ app は bashrc なんて読み込みしないよな、本来 macOS には無いし。