add example 'wear-os' (android WearOS 2+ watches only)

This commit is contained in:
pls.153 2022-05-10 11:02:23 +02:00
parent df358fa5ae
commit 9e30041dba
20 changed files with 1598 additions and 6 deletions

View file

@ -8,7 +8,7 @@ QT_BEGIN_NAMESPACE
QObject* ini() {
// any QObject inherited class will do
static QObject* cpp = 0;
static QObject* cpp = nullptr;
if (!cpp) {
cpp = new CPP;

View file

@ -98,8 +98,12 @@ Simulator note: to show the virtual keyboard, use `cmd-k`.
Advanced note
-------------
Notes
-----
You will note that sometimes a change of a single Lisp file won't recompile
that file on the next `make`; in those cases, just do something like
`touch ../app.asd` to force recompilation of everything.
For conditions during Qt event processing, a fallback restart is added at
startup (needed in e.g. Slime).

View file

@ -19,7 +19,7 @@ Q_DECLARE_METATYPE(QTextDocument*)
QT_BEGIN_NAMESPACE
QObject* ini() {
static QObject* qt = 0;
static QObject* qt = nullptr;
if (!qt) {
qt = new QT;
#ifdef PLUGIN

2
examples/wear-os/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

8
examples/wear-os/app.asd Normal file
View file

@ -0,0 +1,8 @@
(defsystem :app
:serial t
:depends-on ()
:components ((:file "lisp/package")
(:file "lisp/ui-vars")
(:file "lisp/qt")
(:file "lisp/main")))

83
examples/wear-os/app.pro Normal file
View file

@ -0,0 +1,83 @@
CONFIG += 32bit
LISP_FILES = $$files(lisp/*) app.asd make.lisp
android {
32bit {
ECL = $$(ECL_ANDROID_32)
} else {
ECL = $$(ECL_ANDROID)
}
lisp.commands = $$ECL/../ecl-android-host/bin/ecl \
-norc -shell $$PWD/make.lisp
} else:unix {
lisp.commands = /usr/local/bin/ecl -shell $$PWD/make.lisp
} else:win32 {
lisp.commands = ecl.exe -shell $$PWD/make.lisp
}
lisp.input = LISP_FILES
win32: lisp.output = tmp/app.lib
!win32: lisp.output = tmp/libapp.a
QMAKE_EXTRA_COMPILERS += lisp
win32: PRE_TARGETDEPS = tmp/app.lib
!win32: PRE_TARGETDEPS = tmp/libapp.a
QT += quick qml widgets
TEMPLATE = app
CONFIG += c++17 no_keywords release
DEFINES = DESKTOP_APP INI_LISP
INCLUDEPATH = /usr/local/include
LIBS = -L/usr/local/lib -lecl
DESTDIR = .
TARGET = app
OBJECTS_DIR = tmp
MOC_DIR = tmp
linux: LIBS += -L../../../platforms/linux/lib
macx: LIBS += -L../../../platforms/macos/lib
win32: LIBS += -L../../../platforms/windows/lib
win32 {
include(../../src/windows.pri)
}
android {
QT += androidextras sensors
DEFINES -= DESKTOP_APP
DEFINES += QT_EXTENSION
INCLUDEPATH = $$ECL/include ../../../src/cpp
LIBS = -L$$ECL/lib -lecl
LIBS += -L../../../platforms/android/lib
HEADERS += cpp/qt.h
SOURCES += cpp/qt.cpp
ANDROID_EXTRA_LIBS += $$ECL/lib/libecl.so
ANDROID_PACKAGE_SOURCE_DIR = ../platforms/android
32bit {
ANDROID_ABIS = "armeabi-v7a"
} else {
ANDROID_ABIS = "arm64-v8a"
}
}
32bit {
LIBS += -llqml32 -llisp32
} else {
LIBS += -llqml -llisp
}
LIBS += -Ltmp -lapp
HEADERS += ../../src/cpp/main.h
SOURCES += ../../src/cpp/main.cpp
RESOURCES += $$files(qml/*)
RESOURCES += $$files(i18n/*.qm)
lupdate_only {
SOURCES += i18n/tr.h
}

View file

@ -0,0 +1,5 @@
# IP of your watch / run and confirm on the watch
adb kill-server
adb connect 192.168.1.?:5555

View file

@ -0,0 +1,31 @@
(in-package :heart-rate)
(defun run ()
(qt:ini)
#+android
(when t (ensure-permissions :body-sensors)
(qt:ini-sensors qt:*cpp*))
(update-heart-rate))
;;; runs a demo mode in absence of sensor data (first few seconds on the watch)
(defun update-heart-rate ()
(q! |stop| ui:*animation*)
(let* ((bpm #+android (qt:heart-rate qt:*cpp*))
(iv 1000)
(demo-mode (or (not bpm) (zerop bpm))))
(q> |border.color| ui:*main* (if demo-mode "#e04040" "#40c040"))
(when demo-mode
(setf bpm 60))
(let ((ms (truncate (/ 60000 bpm 2))))
(q> |duration| ui:*zoom-in* ms)
(q> |duration| ui:*zoom-out* ms)
(q> |text| ui:*heart-rate* (princ-to-string bpm))
(q> |text| ui:*accuracy* (if demo-mode
"demo"
(princ-to-string (qt:heart-rate-accuracy qt:*cpp*))))
(setf iv (* 2 ms)))
(q! |start| ui:*animation*)
(qsingle-shot iv 'update-heart-rate)))
(qlater 'run)

View file

@ -0,0 +1,4 @@
(defpackage :heart-rate
(:use :cl :qml)
(:export))

View file

@ -0,0 +1,20 @@
(defpackage :qt
(:use :cl :qml)
(:export
#:*cpp*
#:ini
#:ini-sensors
#:heart-rate
#:heart-rate-accuracy
#:keep-screen-on))
(in-package :qt)
(defvar *cpp* nil)
(defun ini ()
#+android
(progn
(setf *cpp* (qfind-child nil "QT"))
(let ((*package* (find-package :qt)))
(define-qt-wrappers *cpp*))))

View file

@ -0,0 +1,18 @@
(defpackage ui
(:use :cl)
(:export
#:*accuracy*
#:*animation*
#:*heart-rate*
#:*main*
#:*zoom-in*
#:*zoom-out*))
(in-package :ui)
(defparameter *animation* "animation")
(defparameter *accuracy* "accuracy")
(defparameter *heart-rate* "heart_rate")
(defparameter *main* "main")
(defparameter *zoom-in* "zoom_in")
(defparameter *zoom-out* "zoom_out")

View file

@ -0,0 +1,72 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.example.app" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<uses-sdk android:minSdkVersion="25" android:targetSdkVersion="30"/>
<uses-feature android:name="android.hardware.type.watch"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Heart Rate" android:extractNativeLibs="true">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="Heart Rate" android:screenOrientation="portrait" android:launchMode="singleTop" android:theme="@android:style/Theme.DeviceDefault">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidExtras.jar:jar/QtAndroidBearer.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value=""/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
<meta-data android:name="com.google.android.wearable.standalone" android:value="true"/>
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
</manifest>

View file

@ -0,0 +1,60 @@
import QtQuick 2.15
Rectangle {
objectName: "main"
anchors.fill: parent
anchors.centerIn: parent
radius: width / 2
color: "black"
border.width: 5
border.color: "#e04040"
Column {
anchors.centerIn: parent
Text {
anchors.horizontalCenter: parent.horizontalCenter
objectName: "heart_rate"
color: "white"
font.weight: Font.DemiBold
font.pixelSize: 32
text: "60"
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
objectName: "accuracy"
color: "white"
font.pixelSize: 14
text: "demo"
}
Image {
id: heart
width: 100
height: width
source: "../img/heart.png"
SequentialAnimation {
objectName: "animation"
running: true
ScaleAnimator {
objectName: "zoom_in"
target: heart
from: 0.7; to: 1.0
duration: 500
easing.type: Easing.InCubic
}
ScaleAnimator {
objectName: "zoom_out"
target: heart
from: 1.0; to: 0.7
duration: 500
easing.type: Easing.OutCubic
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,17 @@
import QtQuick 2.15
import "ext/" as Ext
Rectangle {
width: 210 // for desktop
height: 210
color: "black"
Ext.HeartRate {}
Keys.onPressed: {
if (event.key === Qt.Key_Back) {
event.accepted = true
Lisp.call("qml:qquit")
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
Note
----
This will only work with Qt 5.15 (customized AndroidManifest.xml file) and
requires WearOS 2 or later.
You'll also need an easy to apply hack for the **heart rate sensor**, see
[qt-sensor-hack](qt-sensor-hack/).
It is assumed that you already enabled the developer settings on your watch.
Build 32bit android APK
-----------------------
First you need to connect your device through WiFi. Please set your watch IP
address in [build-android/connect.sh](build-android/connect.sh) and run:
```
$ ./build-android/connect.sh
```
You probably want to build a **32bit** version, because most watches are 32bit.
For this you first need to build a 32bit version of the LQML library, see
[readme-prepare-android.md](../../readme-prepare-android.md).
Now you can build the app:
```
$ cd build-android
$ qmake-android "CONFIG+=32bit" ..
$ make apk
$ ./install-run.sh
```
Log note: for showing only your own messages, see `log.sh`.
Important note (Qt bug)
-----------------------
After editing [AndroidManifest.xml](platforms/android/AndroidManifest.xml)
in **Qt Creator** you need to add this inside `activity`:
```
android:theme="@android:style/Theme.DeviceDefault"
```
If you omit the above setting, the app will not start and just show an error
message instead.

View file

@ -0,0 +1,41 @@
Prepare
-------
Please copy the app template files first:
```
$ cd ..
$ ./copy.sh wear-os
```
Info
----
This shows how to build WearOS apps (typically a 32bit watch).
It also shows how to integrate the (not yet officially supported by Qt) heart
rate sensor.
It takes the watch sensor a few seconds before heart rate data is available, so
initially a demo mode with 60 bpm is shown. The red border will change to green
as soon as real data is arriving.
Run (desktop demo)
------------------
```
lqml run.lisp
```
Optionally pass `-slime` to start a Swank server, and connect from Emacs with
`M-x slime-connect`.
During development you can pass `-auto`, which will releoad all QML files after
you made a change to any of them and saved it. For re-initialization after
reloading, file `lisp/qml-reload/on-reloaded` will be loaded.
Closing the window quits the app. If you try to kill it with `ctrl-c`, you need
an additional `ctrl-d` to exit from ECL. To quit from Slime, do `(qq)` which is
short for `(qquit)`.

View file

@ -68,3 +68,5 @@ For 32bit builds you need to substitute every `qmake-android ...` with:
```
qmake-android "CONFIG+=32bit" ...
```
Just use the respective `build-android/` directory for building, and remember
to do a `make clean` before running `make`.

BIN
screenshots/wear-os.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB