3 Commits

Author SHA1 Message Date
ml a3e437277f Fix watch build to generate resources before the Pebble SDK packs them
build / build (push) Successful in 51s
2026-07-18 08:01:29 +02:00
ml ff2b43f1e0 Add a Docker-based build image and wire it into CI for the watch app
build / build (push) Failing after 0s
2026-07-18 07:52:40 +02:00
ml 4e83ebbf50 Add daily notification wakeups and a make watchapp packaging target
build / build (push) Successful in 20s
Schedules a one-shot Pebble wakeup for the configured notification
time, re-arming it on every launch and config change, and vibrates/
refreshes the report when it fires (foreground or fresh launch).
Also adds a `make watchapp` target that builds the watchapp via the
Pebble SDK and copies a versioned .pbw to dist/, matching `make
package`'s version resolution — deliberately not wired into CI, which
has no Pebble SDK installed.
2026-07-17 20:48:31 +02:00
15 changed files with 457 additions and 16 deletions
+28 -8
View File
@@ -1,7 +1,11 @@
name: build
# Build a versioned Linux CLI release tarball (see `make package`) on
# every push/PR, plus on-demand via the Gitea "Run workflow" button.
# Build a versioned Linux CLI release tarball (see `make package`) and the
# Pebble watchapp (see `make watchapp`) on every push/PR, plus on-demand
# via the Gitea "Run workflow" button. Runs inside build-image (see
# ../../build-image/Dockerfile, built/pushed via ../../build-image.sh and
# ../../upload-image.sh), which bundles the Pebble SDK alongside the C
# toolchain.
on:
push:
pull_request:
@@ -17,12 +21,18 @@ jobs:
# Must match a label your act_runner is registered with.
runs-on: ubuntu-latest
container:
image: cr.ladkau.de/deck-in-a-dash/builder:latest
credentials:
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
steps:
- uses: actions/checkout@v4
with:
# `make package`'s version comes from `git describe --tags` (see
# the Makefile's `package` target) - needs full history/tags,
# not actions/checkout's default shallow single-commit clone.
# `make package`/`make watchapp`'s version comes from `git
# describe --tags` - needs full history/tags, not
# actions/checkout's default shallow single-commit clone.
fetch-depth: 0
- name: Preflight
@@ -30,12 +40,14 @@ jobs:
set -euo pipefail
command -v cc >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: no C compiler (cc) in PATH" >&2; exit 1; }
command -v make >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: make not in PATH" >&2; exit 1; }
command -v pebble >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: pebble not in PATH" >&2; exit 1; }
- name: Build and test
run: |
set -euo pipefail
make test
make package
make watchapp
- name: Upload build artifact
# v4 uses the newer @actions/artifact backend, which this Gitea
@@ -45,14 +57,21 @@ jobs:
name: deck-in-a-dash
path: dist/deck-in-a-dash-*-linux-*.tar.gz
- name: Upload watchapp artifact
uses: actions/upload-artifact@v3
with:
name: deck-in-a-dash-watchapp
path: dist/deck-in-a-dash-*.pbw
- name: Publish to dl.ladkau.de
# Uploads the tarball over SFTP instead of using
# Uploads the tarball and .pbw over SFTP instead of using
# actions/upload-artifact (whose zip wrapping can't be disabled).
# Only runs on push so PR builds don't publish.
if: gitea.event_name == 'push'
run: |
set -euo pipefail
FILE="$(ls dist/deck-in-a-dash-*-linux-*.tar.gz)"
TARBALL="$(ls dist/deck-in-a-dash-*-linux-*.tar.gz)"
PBW="$(ls dist/deck-in-a-dash-*.pbw)"
mkdir -p ~/.ssh
echo "${{ secrets.DL_SFTP_KEY }}" > ~/.ssh/dl_sftp_key
chmod 600 ~/.ssh/dl_sftp_key
@@ -60,5 +79,6 @@ jobs:
-o StrictHostKeyChecking=accept-new \
uploader@dl.ladkau.de <<EOF
-mkdir files/deck-in-a-dash
put $FILE files/deck-in-a-dash/$(basename "$FILE")
put $TARBALL files/deck-in-a-dash/$(basename "$TARBALL")
put $PBW files/deck-in-a-dash/$(basename "$PBW")
EOF
+10
View File
@@ -7,6 +7,12 @@
/watch/resources/img
/watch/resources/app_icon.png
# waf's own build lock file - regenerated every `pebble build`, holds
# absolute machine paths and a full dump of the invoking shell's
# environment variables. Was tracked accidentally; see the commit that
# added this .gitignore line for the git rm --cached that untracked it.
/watch/.lock-waf_linux_build
# Durable backup of the same real birth data, outside dist/ so a dist/
# wipe doesn't lose it — see the Makefile's `scripts` target. Never
# commit this either.
@@ -17,3 +23,7 @@ core
core.*
.DS_Store
# Container registry credentials for build-image.sh/run-image.sh/
# upload-image.sh — never commit; copy registry.env.example instead.
/registry.env
+47
View File
@@ -20,6 +20,9 @@ make # builds dist/{deck-engine,interpreter-cli}, dist/img/, dist/i18n/
make test # builds and runs engine/tests/smoke_test.c and interpreter/tests/*_test.c
make clean # removes build/ and the generated binaries/img/i18n (never touches dist/user.properties)
make package # builds a versioned Linux CLI tarball at dist/ - see "Packaging" below
make watchapp # builds the Pebble watchapp and copies a versioned .pbw to
# dist/ - see "Packaging" below. Requires the Pebble SDK
# (not needed for anything else above); not run by CI.
```
A single `Makefile` at the repo root builds both `engine/` and
@@ -101,6 +104,50 @@ published over SFTP to `dl.ladkau.de` under `files/deck-in-a-dash/` -
requires a `DL_SFTP_KEY` secret configured on this repo (or inherited
from the org/instance level).
### Watch app packaging (`make watchapp`)
A separate target, deliberately **not** a prerequisite of `all` - it
shells out to `pebble build` inside `watch/` (via `watch/wscript`'s own
Pebble SDK/waf tooling, not this Makefile's plain-`cc` rules) and copies
the resulting `watch/build/watch.pbw` to
`dist/deck-in-a-dash-<version>.pbw`, using the exact same version
resolution as `make package` above (current git tag, or `make watchapp
VERSION=1.2.3` to override - see that section, not duplicated here).
Requires the Pebble SDK (`pebble` on `PATH`, plus the Python 3 + Pillow
`watch/wscript` needs for its resource-generation scripts) - a machine
without it can still build/test/package the CLI via every other target
in this file.
`.gitea/workflows/build.yml` **does** run `make watchapp` on every
push/PR, alongside `make test`/`make package` - the whole job runs
inside a container image (`build-image/`, built and pushed with
`build-image.sh`/`upload-image.sh`, see "Build image" below) that has
the Pebble SDK baked in, so the CI runner itself needs no toolchain
beyond Docker access to pull that image. Both the CLI tarball and the
`.pbw` are uploaded as build artifacts and published to
`dl.ladkau.de/files/deck-in-a-dash/` on push.
### Build image
`build-image/Dockerfile` pins the exact toolchain versions (Pebble
Tool, Pebble SDK core, Node.js) needed for `make test`, `make package`,
and `make watchapp`, so builds are reproducible independent of whatever
happens to be installed on a given machine. Three root-level scripts
drive it, each reading registry settings from a gitignored
`registry.env` (copy `registry.env.example` to create it - never commit
the real file, since it holds registry credentials) and the image tag
from `build-image/VERSION`:
- `build-image.sh` - builds and locally tags the image (`:VERSION` and
`:latest`), no push.
- `run-image.sh [VERSION]` - runs `make test package watchapp` inside
the locally-built image against a live bind-mount of the repo, wiping
generated build artifacts first for CI parity. Use this to validate a
`build-image/Dockerfile` change before pushing the image anywhere.
- `upload-image.sh` - logs in and pushes the image (`:VERSION` and
`:latest`) to the registry; `.gitea/workflows/build.yml` pulls
`:latest` to run its build job (see "Watch app packaging" above).
## Architecture
```
+35 -1
View File
@@ -87,7 +87,12 @@ GUIDANCE_TEST_BIN := $(BUILD_DIR)/guidance_test
ARCH := $(shell uname -m)
PACKAGE_DIR := $(BUILD_DIR)/package
.PHONY: all test clean images i18n scripts package
# ---- watch app (Pebble) ----
WATCH_DIR := watch
WATCH_PBW := $(WATCH_DIR)/build/watch.pbw
.PHONY: all test clean images i18n scripts package watchapp
all: $(ENGINE_BINARY) $(INTERP_BINARY) images i18n scripts
@@ -208,3 +213,32 @@ clean:
rm -rf $(BUILD_DIR) $(ENGINE_BINARY) $(INTERP_BINARY) $(DIST_IMG_DIR) $(DIST_I18N_DIR) \
$(DIST_DIR)/run-engine.sh $(DIST_DIR)/run-interpreter.sh
# user.properties holds the user's own data - never removed by clean.
# Builds the Pebble watchapp (`pebble build` in watch/, via its own
# wscript/pebble_sdk waf tooling - not a plain C toolchain, so this is a
# separate target from `all` rather than one of its prerequisites) and
# copies the resulting .pbw into dist/, versioned the same way `package`
# above versions the CLI tarball (current git tag, or
# `make watchapp VERSION=1.2.3` to override). Requires the Pebble SDK
# (`pebble` on PATH, plus Python 3 + Pillow for watch/scripts/*.py - see
# watch/wscript) - this is NOT checked by the root `make`/`make test`,
# so a machine without the Pebble SDK can still build/test the CLI.
watchapp:
cd $(WATCH_DIR) && pebble build
@mkdir -p $(DIST_DIR)
@V="$(VERSION)"; \
if [ -z "$$V" ]; then \
if TAG=$$(git describe --tags --exact-match --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null); then \
V=$${TAG#v}; \
else \
V="0.0.0-dev+$$(git rev-parse --short HEAD)"; \
echo "WARNING: HEAD is not on a vX.Y.Z tag - building placeholder version $$V (push a tag to drive a real release version)" >&2; \
fi; \
fi; \
case "$$V" in \
[0-9]*.[0-9]*.[0-9]*) ;; \
*) echo "PREFLIGHT FAIL: VERSION '$$V' is not a semantic version (expected X.Y.Z, optionally with a -pre+meta suffix)" >&2; exit 1;; \
esac; \
PBW_NAME="deck-in-a-dash-$$V.pbw"; \
cp $(WATCH_PBW) "$(DIST_DIR)/$$PBW_NAME"; \
echo "Wrote $(DIST_DIR)/$$PBW_NAME"
+34
View File
@@ -52,7 +52,18 @@ interpreter/ Separate module + binary (dist/interpreter-cli)
deck-engine's --format json output. Supports
--format text|html|json and --lang en|de for
its own output too (see CLAUDE.md).
watch/ The actual Pebble watchapp - links engine/src/
and interpreter/src/ in directly (see CLAUDE.md's
"Portability to the watch"), plus its own
persistence, reading recompute, UI, daily
notification, and config page. Built separately
from the rest of this repo (`make watchapp`,
below) - requires the Pebble SDK.
docs/ Architecture/dataflow diagrams, input/output format reference.
build-image/ Dockerfile for the reproducible build environment
(C toolchain + Pebble SDK) used by CI and by the
build-image.sh/run-image.sh/upload-image.sh scripts
at the repo root (see "Building" below).
Makefile Single root Makefile, builds both engine/ and
interpreter/, and packages a release (`make package`).
```
@@ -78,6 +89,29 @@ make package # -> dist/deck-in-a-dash-<version>-linux-<arch>.tar.gz, a
Requires only a C99 compiler and `make` — no other dependencies.
**Building the watch app itself** (`watch/`, the actual Pebble app)
needs the separate Pebble SDK (`pebble` on `PATH`), so it's a separate
target, not part of `make`/`make test`/`make package` above:
```bash
make watchapp # -> dist/deck-in-a-dash-<version>.pbw, same version
# resolution as `make package`
```
**CI runs `make watchapp` too**`.gitea/workflows/build.yml` builds
and tests everything inside a container built from `build-image/`,
which bundles the Pebble SDK alongside the C toolchain so the runner
itself doesn't need either installed. To build/test that image
yourself, copy `registry.env.example` to `registry.env` and fill in
your registry credentials, then:
```bash
./build-image.sh # build and locally tag the image
./run-image.sh # run `make test package watchapp` inside it, against
# this checkout, for a CI-equivalent local build
./upload-image.sh # push the image so CI can pull it
```
## Running
**Daily use**, via `dist/run-engine.sh`: pulls today's date and the
Executable
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Builds the release build environment image (build-image/Dockerfile)
# locally, tagged with build-image/VERSION and :latest. Does not push -
# test it with ./run-image.sh first, then publish with ./upload-image.sh.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROOT="$(pwd)"
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
command -v docker >/dev/null 2>&1 \
|| fail "docker not found in PATH"
[ -f "$ROOT/registry.env" ] \
|| fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials"
# shellcheck disable=SC1091
source "$ROOT/registry.env"
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
VERSION="$(<"$ROOT/build-image/VERSION")"
[ -n "$VERSION" ] || fail "build-image/VERSION is empty"
echo "== Building $REGISTRY_IMAGE:$VERSION =="
docker build \
-t "$REGISTRY_IMAGE:$VERSION" \
-t "$REGISTRY_IMAGE:latest" \
"$ROOT/build-image"
echo "== Done =="
echo "Image: $REGISTRY_IMAGE:$VERSION (and :latest)"
echo "Test it with ./run-image.sh, then publish with ./upload-image.sh"
+49
View File
@@ -0,0 +1,49 @@
# Build environment for deck_in_a_dash release artifacts: a plain C
# toolchain for the desktop deck-engine/interpreter-cli binaries (`make
# test`, `make package`) plus the Pebble SDK for the watch app (`make
# watchapp`), pinned to the versions validated on the maintainer's dev
# machine. Rebuild with ../build-image.sh whenever a version below
# changes.
#
# Does NOT contain any secrets - registry/SFTP credentials are injected
# at job runtime from Gitea Actions secrets, never baked into this image.
FROM ubuntu:22.04
ARG PEBBLE_TOOL_VERSION=5.0.39
ARG PEBBLE_SDK_CORE_VERSION=4.17
ARG NODE_VERSION=24.16.0
ENV DEBIAN_FRONTEND=noninteractive \
PATH=/root/.local/bin:${PATH}
# build-essential: gcc/make for the desktop deck-engine/interpreter-cli
# binaries (`make test`, `make package`).
# python3/python3-venv/python3-pip: `pebble sdk install` below creates a
# venv per SDK version; Pillow (installed below) is needed at `pebble
# build` time by watch/scripts/gen_card_images.py and gen_app_icon.py.
# git/curl/ca-certificates/unzip/tar/xz-utils: checkout, tool downloads,
# and `make package`/`make watchapp`'s `git describe --tags` versioning.
# openssh-client: the build workflow's `sftp` publish to dl.ladkau.de.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential python3 python3-venv python3-pip \
git curl ca-certificates unzip tar xz-utils openssh-client \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --no-cache-dir Pillow
# Node.js: `pebble sdk install` below runs `npm install` for the SDK
# core's bundled webpack tooling - it does not bring its own node/npm.
RUN curl -sSL -o /tmp/node.tar.xz \
"https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \
&& tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \
&& rm /tmp/node.tar.xz
# Pebble SDK: pebble-tool (via uv, pipx-style isolated install) + the
# sdk-core toolchain/webpack deps for PEBBLE_SDK_CORE_VERSION, both
# fully resolved here so no `docker run` of this image needs network
# access to install anything before `pebble build` works.
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
&& uv tool install "pebble-tool==${PEBBLE_TOOL_VERSION}" \
&& pebble sdk install "${PEBBLE_SDK_CORE_VERSION}"
WORKDIR /workspace
+1
View File
@@ -0,0 +1 @@
1
+7
View File
@@ -0,0 +1,7 @@
# Copy to registry.env (gitignored, never commit the real values) and
# fill in your cr.ladkau.de credentials. Used by build-image.sh,
# run-image.sh, and upload-image.sh.
REGISTRY=cr.ladkau.de
REGISTRY_IMAGE=cr.ladkau.de/deck-in-a-dash/builder
REGISTRY_USER=
REGISTRY_PASSWORD=
Executable
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Runs `make test package watchapp` inside the local build-image
# container, mirroring what the Gitea Actions build workflow does -
# useful for testing build-image changes locally before pushing
# anything to cr.ladkau.de.
#
# Usage: ./run-image.sh [VERSION]
# VERSION is passed through to `make` (see the Makefile's `package`/
# `watchapp` targets); omit it for their own git-tag-based default.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROOT="$(pwd)"
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
command -v docker >/dev/null 2>&1 \
|| fail "docker not found in PATH"
[ -f "$ROOT/registry.env" ] \
|| fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials"
# shellcheck disable=SC1091
source "$ROOT/registry.env"
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
IMAGE_TAG="$(<"$ROOT/build-image/VERSION")"
[ -n "$IMAGE_TAG" ] || fail "build-image/VERSION is empty"
IMAGE="$REGISTRY_IMAGE:$IMAGE_TAG"
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|| fail "$IMAGE not found locally — run ./build-image.sh first"
VERSION="${1:-${VERSION:-}}"
# CI always starts from a fresh checkout, but this script bind-mounts the
# live host repo — so build output left over from a previous local run
# (build/, dist/, watch/build, and the generated watch resources below)
# would make this run skip work a real fresh checkout wouldn't, silently
# hiding bugs that only show up in CI. Wipe them first so every run
# exercises a true from-scratch build, same as CI.
echo "== Cleaning generated build artifacts for a fresh build =="
rm -rf \
"$ROOT/build" "$ROOT/dist" "$ROOT/watch/build" \
"$ROOT/watch/resources/img" "$ROOT/watch/resources/app_icon.png" \
"$ROOT/watch/src/c/i18n_tables.auto.c"
echo "== Running make test package watchapp inside $IMAGE =="
# The container runs as root (needed for the toolchain baked into the
# image), so anything it writes into this bind mount — dist/, build/,
# watch/build, etc. — would otherwise come back owned by root, leaving
# the host repo unusable without sudo. Chown everything back to the host
# user on exit, whether the build succeeds or fails.
docker run --rm \
-v "$ROOT:/workspace" \
-w /workspace \
-e VERSION="$VERSION" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
"$IMAGE" \
bash -c '
git config --global --add safe.directory /workspace
trap "chown -R \"$HOST_UID:$HOST_GID\" /workspace" EXIT
make test package watchapp
'
echo "== Done — artifacts in dist/ =="
ls -la "$ROOT/dist"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Pushes the build environment image — already built locally with
# ./build-image.sh, and ideally verified with ./run-image.sh — to
# cr.ladkau.de. The Gitea Actions build workflow pulls this image to run
# `make test package watchapp`.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ROOT="$(pwd)"
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
command -v docker >/dev/null 2>&1 \
|| fail "docker not found in PATH"
[ -f "$ROOT/registry.env" ] \
|| fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials"
# shellcheck disable=SC1091
source "$ROOT/registry.env"
: "${REGISTRY:?registry.env must set REGISTRY}"
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
: "${REGISTRY_USER:?registry.env must set REGISTRY_USER}"
: "${REGISTRY_PASSWORD:?registry.env must set REGISTRY_PASSWORD}"
VERSION="$(<"$ROOT/build-image/VERSION")"
[ -n "$VERSION" ] || fail "build-image/VERSION is empty"
docker image inspect "$REGISTRY_IMAGE:$VERSION" >/dev/null 2>&1 \
|| fail "$REGISTRY_IMAGE:$VERSION not found locally — run ./build-image.sh first"
echo "== Logging in to $REGISTRY =="
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
echo "== Pushing $REGISTRY_IMAGE:$VERSION and :latest =="
docker push "$REGISTRY_IMAGE:$VERSION"
docker push "$REGISTRY_IMAGE:latest"
echo "== Done =="
echo "Image: $REGISTRY_IMAGE:$VERSION"
+22
View File
@@ -4,6 +4,7 @@
#include "app_message.h"
#include "config.h"
#include "i18n_tables.h"
#include "notify.h"
#include "reading_state.h"
#include "ui_report_window.h"
#include "ui_text_window.h"
@@ -26,6 +27,7 @@ static void on_config_updated(const WatchConfig *cfg) {
config_log(&s_config); /* the values this newly-recomputed reading is based on */
reading_state_recompute(&s_config.birth, s_config.gender);
notify_reschedule(&s_config); /* settings may have changed notify_enabled/hour/minute */
if (was_configured) {
report_window_reload();
} else {
@@ -33,16 +35,36 @@ static void on_config_updated(const WatchConfig *cfg) {
}
}
/* Fires only if the scheduled wakeup goes off while this app is already
* open (notify_was_wakeup_launch() below covers the far more common
* case: the wakeup launching the app fresh). Refreshes the same way a
* new day's config submission would, since the report window is already
* on screen and the reading may now be for a new day. */
static void on_wakeup(WakeupId wakeup_id, int32_t cookie) {
(void)wakeup_id;
(void)cookie;
vibes_double_pulse();
if (!s_config.configured) return;
reading_state_recompute(&s_config.birth, s_config.gender);
report_window_reload();
notify_reschedule(&s_config); /* wakeup_schedule() is one-shot - re-arm for tomorrow */
}
int main(void) {
config_load(&s_config);
watch_i18n_load(s_config.lang);
app_message_init(on_config_updated);
notify_subscribe(on_wakeup);
config_log(&s_config); /* the values this session's reading (if any) is based on */
if (s_config.configured) {
reading_state_recompute(&s_config.birth, s_config.gender);
report_window_push();
notify_reschedule(&s_config); /* wakeup_schedule() is one-shot - re-arm on every launch */
if (notify_was_wakeup_launch()) {
vibes_double_pulse();
}
} else {
show_setup_required();
}
+33
View File
@@ -0,0 +1,33 @@
#include "notify.h"
/* Only one kind of wakeup event exists in this app, so the cookie value
* itself carries no information - it exists only because
* wakeup_schedule()/wakeup_get_launch_event() require one. */
#define NOTIFY_WAKEUP_COOKIE 0
void notify_reschedule(const WatchConfig *cfg) {
wakeup_cancel_all();
if (!cfg->notify_enabled) return;
time_t now = time(NULL);
struct tm target_tm = *localtime(&now);
target_tm.tm_hour = cfg->notify_hour;
target_tm.tm_min = cfg->notify_minute;
target_tm.tm_sec = 0;
time_t target = mktime(&target_tm);
if (target <= now) target += SECONDS_PER_DAY; /* today's time already passed */
WakeupId id = wakeup_schedule(target, NOTIFY_WAKEUP_COOKIE, true);
if (id < 0) {
APP_LOG(APP_LOG_LEVEL_ERROR, "notify: wakeup_schedule failed: %d", (int)id);
}
}
bool notify_was_wakeup_launch(void) {
return launch_reason() == APP_LAUNCH_WAKEUP;
}
void notify_subscribe(WakeupHandler handler) {
wakeup_service_subscribe(handler);
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef WATCH_NOTIFY_H
#define WATCH_NOTIFY_H
#include "config.h"
/* (Re)schedules the daily notification wakeup from `cfg`'s notify_*
* fields: cancels any wakeup this app previously scheduled, then - if
* cfg->notify_enabled - schedules a new one-shot wakeup_schedule() for
* the next occurrence of notify_hour:notify_minute in the watch's own
* local time (today if that time hasn't passed yet, tomorrow otherwise).
* If !cfg->notify_enabled, this only cancels - no new wakeup is
* scheduled.
*
* Safe to call any time, and needs to be: once per app launch (Pebble's
* wakeup_schedule() is one-shot - each firing consumes itself, so the
* next day's has to be scheduled again after it fires or after a wakeup
* that arrives while the app happens to already be open - see
* notify_subscribe()) and on every config submission (settings may have
* changed notify_enabled/notify_hour/notify_minute). */
void notify_reschedule(const WatchConfig *cfg);
/* True if this app launch was triggered by the daily notification wakeup
* firing rather than the user/phone opening it normally - main.c uses
* this to vibrate on launch, since the report window it pushes either
* way already leads with day significance and the top transit (the
* "quick digest" this notification promises), so no separate screen is
* needed for it. */
bool notify_was_wakeup_launch(void);
/* Subscribes `handler` to fire if the scheduled wakeup goes off while
* this app is already running in the foreground - the only case
* notify_was_wakeup_launch() can't see, since no fresh launch happens
* then (see wakeup_service_subscribe()'s own doc comment in pebble.h).
* Call once, early in main(). */
void notify_subscribe(WakeupHandler handler);
#endif
+10 -7
View File
@@ -61,21 +61,24 @@ def configure(ctx):
def build(ctx):
ctx.load('pebble_sdk')
# Regenerate src/c/i18n_tables.auto.c from engine/i18n/*.lang,
# resources/img/*.png from res/img/*.jpeg, and resources/app_icon.png
# from res/app_icon_50x50_64_color.png, before compiling/bundling
# anything - see each script's own doc comment. All three run eagerly
# (not as waf Tasks) since the bundle step below depends on their
# output existing; gen_card_images.py/gen_app_icon.py both skip
# regenerating anything already up to date, so this stays cheap on
# repeat builds. Requires Pillow (`pip install Pillow`) - see
# watch/README.md.
# (not as waf Tasks) and must run *before* ctx.load('pebble_sdk')
# below, since loading that tool in a build context immediately packs
# resources/ into the app bundle - on a fresh checkout with no
# generated resources/img/ yet, that packing step would crash before
# these scripts ever got a chance to create it. gen_card_images.py/
# gen_app_icon.py both skip regenerating anything already up to date,
# so this stays cheap on repeat builds. Requires Pillow (`pip install
# Pillow`) - see watch/README.md.
subprocess.check_call([sys.executable, 'scripts/gen_i18n_tables.py'])
subprocess.check_call([sys.executable, 'scripts/gen_card_images.py'])
subprocess.check_call([sys.executable, 'scripts/gen_app_icon.py'])
ctx.load('pebble_sdk')
engine_dir = ctx.path.parent.find_dir('engine/src')
engine_nodes = [engine_dir.find_node(name) for name in ENGINE_SRCS]
interp_dir = ctx.path.parent.find_dir('interpreter/src')