📖

Apple SiliconでQt6

2022/05/29に公開

Apple SiliconでQt5 の続きです。
続きにしたかったのでこのタイトルですが、もはやApple Siliconはほとんど関係ありませんし、Qt6対応と言うよりは QtQuick Controls(2)対応の話です。

Qt6 対応

前回の前置きで、quazipとQt6がhomebrewでインストールできることを書きました。
Qt5は開発終了で、5.15 LTSのEOLが2023年5月らしいので、Qt6対応できるならやっておいた方が良いかもしれません。
(とは言え、現在homebrewでインストールされるQt6.3.0のEOLは2022年10月です。6.2 LTSで2024年9月)

※ 以下のサイトで確認しました。
https://endoflife.date/qt

とりあえずビルド

自分でビルドした Qt5.15.2 と quazip 1.3 の組み合わせだと、ComicsViewerを起動したときに落ちていたのですが、homebrewでインストールしたQt6とquazipの組み合わせだと落ちずに起動しました。

  • homebrew で quazip をインストール。
    • quazip 1.3 がインストールされます。依存関係で qt6.3.0もインストールされます。
    • qtのインストール先は /opt/homebrew
    • quazip は /opt/homebrew/include/quazip, /opt/homebrew/lib/quazip/libquazip1-qt6.dylib に入ります。(シンボリックリンクです)
  • ComicsViewer.pro では libquazip.dylib をリンクしようとするので、ファイル名を変更します。
diff --git a/ComicsViewer.pro b/ComicsViewer.pro
index ae7f1a5..0006ec1 100644
--- a/ComicsViewer.pro
+++ b/ComicsViewer.pro
@@ -13,7 +13,7 @@ CONFIG += c++11
 QMAKE_INCDIR += $$QUAZIP_DIR/include
 QMAKE_LIBDIR += $$QUAZIP_DIR/lib
 QMAKE_RPATHDIR += $$QUAZIP_DIR/lib
-QMAKE_LIBS += -lquazip
+QMAKE_LIBS += -lquazip1-qt6

 macx: ICON = ComicsViewer.icns
 win32: RC_FILE = ComicsViewer.rc
  • Qt6 からはビルドシステムがcmakeに移行していますが、まだqmakeも残っているのでqmakeでビルドします。
% qmake QUAZIP_DIR=/opt/homebrew
% make

あっさりビルドできました。

ぷちぷち潰す

Qt5 から Qt6 への移行方法は、以下に書いてあります。

https://doc.qt.io/qt-6/modulechanges.html

死ぬほど項目があって読む気がしないので、実際に実行して問題を潰していきます。
ただ、後から見返すと必要な情報はだいたい以下の二つだったようです。

実行時のエラーはコンソールに出るので、コマンドラインから直接起動します。

% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:2:1: module "QtQuick.Controls" version 1.3 is not installed

上のリンク先に書いてありますが、QtQuick.Controls 1.3 がないと言われます。Qt5.11以降では Qt Quick Controls 1系がdeprecatedになり、Qt6で削除されたようです。
最近のQMLの書き方では、バージョンを書かなくて良いようなので、バージョンを外していきます。

diff --git a/main.qml b/main.qml
index d5ed22c..e2e323b 100644
--- a/main.qml
+++ b/main.qml
@@ -1,9 +1,9 @@
-import QtQuick 2.4
-import QtQuick.Controls 1.3
-import QtQuick.Window 2.2
-import QtQuick.Dialogs 1.2
-import Qt.labs.settings 1.0
-import ComicsViewer 1.0
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Window
+import QtQuick.Dialogs
+import Qt.labs.settings
+import ComicsViewer
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:55:16: StatusBar is not a type

Qt Quick Controls 1 から 2(のちに2は取れた)になったときに、StatusBarはなくなったようです。(かつ、ApplicationWindowのstatusBar は footer に変わったようです。)

-    statusBar: StatusBar {
-        anchors.fill: parent
+    footer: ToolBar {
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:94:13: Cannot assign to non-existent property "maximumValue"

Slider の minimumValue, maximumValue が from, to に変わったようです。(ビキビキ)

-            minimumValue: 0
-            maximumValue: comicModel.maxPage < 2 ? 1 : comicModel.maxPage - 1
+            from: 0
+            to: comicModel.maxPage < 2 ? 1 : comicModel.maxPage - 1

ここまでで、やっと画面が出ますが、まだ以下のメッセージが出ます。

% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
qt.qml.context: qrc:/main.qml:34:21 Parameter "index" is not declared. Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead.

なんのことやら良くわからなかったのですが、エラーメッセージでググったら qt - Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead - Stack Overflow に辿り着きました。

@ -31,8 +31,8 @@ ApplicationWindow {
                 title: qsTr("Open &recent")
                 Instantiator {
                     model: comicModel.recentFiles
-                    onObjectAdded: recentFileMenu.insertItem(index, object)
-                    onObjectRemoved: recentFileMenu.removeItem(object)
+                    onObjectAdded: (index, object) => recentFileMenu.insertItem(index, object)
+                    onObjectRemoved: object => recentFileMenu.removeItem(object)
                     delegate: MenuItem {
                         text: modelData
                         onTriggered: comicModel.open("file://" + modelData)
@@ -61,7 +60,7 @@ ApplicationWindow {

     ComicModel {
         id: comicModel
-        onCurrentPageChanged: {
+        onCurrentPageChanged: function(currentPage) {
             pageSlider.pageNumber = currentPage
         }
     }

これで、起動時にはエラーが出なくなりました。
続いて、メニューからファイルを開くと、qrc:/main.qml:149: ReferenceError: fileUrl is not defined と言われます。
FileDialog の fileUrl が、selectedFile に変わったようです。

@@ -147,7 +146,7 @@ ApplicationWindow {
     FileDialog {
         id: fileDialog
         onAccepted: {
-            comicModel.open(fileUrl)
+            comicModel.open(selectedFile)
             //comicModel.currentPage = 1
         }
     }

これで、ファイルを開いて読めるようになりました。
しかし、マウスクリックでページ送りができるのですが、カーソルキーが効きません。
どうも、ファイルダイアログを開くとキーが効かなくなるようです。(ファイルダイアログを開かずに、「最近開いたファイル」から開くとキーが効きます)
これもググると、 QT/QML , After Dialog is open , Back button not working on Android? - Stack Overflow に辿り着きました。ダイアログが閉じたとき(visibleがfalseになったとか)に、FocusScopeにforceActiveFocus()してあげると良いみたいです。

@@ -129,6 +128,7 @@ ApplicationWindow {
     }

     FocusScope {
+        id: focus
         focus: true
         Keys.onRightPressed: {
             comicModel.previousPage()
@@ -147,8 +147,13 @@ ApplicationWindow {
     FileDialog {
         id: fileDialog
         onAccepted: {
             comicModel.open(selectedFile)
             //comicModel.currentPage = 1
         }
+        onVisibleChanged: {
+            if (!visible) {
+                focus.forceActiveFocus()
+            }
+        }
     }
 }

これで、キーも効くようになりました。(ちょっとまだたまにおかしかったりするようですが)

これでだいたい動くようになったかなと思ったのですが、何か違和感があります。
良く見ると、今までMacのメニューバーに表示されていたメニューバーが、アプリケーションウィンドウ内に表示されているのです。

  • Qt5のとき
    Qt5のとき
  • Qt6のとき
    Qt6のとき

Qt6 になったときと言うか、Qt Controls 1 から 2 になったときに、platformのメニューバーを使うのをやめて、ウィンドウ内にQtが描画するようになったらしいです。
で、QtQuick.Controls の代わりに Qt.labs.platform を使うと、昔のメニューバーが出せるようです。
ただし、Qt.labs.platform は仕様が変わったり、なくなったりする可能性があるとのことで、いつまで使えるかわかりません。

Note: Types in Qt.labs modules are not guaranteed to remain compatible in future versions.

あと、QtQuick.Controls と Qt.labs.platform に同名の Type があるので、そのまま import できません。import Qt.labs.platform as Platform のように as を使って別名をつけてあげます。(完全修飾名的なものはないのかな?)

さらに、QtQuick.Controls.MenuBar は ApplicationWindow の menuBar プロパティに設定しますが、Qt.labs.platform.MenuBar は ApplicationWindow の子要素にします。

% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:23:14: Cannot assign object of type "Platform.MenuBar" to property of type "QQuickItem*" as the former is neither the same as the latter nor a sub-class of it.
-    menuBar: MenuBar {
-        Menu {
+    Platform.MenuBar {
+        Platform.Menu {
             title: qsTr("&File")
-            MenuItem {
+            Platform.MenuItem {
                 text: qsTr("&Open...")
                 onTriggered: fileDialog.open()
             }
-            Menu {
+            Platform.Menu {
                 id: recentFileMenu
                 title: qsTr("Open &recent")
                 Instantiator {
                     model: comicModel.recentFiles
                     onObjectAdded: (index, object) => recentFileMenu.insertItem(index, object)
                     onObjectRemoved: object => recentFileMenu.removeItem(object)
-                    delegate: MenuItem {
+                    delegate: Platform.MenuItem {
                         text: modelData
                         onTriggered: comicModel.open("file://" + modelData)
                     }
                 }
-                MenuSeparator {
+                Platform.MenuSeparator {

                 }
-                MenuItem {
+                Platform.MenuItem {
                     text: qsTr("&Delete all")
                     onTriggered: comicModel.clearRecentFiles()
                 }
             }
-            MenuItem {
+            Platform.MenuItem {
                 text: qsTr("E&xit")
                 onTriggered: Qt.quit();
             }
         }
     }

ここまでの内容を github のqt6ブランチに入れました。
commit はこちら

元々のコードを書いたのが7年前の Qt 5.4の頃で、QMLもまだ始まったばかりと言う感じだったので、いろいろ変更があるのはしょうがないのですが、え、そこ変える?って言うのが結構あるような気がします。
iPhoneとかAndroidのアプリを作ってる人はもっと大変なんでしょうねえ。

追記(2022.6.6)

どうも、Qt6でビルドしたものだと表示されない画像があることに気づきました。
コンソールから実行してみると、以下のエラーが出ています。

qt.gui.imageio: QImageIOHandler: Rejecting image as it exceeds the current allocation limit of 128 megabytes

エラーメッセージでぐぐると、QImageReaderのドキュメントに辿り着きました。
QImageReaderのクラスメソッドに allocationLimit(), setAllocationLimit() が追加されていて、このサイズを超える画像は読み込まなくなったようです。
と、言うことで、QImageReader::setAllocationLimit(0) を追加して無制限に戻してあげたら無事に画像が読み込めるようになりました。

Discussion