mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
add example 'wear-os' (android WearOS 2+ watches only)
This commit is contained in:
parent
df358fa5ae
commit
9e30041dba
20 changed files with 1598 additions and 6 deletions
|
|
@ -8,8 +8,8 @@ QT_BEGIN_NAMESPACE
|
|||
|
||||
QObject* ini() {
|
||||
// any QObject inherited class will do
|
||||
static QObject* cpp = 0;
|
||||
if(!cpp) {
|
||||
static QObject* cpp = nullptr;
|
||||
if (!cpp) {
|
||||
cpp = new CPP;
|
||||
|
||||
// needed for QMessageBox
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ Q_DECLARE_METATYPE(QTextDocument*)
|
|||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QObject* ini() {
|
||||
static QObject* qt = 0;
|
||||
if(!qt) {
|
||||
static QObject* qt = nullptr;
|
||||
if (!qt) {
|
||||
qt = new QT;
|
||||
#ifdef PLUGIN
|
||||
ini_lisp();
|
||||
|
|
|
|||
2
examples/wear-os/.gitignore
vendored
Normal file
2
examples/wear-os/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
8
examples/wear-os/app.asd
Normal file
8
examples/wear-os/app.asd
Normal 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
83
examples/wear-os/app.pro
Normal 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
|
||||
}
|
||||
5
examples/wear-os/build-android/connect.sh
Executable file
5
examples/wear-os/build-android/connect.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
# IP of your watch / run and confirm on the watch
|
||||
|
||||
adb kill-server
|
||||
adb connect 192.168.1.?:5555
|
||||
|
||||
31
examples/wear-os/lisp/main.lisp
Normal file
31
examples/wear-os/lisp/main.lisp
Normal 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)
|
||||
4
examples/wear-os/lisp/package.lisp
Normal file
4
examples/wear-os/lisp/package.lisp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(defpackage :heart-rate
|
||||
(:use :cl :qml)
|
||||
(:export))
|
||||
|
||||
20
examples/wear-os/lisp/qt.lisp
Normal file
20
examples/wear-os/lisp/qt.lisp
Normal 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*))))
|
||||
18
examples/wear-os/lisp/ui-vars.lisp
Normal file
18
examples/wear-os/lisp/ui-vars.lisp
Normal 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")
|
||||
72
examples/wear-os/platforms/android/AndroidManifest.xml
Normal file
72
examples/wear-os/platforms/android/AndroidManifest.xml
Normal 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>
|
||||
60
examples/wear-os/qml/ext/HeartRate.qml
Normal file
60
examples/wear-os/qml/ext/HeartRate.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
examples/wear-os/qml/img/heart.png
Normal file
BIN
examples/wear-os/qml/img/heart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
17
examples/wear-os/qml/main.qml
Normal file
17
examples/wear-os/qml/main.qml
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
1172
examples/wear-os/qt-sensor-hack/QtActivity.java
Normal file
1172
examples/wear-os/qt-sensor-hack/QtActivity.java
Normal file
File diff suppressed because it is too large
Load diff
53
examples/wear-os/readme-build.md
Normal file
53
examples/wear-os/readme-build.md
Normal 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.
|
||||
|
||||
41
examples/wear-os/readme.md
Normal file
41
examples/wear-os/readme.md
Normal 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)`.
|
||||
|
||||
|
|
@ -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
BIN
screenshots/wear-os.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Loading…
Add table
Add a link
Reference in a new issue