Yearly Archives: 2022

2022-08-15

companyからcorfuへ移行~自動と手動で補完候補を変える

corfucompanyよりは幾分素直で扱いやすい印象。

corfuの設定:

(2024-02-18追記: Corfuの自動補完で候補の存在を伝える事と候補を選べるようにする事を分離するで設定を書き直したので以下のコードは古い)

(setq corfu-cycle t) ;; 候補の最初と最後を行き来出来るようにする。
(setq corfu-auto t) ;; 自動的に補完候補を出す。
(setq corfu-preselect 'prompt) ;; 最初の候補を選択しない。誤入力が多すぎるので。

;; 無選択時のRETはquitだけでなく改行もする。
;; (2024-02-15修正:my-corfu-だと素早くC-M-iの後素早くRETを押したときに正しく補完されない。コマンド名がcorfu-で始まっているときだけupdateしている場所があるので)
(defun corfu-my-insert-or-newline ()
  (interactive)
  (if (>= corfu--index 0)
      (corfu--insert 'finished)
    (corfu-quit)
    ;; (2024-02-15修正:インタラクティブじゃないとインデントされなかったりする)
    (call-interactively 'newline)))
(with-eval-after-load "corfu"
  (define-key corfu-map (kbd "RET") 'corfu-my-insert-or-newline))

(global-corfu-mode)

;; lsp-modeでcorfuを使う。
;; (see: https://github.com/minad/corfu/wiki#example-configuration-with-flex)
(setq lsp-completion-provider :none)
(defun my-lsp-mode-setup-completion ()
  (setf (alist-get 'styles
                   (alist-get 'lsp-capf completion-category-defaults))
        '(flex)))
(add-hook 'lsp-completion-mode-hook #'my-lsp-mode-setup-completion)

;; corfuの候補リストにアイコンを表示する。
(setq kind-icon-default-face 'corfu-default)
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)

(corfu-auto=tで)自動的に補完候補を出しつつ自動的に最初の候補を選択する(corfu-preselectが'firstや'valid)というのは誤操作を引き起こす可能性がある。 何かを入力してRETやTABを押したら自動的に出てきた補完候補の方を意図せず選んでしまったというようなことが起こりえる。そのためにcorfu-preselectを'promptにして明示的に選ばなければ補完しないように設定した。

それでも何かを入力してC-n RETを押すようなシチュエーションでは下に移動して改行するつもりが最初の候補を選択して確定してしまうので、結局誤入力は完全には回避できなかった。なので自動的に出す時の候補をできるだけ少なくする(自動ではあまり候補を出さない)ようにしてみた。補完候補(バックエンド)は completion-at-point-functions (capf)という元々Emacsに備わっている仕組みに一本化されているので、それを調整する。

completion-at-point-functionsの設定:

;; 補完候補を出すときの文脈を特定

(defvar my-capf-context nil)

;; (2022-10-26修正: corfu--auto-complete => corfu--auto-complete-deferred)
(defun my-capf--corfu--auto-complete-deferred (old-fun &rest args)
  ;; corfu-autoの作用で補完候補を出すときに呼び出される。
  (let (;; 自動で補完候補を出す文脈だということを変数に記録する。
        (my-capf-context 'in-corfu--auto-complete-deferred)
        ;; 2024-02-14追記:
        ;; 自動で補完候補を出すときは必ずbasicスタイルのみを使うべし!
        ;; 先頭すら一致していない候補をバリバリ出されたら鬱陶しい!
        (completion-styles '(basic)))
    ;; 元の処理
    (apply old-fun args)))
(advice-add 'corfu--auto-complete-deferred :around #'my-capf--corfu--auto-complete-deferred)

;; 追加の補完関数

(defun my-capf-additional ()
  (pcase my-capf-context
    ('in-corfu--auto-complete-deferred
     ;; 自動補完の場合は確度の高い候補しか出さない。
     nil)
    (_
     ;; 手動補完の場合は積極的にいろんな候補を出す。
     (my-capf-manual))))
(add-hook 'completion-at-point-functions #'my-capf-additional 100)

;; 手動補完時の補完関数

(defvar my-capf-manual nil)
(defun my-capf-manual ()
  ;; capeパッケージの読み込みを遅延させる。
  (unless my-capf-manual
    (setq my-capf-manual
          ;; いろんな補完候補を合成する。
          (cape-super-capf
           #'cape-file #'cape-dabbrev #'cape-abbrev #'cape-line)))
  (funcall my-capf-manual))

自動的に補完候補を出す場合は元々モードに備わっているような補完候補しか出さないようにした。プログラミング言語用のモードの場合は文法に則した候補が出るのでそれほど邪魔にならないと思われる。

逆にC-M-iで手動で補完候補を出す場合はcapeパッケージの多種多様な補完候補を利用する。手動で出しているのだから多少確度の低い候補が出てきても構わない。

cape-super-capfで複数のバックエンドをマージしている。

capeは他にもcompany用バックエンドをcapfに変換するアダプタも持っている。手元にはorg-modeの「#+」行をより良く補完する自作のCompanyバックエンドがあるので、このアダプタでcorfu用に変換した。

今のところM-/はdabbrev-expandのままにしてある。

補完候補については不満なところがまだまだ沢山あるので逐一直していくつもりだ。completion-stylesも過剰な補完生成に一役買っているように見える。verticoにせよcorfuにせよ、どうも補完することばかりを優先して補完させたくないケースを軽視しているように見える。

(2024-02-15追記: Corfuの自動補完で候補の存在を伝える事と候補を選べるようにする事を分離するで設定を追加した)

2022-08-14

2022年夏の新番組

タイトル 開始 時刻 配信
スプリガン -     Netflix
BASTARD!!(バスタード)-暗黒の破壊神- -     Netflix
× 神クズ☆アイドル 07/02 12:00 dアニメ
彼女、お借りします 第2期 -      
iiiあいすくりん2 07/02 08:00 dアニメ
むさしの! 07/02 23:30 dアニメ
リコリス・リコイル 07/02 23:30 ABEMA
シュート!Goal to the Future 07/08 22:00 dアニメ
× てっぺんっ!!!!!!!!!!!!!!! 07/02 22:00 ABEMA
Engage Kiss 07/02 25:00 dアニメ
うたわれるもの 二人の白皇 -      
× 連盟空軍航空魔法音楽隊ルミナスウィッチーズ 07/03 23:00 dアニメ
RWBY 氷雪帝国 07/03 23:00 dアニメ
× 森のくまさん、冬眠中。 07/03 25:00 dアニメ
ユーレイデコ 07/03 23:30 dアニメ
ようこそ実力至上主義の教室へ 2nd Season -      
カードファイト!! ヴァンガード will+Dress (Season3) -      
転生賢者の異世界ライフ ~第二の職業を得て、世界最強になりました~ 07/04 24:00 ABEMA
邪神ちゃんドロップキックX(第3期) -      
オーバーロードⅣ(第4期) -      
× 東京ミュウミュウ にゅ~♡ 07/09 00:00 dアニメ
金装のヴェルメイユ 07/10 00:00 dアニメ
メイドインアビス -烈日の黄金郷-        
新テニスの王子様 U-17 WORLD CUP        
異世界迷宮でハーレムを 07/13 00:30 dアニメ
異世界おじさん 07/06 23:30 dアニメ
× 継母の連れ子が元カノだった -      
× 咲う アルスノトリア すんっ! 07/11 00:00 dアニメ
ちみも 07/09 00:00 dアニメ
組長娘と世話係 07/07 22:30 dアニメ
それでも歩は寄せてくる 07/07 02:28 Amazon
よふかしのうた 07/08 12:00 dアニメ
惑星のさみだれ 07/08 12:00 dアニメ
× プリマドール 07/09 01:00 dアニメ
シャドーハウス 2nd Season -      
× ブッチギレ! 07/09? 22:30 Amazon
メガトン級ムサシ シーズン1特別篇 -      
Extreme Hearts 07/10 02:00 dアニメ
Extreme Hearts SxSxS 07/10 02:00 dアニメ
黒の召喚士 07/09 22:00 dアニメ
Dr.STONE 龍水(TVスペシャル)        
ハナビちゃんは遅れがち 07/10 22:00 dアニメ
異世界薬局 07/10 23:00 dアニメ
× KJファイル 07/11 12:00 dアニメ
オリエント-淡路島激闘編- 07/15 00:00 dアニメ
× シャインポスト        
5億年ボタン~菅原そうたのショートショート~ 07/15 00:30 dアニメ
はたらく魔王さま!!(第2期)        
ラブライブ!スーパースター!! 第2期        
ダンジョンに出会いを求めるのは間違っているだろうかIV        
× 最近雇ったメイドが怪しい 07/26 02:30 dアニメ
BanG Dream! Morfonication        
うたの☆プリンスさまっ♪ マジLOVEスターリッシュツアーズ~旅の始まり~        
風都探偵(仮面ライダーW続編)       U-NEXT
夜は猫といっしょ 08/03   YouTube
賭ケグルイ双(ツイン) 08/04     Netflix
リラックマと遊園地(第2期)       Netflix
D4DJ Doubel Mix        
僕のヒーローアカデミア 【アニメ新作オリジナルエピソード×2話】        
ROLY POLY PEOPLES        
トニカクカワイイ ~制服~(新作エピソード)        
× 世界の終わりに柴犬と(※漫画動画)        
アイドルランドプリパラ        

ちみもの三姉妹の目の描き方が三人とも違うのが好き。

2022-05-04

Emacs Lispでline-prefixを使うと画像上のマウスイベントの座標がずれる件

再現コード:

(progn
  (require 'svg)
  (goto-char (point-min))
  ;; (insert "\n") ;;これがあると発生しづらい(スクロールして画像を一番上にもってくれば発生する)
  (let ((ov (make-overlay (point) (progn (insert " ") (point)) nil t)))
    (overlay-put ov 'evaporate t)
    (overlay-put ov 'pointer 'arrow)
    (overlay-put ov 'line-prefix "XXX") ;;これが無ければ発生しない(実際の状況ではこれが無くてもorg-indentがテキストプロパティのline-prefixを変更するといったことでこれと同じ事になる)
    (overlay-put ov 'display
                 (let ((svg (svg-create 400 300)))
                   (svg-rectangle svg 0 0 400 300 :fill "blue")
                   (svg-text svg "Click here"
                             :x 200 :y 150
                             :text-anchor "middle" :fill "white")
                   (svg-image svg)))
    (overlay-put ov 'keymap
                 (let ((km (make-sparse-keymap)))
                   (define-key km [mouse-1]
                     (lambda (ev)
                       (interactive "e")
                       (message "xy=%s" (posn-object-x-y (event-start ev)))))
                   km)))
  (insert "\n"))

上のコードをscratchバッファで実行すると画像が表示される。その画像をクリックするとミニバッファに座標が表示されるが、画像の左上からの座標になっていないことが分かる。原点は画像の左上では無く「XXX」の左上になっている。

画像の左端、line-prefix文字列の上端付近をクリックしたときの座標
図1: 画像の左端、line-prefix文字列の上端付近をクリックしたときの座標

この現象は画像がウィンドウの先頭にあり、その左にline-prefixによる文字列があるときのみ起こる。(line-prefixはテキストプロパティオーバーレイプロパティline-prefix変数のいずれで設定しても現象は起きる)

Emacs Lisp Reference Manualの22.7.4 Click Eventsには、マウスイベントのdx,dyはobjectの左上角を(0 . 0)とする相対座標と書かれている。今回の場合はobjectは画像('imageで始まるdisplayプロパティ)になるので、画像の左上からの座標になっていなければならない。しかし、画像がウィンドウの先頭にあって、かつ、line-prefixが使われている時に限り、line-prefix文字列の左上からの座標になってしまっている。画像がウィンドウの先頭に無いときや、line-prefixが使われていないときは正しく画像左上からの座標になっている。動作に一貫性が無く、文書化されている仕様と異なる結果が生じているのでEmacsの問題と言って良いのではないかと思う。

回避策としては、マウスの入力を必要とする時だけline-prefixを無効化するといったことが考えられる。

実際にel-easydrawにおいて作図エディタがウィンドウの一番上にあるときに座標がずれる問題が発生していた。org-indentを有効にすると発生する。org-indentはline-prefixやwrap-prefixを使用するので、まさに今回のケースに当てはまる。コミット701bfaaにて一時的にline-prefixを無効化することで回避した。

おそらくEmacsのバグだと思うが、この問題に気がつく人はどのくらいいるのだろうか。Emacs内に画像を表示してその中に対してマウス入力するなんてことを伊達や酔狂で無く本気でやっている人はどのくらいいるのだろうか。

2022-04-11

Windows上のEmacs 28.1でネイティブコンパイルする方法(まとめ)

(2024-02-19追記: 手っ取り早く使い方を知りたい人はMS-Windows版 Emacs 29.1への移行作業をご覧下さい)

前回の続き。

前提

  • MSYS2のMinGW64環境下でGCCが使える状況になっていること。
  • MSYS2にlibgccjitパッケージが入っていること。
  • 公式で配布しているWindowsバイナリ emacs-28.1.zip を使用すること。

MSYS2のセットアップについては割愛します。Emacsはlibgccjitを使用してネイティブコンパイルを行うため、それに関連したファイル群をMSYS2から入手する必要があります。おそらく base-devel, mingw-w64-x86_64-toolchain, mingw-w64-x86_64-libgccjit あたりのパッケージが入っていれば良いのだと思います。

ただし実際にEmacsを使用するときにはMSYS2全体は必ずしも必要なく、少数のファイルだけコピーして他のPCに移すことも出来ます。

ここでは公式配布の emacs-28.1.zip を前提に説明しますが、自分で --with-native-compilation を指定してビルドしたEmacsを他の環境へ移す場合にも同じ考え方が適用出来ると思います。ちなみに emacs-28.1.zip は --with-native-compilation を指定してビルドされていますが(C-h v system-configuration-optionsで確認できる)ネイティブコンパイルに必要なDLL等が含まれていない状態です。

方法1:Emacs起動前からmingw64/binへPATHを通す

Emacsが起動する前に、環境変数PATHにMSYS2内の mingw64/bin ディレクトリが含まれていればネイティブコンパイルは正常に動作します(もちろんlibgccjitパッケージがインストールされていること)。

Windowsの「システムの詳細設定」で環境変数を変更するか、次のようなbatファイルを経由するといった方法が考えられます。

set PATH=c:/hogehoge/msys/mingw64/bin;%PATH%
c:/hogehoge/emacs-28.1/bin/runemacs.exe

mingw64/bin/には libgccjit-0.dll, as.exe, ld.exe が含まれている必要があります。また、 mingw64/lib/以下には必要なライブラリが含まれている必要があります。

ここで大事なのは「Emacsの起動前から」という点です。

環境変数を変更するには early-init.el や init.el 内でsetenv関数を呼び出す方法もありますが、それだと起動後すぐのネイティブコンパイルには適用されません。一番最初のネイティブコンパイルは early-init.el よりも前に起動する場合があります。なので early-init.el で (setenv "PATH" ~) しても手遅れな場合があります。

この方法は環境変数PATHを常時変更するため、人によっては許容できない場合があります。例えばCygwinやその他GNUツールを含むプロダクトに既にPATHが通っていてEmacs使用中にそのコマンドを使いたい場合です。

方法2:必要なファイルをEmacsにコピーして必要なときだけPATHを通す

alpha.gnu.org has shiny new Emacs 28.0.91 Windows binaries : emacs のコメントによれば、ネイティブコンパイルに必要なファイルは次の17個だそうです。

  • binに入れるもの
    • libgccjit-0.dll
    • (追記: 2022-09-23)libisl-23.dll (←最新のlibgccjitが必要とするdll)
    • (追記: 2023-07-31)Emacs29.1が出たので入れ直しましたが、libmpc-3.dllとlibmpfr-6.dllも増えていました(MS-Windows版 Emacs 29.1へ移行)
  • lib/gccに入れるもの
    • crtbegin.o
    • crtend.o
    • dllcrt2.o
    • libadvapi32.a
    • libgcc.a
    • libgcc_s.a
    • libkernel32.a
    • libmingw32.a
    • libmingwex.a
    • libmoldname.a
    • libmsvcrt.a
    • libpthread.a
    • libshell32.a
    • libuser32.a
    • ld.exe
    • as.exe (ldとasはリンク先では libexec/emacs/28.0.91/x86_64-w64-mingw32/ とありますが、libexec/emacs/28.1/x86_64-w64-mingw32に置いても認識されず、色々試したところlib/gccに置いたら認識されました)

これらをemacs-28.1.zipを展開して出来たディレクトリの適切なディレクトリへコピーするとネイティブコンパイルが条件付きで動作するようになります。libgccjit-0.dll は bin (既にrunemacs.exe等があるディレクトリ)へ、それ以外は lib (既にemacs/やsystemd/がある)の下にgccというディレクトリを作成してその中へ入れてください。

これだけである程度の割合でネイティブコンパイルが成功するようになるのですが、自動で起動する非同期コンパイルがなぜか失敗する場合があります(カレントディレクトリがbinやlib等の階層に無い場合にas.exeが無いと言われます)。

そこで early-init.el (あるいはinit.el)に次のコードを追加します。

(2022-09-23追記: Windows版Emacsを28.1に上げたのでNative Compilationフィーバーに便乗する - Qiitaにあるように native-comp-driver-options 変数に -B オプションを指定した方が良さそうです。私はearly-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) )) のようにしました)

(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降
           (native-comp-available-p) ;;libgccjitが使える
           (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること)
  (defun my-comp-wrap-process-call (orig-fun &rest args)
    (let* (;; emacs.exeのあるディレクトリの一つ上のlib/gcc
           (lib-dir (expand-file-name
                     (file-name-concat invocation-directory "../lib/gcc")))
           ;; 環境変数PATHとLIBRARY_PATHを一時的に変更
           (process-environment
            (append
             (list (concat "PATH=" lib-dir ";" (getenv "PATH"))
                   (concat "LIBRARY_PATH=" lib-dir))
             process-environment)))
      ;; 元の関数を呼び出す
      (apply orig-fun args)))

  ;; コンパイル用にemacsを起動する関数をラップする
  (advice-add #'comp-final :around #'my-comp-wrap-process-call)
  (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))

このコードはネイティブコンパイルのために別プロセスでemacsを起動するときにだけ環境変数PATHとLIBRARY_PATHを変更します。起動されたemacsは最初からネイティブコンパイルに必要なコマンドにPATHが通った状態になります。

つまり、early-init.elより前に起動するネイティブコンパイルについてはlib/gccに置いたas.exeやld.exeが使われることによって解決し、それ以降なぜかエラーになるケースについては環境変数の一時的な変更で解決します。

ちなみに前者はあらかじめ必要なファイルをネイティブコンパイルしておくことによって回避可能です。エラーが出るファイルを手動でネイティブコンパイルしたり、試してはいませんが NATIVE_FULL_AOT=1 でビルドすると回避できるかもしれません。

なお、既にCygwin等にPATHが通っていると、設定が間違っていてもCygwinのas.exeやld.exeが起動する場合があるので注意が必要です。

なぜか失敗するケースは、調べた限りコンパイル時のカレントディレクトリによるようなので次のようなコードでも良いかもしれません。ただ、ネイティブコンパイルがカレントディレクトリに依存している(または将来するようになる)とまずいかもしれません。

(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降
           (native-comp-available-p) ;;libgccjitが使える
           (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること)
  (defun my-comp-wrap-process-call (orig-fun &rest args)
    ;; 一時的にカレントディレクトリを emacs-28.1/bin にする
    ;; でないと emacs-28.1/lib/gcc/as.exe を見つけてくれない
    (let ((default-directory invocation-directory))
      ;; 元の関数を呼び出す
      (apply orig-fun args)))

  ;; コンパイル用にemacsを起動する関数をラップする
  (advice-add #'comp-final :around #'my-comp-wrap-process-call)
  (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))

方法3:comp.elを修正する

最も単純な方法は comp.el を修正することだと思います。

前述の通りネイティブコンパイルはearly-init.elよりも前に起動する場合があり、comp.elもその時にロードされます。

従ってearly-init.elやinit.elで挙動を完全に修正するのは無理なので、comp.elを直接書き替えてしまった方が素直な方法となるでしょう。(Emacsの初期化プロセスについて詳しくないので他に何か方法があったらすみません)

具体的には、前と同じようなことをcomp.elの末尾に書き加えてやれば良いでしょう。例えば:

;; share/emacs/28.1/lisp/emacs-lisp/comp.el の末尾、(provide 'comp)の前に以下を追加
(defconst my-comp-tool-path "c:/hogehoge/msys/mingw64/bin") ;;自分のmingw64/binの場所

(defun my-comp-wrap-process-call (orig-fun &rest args)
  (let* ((process-environment
          (cons
           (concat "PATH=" my-comp-tool-path ";" (getenv "PATH"))
           process-environment)))
    ;; 元の関数を呼び出す
    (apply orig-fun args)))

;; コンパイル用にemacsを起動する関数をラップする
(advice-add #'comp-final :around #'my-comp-wrap-process-call)
(advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call)

my-comp-tool-pathにはmingw64/binの場所を指定してください。もしくは前にやったようにemacs.exeの位置から相対的に割り出しても良いでしょう(関連ファイルのコピーが必要になりますが)。

ファイル(comp.el)の途中を書き替えるのも分かりづらいかなと思ったのでadviceのままにしてあります。make-processやcall-processの前後を直接書き替えても良いでしょう。

comp.elを書き替えたら対応する.elcや.elnを削除するのもお忘れ無く。

(追記:2022-09-23)Emacs 28.2でNative Compileする

Emacs 28.2がリリースされてWindows版の公式ビルドも公開されました。

Index of /gnu/emacs/windows/emacs-28

emacs-28.2.zipを展開して、MSYS2のlibgccjit-0.dllとlibisl-23.dllをemacs-28.2/binへ、その他のライブラリやらas.exe、ld.exeやらをemacs-28.2/lib/gcc/へコピーし、early-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) )) と指定しただけで問題なくNative Compileできるようになりました。

ちなみに、最初MSYS2最新のlibgccjit-0.dllをbinに入れてもNative Compileが有効になりませんでした((native-comp-available-p)がnil)。

それをTwitterに書いたところ、libisl-23.dllも必要になったとの情報を頂きました。

調べてみると確かに最新のlibgccjit-0.dll内にはlibisl-23.dllという文字列があります。libgccjitのバージョンアップに伴い依存するdllが増えたようです。libisl-23.dllもemacs-28.2/binへコピーしたところnative-comp-available-pがtになりました。

こういうことがあるからバイナリ配布するならlibgccjit(や関連ファイル)も一緒に配布してほしいものです。

(追記:2023-07-31)Emacs 29.1

MS-Windows版 Emacs 29.1へ移行に書きました。dllが二つ増えていただけです。

2022-04-06

Windows上のEmacs 28.1でNative Compilationを使う

(2024-02-19追記: 手っ取り早く使い方を知りたい人はMS-Windows版 Emacs 29.1への移行作業をご覧下さい)

Emacs 28.1がリリースされましたね。私は少し前にpretestをいじりましたがすぐに27.2に戻ったので28系を本格的に使うのはようやくこれからです。

Windows版が見当たらないので自分でビルドしようかなーと思っていたらすぐにアップロードされたのでそれ(emacs-28.1.zip)をダウンロードして試してみました。

Emacs 28と言えば大分前からネイティブコンパイルという単語をあちこちで見かけていました。WindowsでGCCに依存した機能って大変なんじゃないの? と思っていたので期待はしていなかったのですが、一応どうなっているのか調べてみることにしました。

Native Compilation (GNU Emacs Lisp Reference Manual)

ええと何々

Emacsのカレントプロセスでネイティブコンパイル済みLispコードの生成およびロードが可能かどうか判断するには、native-comp-available-p (Native-Compilation Functionsを参照)を呼び出してください。

(native-comp-available-p)
nil

あー、やっぱり……。そもそも emacs-28.1.zip の中にGCCなんて入ってないですもんね。

ええと、ネイティブコンパイル機能はlibgccjitを利用していてビルド時に --with-native-compilation の指定が必要。MSYS2にはlibgccjitパッケージもちゃんとあるんですね。

仕方ない、ビルドしますか。以前ビルドした時のMSYS2環境が残っていますし。MSYS2のEmacsパッケージ(mingw-w64-x86_64-emacs)もまだ27.2みたいですし。

MSYS2でEmacs 28.1のビルド

(2022-04-07追記: 公式配布のWindows版はどうやら --with-native-compilation 付きでビルドされているようです。ただし必要なDLLが含まれていないので (native-comp-available-p)nil になります。MSYS2のmingw64/bin/libgccjit-0.dllをEmacsのbinへコピーするかmingw64/binへPATHを通したら (native-comp-available-p)t になりました。なので以下のビルドはしなくても行けます。ただしmingw64/binへ常にPATHを通したくない場合は色々と調整する必要があります(後述))

nt/INSTALLnt/INSTALL.W64等を見るにビルド方法に大きな変更は無さそうです。

まずはMSYS2から既存パッケージのアップグレード(sourceforgeのミラーが廃止されたんですね。/etc/pacman.d以下を少し修正)。

その後今回追加で必要になるであろうmingw-w64-x86_64-libgccjitをインストール。

pacman -S mingw-w64-x86_64-libgccjit

次にMinGW64環境からビルド。 --without-dbus--with-gnutls は前にビルドした時に付けていたので一応。今も必要なのか不明。 --with-native-compilation は無いとダメだったので付けました。 --prefix にはインストール先を明示的に指定します。後でMinGW64環境と分離したいので。

mkdir 20220406 && cd 20220406
wget https://ftp.gnu.org/gnu/emacs/emacs-28.1.tar.gz
tar xvfz emacs-28.1.tar.gz
./configure --without-dbus --with-gnutls --with-native-compilation --prefix=/c/hogehoge/emacs-28.1
make -j4 #4はコア数
make install-strip
/c/hogehoge/emacs-28.1/bin/runemacs.exe -Q # 試しに起動
(native-comp-available-p)
t

うん、大丈夫っぽい。というか裏で自動的にコンパイルが走っててキモいんですけど(笑)

MSYS2 MinGW環境からの分離

次にMSYS2 MinGW環境以外からでも単独で起動できるように必要なDLL等をemacsのディレクトリへコピーします。

これで runemacs.exe をエクスプローラ等から直接実行しても起動するようになりますが……やはりネイティブコンパイルでエラーが出まくります。libgccjitがGCCを見つけられないのですから当たり前ですよね。というかCygwinのが起動されてるっぽい? ldがライブラリが見つからないとか言ってます。私はCygwinの方にはPATHを通してしまっていますからね。Cygwinのldが起動されて、それがMinGWのライブラリが見つけられないという流れになってしまっているのかもしれません。

Internals — libgccjit 12.0.1 (experimental ) documentation あたりを見ると、PATH、LIBRARY_PATHあたりの環境変数をちゃんとすれば何とかなりそう。

試しに (setenv "PATH" (concat "c:/hogehoge/msys/mingw64/bin;" (getenv "PATH"))) を評価してPATHの先頭にMinGWのbinを追加したところコンパイルが成功するようになりました。

うーん、でも常時MinGWにPATHを通したくないんだよなぁ……。

native-comp-async-env-modifier-form という変数があったので、そこで環境変数を設定してみるもうまくいかず。

ネイティブコンパイルは外部のemacsを起動して行う(コンパイル用のelファイルを生成して、 emacs --batch -l ????.el を起動する)みたいなので、その起動時にだけ環境変数を追加することにしました。

(when (and (fboundp #'native-comp-available-p)
           (native-comp-available-p))
  (defun my-comp-set-env (orig-fun &rest args)
    (let ((process-environment
           (append
            (list (concat "PATH=c:/hogehoge/msys/mingw64/bin;" (getenv "PATH"))
                  ;;"LIBRARY_PATH=c:/hogehoge/msys/mingw64/lib" 不要っぽい
                  )
            process-environment)))
      (apply orig-fun args)))

  (require 'comp)
  (advice-add #'comp-final :around #'my-comp-set-env) ;; call-processでemacsを起動している
  (advice-add #'comp-run-async-workers :around #'my-comp-set-env)) ;;make-processでemacsを起動している

適当ですがとりあえず。これで直接runemacs.exeを起動したとき(PATHにmingw64/binが含まれていないとき)でもネイティブコンパイルが成功するようになりました。

で、 ~/.emacs.d/eln-cache/ に色々ファイルが生成されているわけですが、これってちゃんと使われているのでしょうか。スピードも上がっているのでしょうか。後で調べてみようっと。

(2022-04-08追記:)

28.0.91(pretest)のWindows用バイナリが出たときのredditの投稿に、参考になることが色々書いてありました。

alpha.gnu.org has shiny new Emacs 28.0.91 Windows binaries : emacs

中でも、MSYS2から17ファイルほどコピーすればMSYS2無しでもネイティブコンパイルが出来るのだとか。

  • bin/libgccjit-0.dll
  • lib/gcc/crtbegin.o
  • lib/gcc/crtend.o
  • lib/gcc/dllcrt2.o
  • lib/gcc/libadvapi32.a
  • lib/gcc/libgcc.a
  • lib/gcc/libgcc_s.a
  • lib/gcc/libkernel32.a
  • lib/gcc/libmingw32.a
  • lib/gcc/libmingwex.a
  • lib/gcc/libmoldname.a
  • lib/gcc/libmsvcrt.a
  • lib/gcc/libpthread.a
  • lib/gcc/libshell32.a
  • lib/gcc/libuser32.a
  • libexec/emacs/28.0.91/x86_64-w64-mingw32/ld.exe
  • libexec/emacs/28.0.91/x86_64-w64-mingw32/as.exe

MSYS2の中からファイルを探してemacs-28.1.zipを展開したディレクトリ下の上記の場所にコピーして試したところ、確かに関数の同期コンパイルくらいは出来ているように見えます(私のPCはCygwinのldにPATHが通ってたりするので、他のPCで再現しない可能性あり。後でちゃんと確認します)。非同期のコンパイルはPATHを調整しないとダメだとも書いてあります。

後でもう少し詳しく調べて追記します。

(2022-04-11追記: 別の記事として書きました)

Windows上のEmacs 28.1でネイティブコンパイルする方法(まとめ)

2022-04-06

Windows上のEmacsでSVG画像内のimage要素が表示されない問題

Emacs27になってからSVG画像内のimage要素が表示されなくて困ったのですが、調べたところ lib/gdk-pixbuf-2.0/2.10.0/loaders.cache ファイルが無いのが原因だと分かりました。

いずれも Index of /gnu/emacs/windows にある公式のWindows版ビルドでの話です。独自ビルドして必要そうなDLLファイルをMinGWから抜き出した場合も起こるかもしれません。

表示されないSVGの例

例えば次のようなSVGでimage要素の部分が表示されずblueのrectだけが表示されます。

<svg width="400" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <rect x="10px" y="10px" width="100px" height="80px" fill="blue" />
  <image xlink:href="image1.jpg" x="20px" y="20px" width="100px" height="100px" />
  <image xlink:href="image2.png" x="30px" y="30px" width="100px" height="100px" />
  <image xlink:href="image3.bmp" x="40px" y="40px" width="100px" height="100px" />
</svg>

image1.jpgimage2.pngimage3.bmp はSVGと同じディレクトリにあるものとします。もちろんブラウザ等、他のソフトウェアでは正しく表示されます。Emacsでの確認方法は色々ありますがdiredから開くだけでも十分です。

Emacs 26.3の場合

https://ftp.gnu.org/gnu/emacs/windows/emacs-26/emacs-26.3-x86_64.zip

問題は起きません。正しく表示されます。

Emacs 27.2の場合

解決方法

cmdプロンプトで emacs-27.2-x86_64/bin ディレクトリへ移動し、次のコマンドを実行すると emacs-27.2-x86_64/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache というファイルが生成されます。

gdk-pixbuf-query-loaders.exe --update-cache

Emacsを再起動して再び試してみると正しく表示されました。

Emacs 28.1の場合

https://ftp.gnu.org/gnu/emacs/windows/emacs-28/emacs-28.1.zip

image要素が表示されません。

解決方法

そもそも emacs-28.1/lib/ 以下に gdk-pixbuf-2.0 がありません。

Emacs27のファイル(emacs-27.2-x86_64/lib/gdk-pixbuf-2.0 以下。もちろん loaders.cache も)をEmacs28へコピーしたところ正しく表示されました。

loaders.cache を削除すると正しく表示されないので、Emacs28でもこのファイルは必要なようです。 emacs-28.1.zip には gdk-pixbuf-query-loaders.exe が含まれていないので、生成するにはEmacs 27の場合の手順を踏んでそちらからコピーしてくるのが手っ取り早いと思います。

原理

EmacsはSVGの描画にlibrsvgを使用していますが、librsvgは画像の処理にlibgdk_pixbufを使用しています。

libgdk_pixbufで画像を読み込むには画像の形式に対応するローダーライブラリが必要で、それが lib/gdk-pixbuf-2.0/2.10.0/loaders/ 以下にあるDLLファイル群です。

loaders.cache ファイルには、DLLファイルへのパスとそれが受け付けるMIMEタイプ、拡張子、ファイル先頭のマジックナンバー等の対応表が記されているようです。詳しくは調べていませんが、このファイルが無いとlibgdk_pixbufは画像を読み込めないようです。

不思議なことにEmacs26はこのファイルが無くても正しく読み込めていました。おそらくライブラリのバージョンアップに伴って必要になったのではないでしょうか。

必要性

EmacsでSVG内のimage要素、それもWindowsで扱う必要がある人はどれだけいるのでしょうか。多分ほとんどの人が問題に気がついていないのだと思います(まさか私の環境だけ起きてる?)。

しかしsvg.elにはsvg-embedのようなimage要素を使う機能も存在します。

また、Emacs28ではSVG内のimage要素が外部ファイルを参照する時の基準ディレクトリを指定する機能も追加されました。(https://github.com/emacs-mirror/emacs/blob/78ecd67888566167fb4c881d8350f611fa039649/etc/NEWS.28#L2417 )

図形だけでも面白いのに画像を自由に配置できるとなればもっと面白いことが色々出来るはずです。皆さんも是非試してみてはいかがでしょうか。

私も試しにimage要素を沢山生成して遊んでみました。

2022-04-06-svg-image-ex.gif

一枚一枚の画像をtransform属性で変形し(残念ながら透視投影は無理なので正射影です)、面単位でZソートを行っています。

結構遅いです。

何となくEmacs Lisp側というよりは描画ライブラリ側が遅いような気が。

2022-02-26

Emacsからアメダス観測所の計測データを取得する

前回の続き

一応任意のアメダス観測所の計測データを取得できるようにしてみました。あまり使う予定はありませんが。

misohena/el-jma: Emacs Interface for Japan Meteorological Agency Data

使い方。

まずはアメダスの観測所番号を調べます。 jma-amedas-read-amedas-code 関数で補完付きの入力ができるようになっているのでそれを使います。

(require 'jma-amedas)
(jma-amedas-read-amedas-code)

番号が分かったら jma-amedas-point-latest に渡すだけです。

(jma-amedas-point-latest ;;最新のデータを取得
 "44112");;八王子

結果は次のようになります。

(\20220226141000
 (prefNumber . 44)
 (observationNumber . 112)
 (temp .
       [12.6 0])
 (sun10m .
         [10 0])
 (sun1h .
        [1.0 0])
 (precipitation10m .
                   [0.0 0])
 (precipitation1h .
                  [0.0 0])
 (precipitation3h .
                  [0.0 0])
 (precipitation24h .
                   [0.0 0])
 (windDirection .
                [7 0])
 (wind .
       [8.8 0])
 (maxTempTime
  (hour . 4)
  (minute . 41))
 (maxTemp .
          [13.4 0])
 (minTempTime
  (hour . 20)
  (minute . 22))
 (minTemp .
          [-2.7 0])
 (gustTime
  (hour . 4)
  (minute . 58))
 (gustDirection .
                [8 0])
 (gust .
       [15.2 0]))

一応アクセッサがあるのでそれを使うと楽にデータを取り出せます。

(let* ((amedas-code "44112")
       (sample (jma-amedas-point-latest amedas-code)))
  (format "%sの%sの気温は%s℃、降水量(前1時間)は%smmです"
    (jma-amedas-point-name (jma-amedas-point amedas-code))
    (format-time-string "%Y-%m-%d %H:%M" (jma-amedas-sample-time sample))
    (or (jma-amedas-sample-temp sample) "-")
    (or (jma-amedas-sample-precipitation1h sample) "-")))
"八王子の2022-02-26 14:10の気温は12.6℃、降水量(前1時間)は0.0mmです"

取り出せる情報は設備の種類によって異なります(気象庁 | アメダスあたりを参照のこと)。

また、計測値の品質に問題がある場合はnilを返します。生データの配列(上の例だと(temp . [12.6 0])等)の二番目の要素が品質を示す値で、0のとき正常を意味します。

期間を指定して取得することも可能です。

(let* ((max-time (jma-amedas-latest-time))
       (min-time (time-add max-time (* -5 60 60))))
  (jma-amedas-point-samples-between "44112" min-time max-time))
((\20220226093000 (prefNumber . 44) (observationNumber . 112) (temp . [9.6 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [6 0]) (wind . [1.0 0]) (maxTempTime (hour . 0) (minute . 27)) (maxTemp . [9.7 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 20) (minute . 31)) (gustDirection . [13 0]) (gust . [4.4 0]))
 (\20220226094000 (prefNumber . 44) (observationNumber . 112) (temp . [9.9 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [4 0]) (wind . [1.5 0]) (maxTempTime (hour . 0) (minute . 40)) (maxTemp . [9.9 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 20) (minute . 31)) (gustDirection . [13 0]) (gust . [4.4 0]))
 (\20220226095000 (prefNumber . 44) (observationNumber . 112) (temp . [10.0 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [5 0]) (wind . [2.3 0]) (maxTempTime (hour . 0) (minute . 46)) (maxTemp . [10.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 0) (minute . 50)) (gustDirection . [7 0]) (gust . [5.4 0]))
 (\20220226100000 (prefNumber . 44) (observationNumber . 112) (temp . [10.3 0]) (snow1h . [0 :null]) (snow6h . [0 :null]) (snow12h . [0 :null]) (snow24h . [0 :null]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [3.6 0]) (maxTempTime (hour . 0) (minute . 56)) (maxTemp . [10.8 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 0) (minute . 58)) (gustDirection . [8 0]) (gust . [5.8 0]))
 (\20220226101000 (prefNumber . 44) (observationNumber . 112) (temp . [10.1 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [6 0]) (wind . [3.1 0]) (maxTempTime (hour . 0) (minute . 56)) (maxTemp . [10.8 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 0) (minute . 58)) (gustDirection . [8 0]) (gust . [5.8 0]))
 (\20220226102000 (prefNumber . 44) (observationNumber . 112) (temp . [10.6 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [4.0 0]) (maxTempTime (hour . 1) (minute . 15)) (maxTemp . [10.9 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226103000 (prefNumber . 44) (observationNumber . 112) (temp . [11.2 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [3.2 0]) (maxTempTime (hour . 1) (minute . 29)) (maxTemp . [11.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226104000 (prefNumber . 44) (observationNumber . 112) (temp . [11.2 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [8 0]) (wind . [3.1 0]) (maxTempTime (hour . 1) (minute . 39)) (maxTemp . [11.7 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226105000 (prefNumber . 44) (observationNumber . 112) (temp . [10.8 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [3.0 0]) (maxTempTime (hour . 1) (minute . 39)) (maxTemp . [11.7 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226110000 (prefNumber . 44) (observationNumber . 112) (temp . [11.2 0]) (snow1h . [0 :null]) (snow6h . [0 :null]) (snow12h . [0 :null]) (snow24h . [0 :null]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [8 0]) (wind . [3.6 0]) (maxTempTime (hour . 1) (minute . 58)) (maxTemp . [11.8 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226111000 (prefNumber . 44) (observationNumber . 112) (temp . [11.5 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [4.5 0]) (maxTempTime (hour . 1) (minute . 58)) (maxTemp . [11.8 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226112000 (prefNumber . 44) (observationNumber . 112) (temp . [11.5 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [9 0]) (wind . [2.8 0]) (maxTempTime (hour . 2) (minute . 14)) (maxTemp . [12.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226113000 (prefNumber . 44) (observationNumber . 112) (temp . [11.8 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [6 0]) (wind . [2.6 0]) (maxTempTime (hour . 2) (minute . 14)) (maxTemp . [12.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226114000 (prefNumber . 44) (observationNumber . 112) (temp . [12.8 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [4.4 0]) (maxTempTime (hour . 2) (minute . 40)) (maxTemp . [12.9 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226115000 (prefNumber . 44) (observationNumber . 112) (temp . [12.6 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [9 0]) (wind . [3.0 0]) (maxTempTime (hour . 2) (minute . 50)) (maxTemp . [12.9 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 1) (minute . 13)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226120000 (prefNumber . 44) (observationNumber . 112) (temp . [12.7 0]) (snow1h . [0 :null]) (snow6h . [0 :null]) (snow12h . [0 :null]) (snow24h . [0 :null]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [4.0 0]) (maxTempTime (hour . 2) (minute . 55)) (maxTemp . [13.0 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 0)) (gustDirection . [8 0]) (gust . [7.5 0]))
 (\20220226121000 (prefNumber . 44) (observationNumber . 112) (temp . [12.9 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [8 0]) (wind . [4.9 0]) (maxTempTime (hour . 3) (minute . 3)) (maxTemp . [13.1 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 6)) (gustDirection . [7 0]) (gust . [8.3 0]))
 (\20220226122000 (prefNumber . 44) (observationNumber . 112) (temp . [12.1 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [6 0]) (wind . [5.1 0]) (maxTempTime (hour . 3) (minute . 3)) (maxTemp . [13.1 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 6)) (gustDirection . [7 0]) (gust . [8.3 0]))
 (\20220226123000 (prefNumber . 44) (observationNumber . 112) (temp . [12.1 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [6.1 0]) (maxTempTime (hour . 3) (minute . 3)) (maxTemp . [13.1 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 21)) (gustDirection . [9 0]) (gust . [9.7 0]))
 (\20220226124000 (prefNumber . 44) (observationNumber . 112) (temp . [12.3 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [5.5 0]) (maxTempTime (hour . 3) (minute . 3)) (maxTemp . [13.1 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 21)) (gustDirection . [9 0]) (gust . [9.7 0]))
 (\20220226125000 (prefNumber . 44) (observationNumber . 112) (temp . [12.8 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [6.8 0]) (maxTempTime (hour . 3) (minute . 3)) (maxTemp . [13.1 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 21)) (gustDirection . [9 0]) (gust . [9.7 0]))
 (\20220226130000 (prefNumber . 44) (observationNumber . 112) (temp . [12.5 0]) (snow1h . [0 :null]) (snow6h . [0 :null]) (snow12h . [0 :null]) (snow24h . [0 :null]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [7.3 0]) (maxTempTime (hour . 3) (minute . 55)) (maxTemp . [13.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 3) (minute . 58)) (gustDirection . [7 0]) (gust . [10.1 0]))
 (\20220226131000 (prefNumber . 44) (observationNumber . 112) (temp . [12.8 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [6.6 0]) (maxTempTime (hour . 3) (minute . 55)) (maxTemp . [13.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 7)) (gustDirection . [8 0]) (gust . [10.6 0]))
 (\20220226132000 (prefNumber . 44) (observationNumber . 112) (temp . [12.7 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [6.9 0]) (maxTempTime (hour . 3) (minute . 55)) (maxTemp . [13.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 20)) (gustDirection . [8 0]) (gust . [10.9 0]))
 (\20220226133000 (prefNumber . 44) (observationNumber . 112) (temp . [12.6 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [8.0 0]) (maxTempTime (hour . 3) (minute . 55)) (maxTemp . [13.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 28)) (gustDirection . [8 0]) (gust . [12.5 0]))
 (\20220226134000 (prefNumber . 44) (observationNumber . 112) (temp . [12.9 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [7.4 0]) (maxTempTime (hour . 3) (minute . 55)) (maxTemp . [13.3 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 28)) (gustDirection . [8 0]) (gust . [12.5 0]))
 (\20220226135000 (prefNumber . 44) (observationNumber . 112) (temp . [12.9 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [8 0]) (wind . [7.9 0]) (maxTempTime (hour . 4) (minute . 41)) (maxTemp . [13.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 28)) (gustDirection . [8 0]) (gust . [12.5 0]))
 (\20220226140000 (prefNumber . 44) (observationNumber . 112) (temp . [12.9 0]) (snow1h . [0 :null]) (snow6h . [0 :null]) (snow12h . [0 :null]) (snow24h . [0 :null]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [8 0]) (wind . [9.3 0]) (maxTempTime (hour . 4) (minute . 41)) (maxTemp . [13.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 58)) (gustDirection . [8 0]) (gust . [15.2 0]))
 (\20220226141000 (prefNumber . 44) (observationNumber . 112) (temp . [12.6 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [8.8 0]) (maxTempTime (hour . 4) (minute . 41)) (maxTemp . [13.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 58)) (gustDirection . [8 0]) (gust . [15.2 0]))
 (\20220226142000 (prefNumber . 44) (observationNumber . 112) (temp . [13.0 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [8.0 0]) (maxTempTime (hour . 4) (minute . 41)) (maxTemp . [13.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 58)) (gustDirection . [8 0]) (gust . [15.2 0]))
 (\20220226143000 (prefNumber . 44) (observationNumber . 112) (temp . [13.0 0]) (sun10m . [10 0]) (sun1h . [1.0 0]) (precipitation10m . [0.0 0]) (precipitation1h . [0.0 0]) (precipitation3h . [0.0 0]) (precipitation24h . [0.0 0]) (windDirection . [7 0]) (wind . [7.2 0]) (maxTempTime (hour . 5) (minute . 29)) (maxTemp . [13.4 0]) (minTempTime (hour . 20) (minute . 22)) (minTemp . [-2.7 0]) (gustTime (hour . 4) (minute . 58)) (gustDirection . [8 0]) (gust . [15.2 0])))

1度のアクセスで10分毎の計測データが3時間分まとめて取得できるので、必要なだけアクセスして結果をつなぎ合わせてから不要な部分をカットしています。サーバへのアクセス頻度が高くなりがちなのでご注意ください(とは言え気象庁の観測データ一覧ページで行っていることと同じではあります)。

過去数日程度のデータしか取得できないようです。それ以前のデータは気象庁|各種データ・資料のあたりから取得しましょう。

2022-02-23 ,

Emacsから気象庁の天気予報にアクセスする

Emacs上で気象庁の天気予報を取得するものを作りました。

misohena/el-jma: Emacs Interface for Japan Meteorological Agency Data

例によってorg-modeのagenda上にも天気を表示できます。

org-modeのAgenda Viewに表示された天気予報
図1: org-modeのAgenda Viewに表示された天気予報

気象庁の提供するデータについては次のTweetを始め色々な資料がありました。

実際に天気予報を取得するにあたって、一番理解が難しかったのが予報区に関する知識でした。東京の天気を知りたかったら東京のコードを使ってデータを取得するだけなんじゃないの? と思いきや、事情はもう少し複雑でした。

天気予報のデータは概ね都道府県単位に近い区分(府県予報区)毎に取得(ダウンロード)できるのですが、そのデータの中はさらに場所が細分化されています。東京であれば、東京地方、伊豆諸島北部、伊豆諸島南部、小笠原諸島といった具合です。このサブエリアを一次細分区域と呼びます。東京都と言ってもこれらを一緒くたにはできませんから当然な話です。

それで済むかと思いきや、気温のデータだけ別のエリアコードが使われています。調べたところ、最低気温と最高気温の予報はアメダス観測所の地点のものが発表されており、そのアメダス観測所の名前とコードが使われています。東京の場合は、東京、大島、八丈島、父島といった具合です。それぞれ一次細分区域と対応していますが、一つの一次細分区域に複数のアメダス観測所がある府県もあります。

これで終わりかと思いきや、週間天気予報はまた別なエリア分けが使われています。気象庁|週間天気予報の解説には次のような記述があります。

「府県週間天気予報」は原則として府県予報区ごとに予報していますが、東京都と鹿児島県では、常に予報区内の区域を細分して予報しています(東京都は、東京地方と伊豆諸島と小笠原諸島に、鹿児島県は、鹿児島県(奄美地方除く)と奄美地方に、それぞれ細分)。また、季節を限定して区域を細分している予報区もあります。

基本的には週間予報は府県予報区単位の荒い予報ということになっているようですが、地形的・季節的な要因で多少細分化されています。

この週間天気予報で使用する区分けは府県予報区とも一次細分区域とも異なります。府県予報区や一次細分区域と同等になる区域は同じ名前やコードが使われていますが、どちらとも一致しない区域は独自の名前やコードが使われています。この区分けには明確な用語の定義が見当たらなかったので、とりあえず「週間予報区域」と呼ぶことにしました(今から思えば「府県週間予報区」くらいだったかなとも思いますが)。JSONの中ではweekと書いてあることが多いです。

一次細分区域と異なる範囲になるのでその下の選択すべきアメダス観測所も変わってきます。ただ、この週間予報区域に対応するアメダス観測所は一つのみに限定されるようです。

というわけで、場所を指定するのに五つもコードを指定しなければならなくなっていますがお許しください。一応 jma-forecast-area-read コマンドでコードを調べられるようになってます。市区町村から調べられればより良いのですが、その辺りは今後の課題と言うことで。

天気予報の取得方法については https://github.com/misohena/el-jma/blob/main/docs/how-to-get-jma-forecast.org にもメモを残しておきました。具体的なデータ例も https://github.com/misohena/el-jma/tree/main/example-data にあります。

2022-02-22

2022年春の新番組

開始日 タイトル 時刻 配信元
× 2022/01/04(火) イロドリミドリ 01:05 dアニメ
2022/01/05(水) テイコウペンギン -   -
× 2022/01/05(水) ハコヅメ~交番女子の逆襲~ 00:00 dアニメ
2022/01/05(水) リアデイルの大地にて 23:00 dアニメ
2022/01/05(水) 東京24区 12:00 dアニメ
× 2022/01/07(金) 終末のハーレム 01:30 dアニメ
2022/01/07(金) スローループ 22:30 dアニメ
2022/01/07(金) ジョジョの奇妙な冒険 第6部 ストーンオーシャン -   -
× 2022/01/07(金) ドールズフロントライン 12:00 dアニメ
2022/01/07(金) からかい上手の高木さん3 02:00 dアニメ
× 2022/01/07(金) CUE! 02:55 dアニメ
2022/01/08(土) その着せ替え人形は恋をする 00:30 dアニメ
2022/01/08(土) 明日ちゃんのセーラー服 01:00 dアニメ
2022/01/08(土) 現実主義勇者の王国再建記 第2期 -   -
× 2022/01/08(土) 怪人開発部の黒井津さん 02:30 dアニメ
2022/01/08(土) 失格紋の最強賢者 23:30? ABEMA
2022/01/09(日) 時光代理人-LINK CLICK- 22:30 Amazon
× 2022/01/09(日) 錆色のアーマ-黎明- 22:30 dアニメ
× 2022/01/09(日) 薔薇王の葬列 23:00 dアニメ
× 2022/01/09(日) フットサルボーイズ!!!!! 23:30 ABEMA
2022/01/09(日) 進撃の巨人 The Final Season 第2クール -   -
2022/01/09(日) 王子の本命は悪役令嬢 01:00 dアニメ
2022/01/10(月) ガル学。Ⅱ~Lucky Stars~ -   -
2022/01/10(月) トライブナイン 22:30 dアニメ
2022/01/10(月) 範馬刃牙(バキ 続編) -   -
× 2022/01/06(木) 最遊記RELOAD -ZEROIN- 00:00 dアニメ
2022/01/10(月) プリンセスコネクト!Re:Dive Season2 -   -
× 2022/01/10(月) 幻想三國誌 天元霊心記 12:00 dアニメ
2022/01/11(火) 賢者の弟子を名乗る賢者 00:00 dアニメ
2022/01/10(月) 錆喰いビスコ 00:30 ABEMA
2022/01/11(火) 天才王子の赤字国家再生術 ? ABEMA
2022/01/11(火) 異世界美少女受肉おじさんと ? ABEMA
2022/01/12(水) オンエアできない! 00:00 dアニメ
2022/01/12(水) BABY-HAMITANG -   -
2022/01/12(水) 漢化日記 -   -
2022/01/12(水) 殺し愛 23:30 dアニメ
2022/01/12(水) 平家物語 12:00 dアニメ
× 2022/01/13(木) オリエント 01:30 ABEMA
2022/01/13(木) ありふれた職業で世界最強 2nd season -   -
2022/01/13(木) あたしゃ川尻こだまだよ 01:30? Gyao!
2022/01/14(金) ヴァニタスの手記 第2クール -   -
2022/01/14(金) ニンジャラ 12:00 dアニメ
2022/01/16(日) 佐々木と宮野 00:00 dアニメ
2022/01/20(木) お昼のショッカーさん      
2022/01/21(金) テイルズ オブ ルミナリア -The Fateful Crossroad-      
2022/01/30(日) リーマンズクラブ 12:00 dアニメ
2022/01/28(金) 地球外少年少女      
2022/01/30(日) 永遠の831      
2022/01/–(-) 闇芝居 十期      
2021/02/09(水) デリシャスパーティ♡プリキュア 00:00 dアニメ
2022-01-22 ,

TermuxでEmacsを使うためにやったこと

Termuxのインストール

次の方法があります。

必ずしもF-Droidアプリは必要なくTermuxパッケージのページから直接apkをダウンロードすることもできます。GitHubからも(Debug版ですが)apkファイルをダウンロードできます。ビルドはそれほど難しくは無いと思います。

署名の問題があるので別の供給元から入手したものは共存できません。切り替えるにはいったんアンインストールが必要です(必要に応じてバックアップすること)。

私は最終的に次の二つを自分でビルドしてインストールしました。

ソースをgit cloneで入手し、既にPCに入っていたAndroid Studioでプロジェクトを開いてRunボタンを押したらすんなり成功しました。

Termuxの分かりにくそうな操作

ドロワー

画面左端から右へスワイプすると、ドロワー(サイドバー)が出てきます。

KEYBOARDを 長押し すると、下部ツールバーの表示状態を切り替えられます。

ツールバーの左スワイプ

下部ツールバーを左スワイプすると、Text Input Viewが出てきます。ここはInput Methodが効くので日本語を入力するのに使えます。私は上部で入力できるように改造したので今はほぼ使っていません。

Volume Up+ソフトキーボード

色々ショートカットが割り当てられています。

Touch Keyboard - Termux Wiki

キーボードまわりの設定

先日書きました。

Termuxでハードウェアキーボードからスムーズに日本語入力したい | Misohena Blog

  • .termux/termux-properties
    • soft-keyboard-toggle-behaviour = enable/disable
    • disable-hardware-keyboard-shortcuts = true
    • bell-character = ignore
    • ctrl-space-workaround = true
  • キーレイアウト問題(CtrlとCaps、BACKSLASH、無変換)
  • Input Methodがらみは独自ビルドで解決

Emacsのインストール

pkg upgrade
pkg install emacs
emacs

普通に起動します!

SSHとGitのインストール

自分の設定を持ってくるためにsshとgitをインストールしました。

pkg install openssh
pkg install git

鍵を作ってサーバに設定。

ssh-agentは .bashrcに . source-ssh-agent を追加するだけ。簡単!

(https://www.reddit.com/r/termux/comments/b8il5p/sshagent_want_to_start_again_when_new_terminal_is/ より)

Emacsの設定を持ってくる

Gitで自分の設定を持ってきて……

git clone --recursive ssh://hogehoge/my-emacs-config.git
cp my-emacs-config/.emacs.el ~/.emacs.d/init.el
emacs

エラー箇所を修正。最近色々判定をサボっていたので少し出ましたが、それほど多くはありませんでした。(display-graphic-p)はnilなのでそのあたりの機能はごっそり無効化。

Termux上で動いているかの判別は次のようにしました。

(setq my-termux-p (not (null (executable-find "termux-info"))))

How to detect in a BASH script that I'm in Termux? : termux によればtermux-toolsパッケージは常に存在すると考えて良いみたいです。他にもパスにcom.termuxという特徴的な文字列が含まれているのでそれをチェックするのも(コマンドを検索するより速くて)良さそうです。

パッケージのインストール。

M-x package-refresh
M-x package-install-selected-packages
C-x C-c
emacs

その他Emacsの修正

emacsclientが動かない

TMPDIR"/data/data/com.termux/files/usr/tmp" なのに server-socket-dir のデフォルト値が "/data/data/com.termux/files/usr/var/run/<uid>" になっているのが原因みたいです。次のようにします。

(setq server-socket-dir
      (and (featurep 'make-network-process '(:family local))
           (format "%s/emacs%d" (or (getenv "TMPDIR") "/data/data/com.termux/files/usr/var/run") (user-uid))))

(emacsclient can not find server socket · Issue #4230 · termux/termux-packagesより)

パッチが当たらなくなったのかな?

browse-urlでブラウザが開かない

(setq browse-url-browser-function 'browse-url-xdg-open)

(How can I make Emacs function 'browse-url-at-point work on a tablet running Android? - Emacs Stack Exchangeより)

browse-url-xdg-openxdg-open コマンドを使います。Termuxから使えるようです。 termux-open-url というコマンドもあります。

Androidのクリップボードと連携する

事前にtermux-apiアプリの導入が必要です。私はtermux-appを自分でビルドしてしまったので、こちらも自分でビルドする必要がありました。

その上でTermux内にもパッケージのインストールが必要です。

pkg install termux-api

そうすると次の二つのコマンドが使えるようになります。

termux-clipboard-set <text>
termux-clipboard-get

Emacsからは xclip というパッケージを導入するとこれらのコマンドを使ってくれるようになります。

M-x package-install xclip
M-x xclip-mode

(Copy/paste between apps and Emacs in Termux · Issue #6266 · termux/termux-packagesより)

sdcardの読み書き

termux-setup-storage を実行し権限を許可。すると ~/storage が追加され、 /sdcard にもアクセスできるようになりました。

その他インストールしたツール

  • pkg install ripgrep : Emacsから検索の際に使用
  • pkg install wget : ファイルをダウンロードするときに使用