gneiss 0.1.0

Safe Rust SDK for Pebble watchapps and watchfaces
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
//! Handlers for firmware events.
//!
//! A service is a stream of events from the firmware, such as the time ticking over or the
//! battery level changing. Mark a free function with
//! [`#[gneiss::service]`](macro@crate::service), naming the service and anything it takes, and
//! gneiss subscribes it once your `main` has returned:
//!
//! ```no_run,standalone_crate
//! # use gneiss::{service::*, time};
//! # gneiss::__doctest_slices!();
//! #[gneiss::service(TickService, time::TimeUnits::MINUTES)]
//! fn on_minute(_time: time::TickTime, _changed: time::TimeUnits) {
//!     // ...
//! }
//! ```
//!
//! The function's signature is the service's [`Service::Handler`]. Anything after the service's
//! name becomes its [`Service::SubscribeArg`]: list the values separated by commas, and the macro
//! passes several as a tuple. Each service can be subscribed once per app; a second
//! `#[gneiss::service]` for the same one is a link error.
//!
//! Handlers run on the app's single task, between other callbacks, so they never race the UI. To
//! change what's on screen, take a [`UiGuard`](crate::ui::UiGuard) with `UiGuard::enter()` and reach
//! the window through its [`StaticWindow`](crate::ui::StaticWindow).
//!
//! # Context
//!
//! [`TouchService`], [`HealthService`] and [`AppMessageService`] can carry state between calls. Pass
//! `Some(value)` as the context argument and the handler gets `Some(&mut value)` every time; the
//! value is moved to the app heap for the rest of the app's life. Pass `None` to go without.
//!
//! # Reading on demand
//!
//! [`Peekable`] services can also be read without waiting for an event: `BatteryService::peek(())`
//! is the current charge state.
//!
//! # Availability
//!
//! Some services only exist on some watches, such as touch on watches with a touch panel.
use core::{marker::PhantomData, mem::MaybeUninit, ptr};

#[cfg(pbl_touch)]
use crate::touch;
use crate::{accel, battery, compass, connection, focus, health, link, sys, time, wakeup};

/// The context of a service that carries none.
pub struct NoneT;

/// A firmware event stream that a [`#[gneiss::service]`](macro@crate::service) function can
/// subscribe to.
pub trait Service: Sealed {
	type Handler: Copy;
	/// `extern "C"` generated by `#[gneiss::service]`
	type RawHandler: Copy;
	type SubscribeArg;
	type Context;

	/// Register `f` with the firmware. Called by the code `#[gneiss::service]` generates.
	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg);
}

/// A service whose latest reading can be read on demand.
pub trait Peekable: Service {
	type PeekArgs;
	type Output;

	/// Peeks the service, returning its last reading as converted types
	fn peek(arg: Self::PeekArgs) -> Self::Output;
}

/// Called every time the time elapses the `TimeUnit` used in the `SubscribeArg`
/// For instance, a once-per-hour routine:
/// ```no_run,standalone_crate
/// # use gneiss::{service::*, time};
/// # gneiss::__doctest_slices!();
/// #[gneiss::service(TickService, time::TimeUnits::HOURS)]
/// fn hourly(_time: time::TickTime, _changed: time::TimeUnits) {
///     // ...
/// }
/// ```
pub struct TickService;
impl Sealed for TickService {}
impl Service for TickService {
	type Context = NoneT;
	type Handler = fn(time::TickTime, time::TimeUnits);
	type RawHandler = unsafe extern "C" fn(*mut sys::tm, sys::TimeUnits);
	type SubscribeArg = time::TimeUnits;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering TickService handler;");
		unsafe { sys::tick_timer_service_subscribe(a.bits(), Some(f)) }
	}
}
impl Peekable for TickService {
	type Output = time::TickTime;
	type PeekArgs = ();

	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		// SAFETY: Time returns i32, &i32 is always valid, localtime has no documented fail state
		unsafe { *sys::localtime(&sys::time(ptr::null_mut())) }
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! TickService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_TickService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(tm: *mut $crate::sys::tm, unit: $crate::sys::TimeUnits) {
			// SAFETY: *mut tm is valid for the duration of the callback, after which it is
			// cleaned up - reading now is valid and correct, passing as a ref could escape.
			$f(
				unsafe { tm.read() },
				::gneiss::time::TimeUnits::from_bits_retain(unit),
			)
		}
	};
}

/// Called on battery state changes
pub struct BatteryService;
impl Sealed for BatteryService {}
impl Service for BatteryService {
	type Context = NoneT;
	type Handler = fn(battery::BatteryChargeState);
	type RawHandler = unsafe extern "C" fn(sys::BatteryChargeState);
	type SubscribeArg = ();

	fn subscribe(f: Self::RawHandler, _a: Self::SubscribeArg) {
		crate::log::debug!("Registering BatteryService handler;");
		unsafe { sys::battery_state_service_subscribe(Some(f)) }
	}
}
impl Peekable for BatteryService {
	type Output = battery::BatteryChargeState;
	type PeekArgs = ();

	/// Query the state of the battery
	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		unsafe { sys::battery_state_service_peek() }
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! BatteryService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_BatteryService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(charge: $crate::sys::BatteryChargeState) {
			$f(charge)
		}
	};
}

/// Called when backlight enables/disables.
///
/// Absent on the frozen-SDK watches: their `pebble.h` stubs
/// `backlight_service_subscribe` out to a no-op macro. Upstream's only
/// backlight define, `PBL_RGB_BACKLIGHT`, marks a colour-capable backlight
/// (emery) rather than the presence of this service.
#[cfg(not(pbl_sdk_frozen))]
pub struct BacklightService;
#[cfg(not(pbl_sdk_frozen))]
impl Sealed for BacklightService {}
#[cfg(not(pbl_sdk_frozen))]
impl Service for BacklightService {
	type Context = NoneT;
	type Handler = fn(bool);
	type RawHandler = unsafe extern "C" fn(bool);
	type SubscribeArg = ();

	fn subscribe(f: Self::RawHandler, _a: Self::SubscribeArg) {
		crate::log::debug!("Registering BacklightService handler;");
		unsafe { sys::backlight_service_subscribe(Some(f)) }
	}
}
#[cfg(not(pbl_sdk_frozen))]
#[macro_export]
#[doc(hidden)]
macro_rules! BacklightService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_BacklightService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(on: bool) {
			$f(on)
		}
	};
}

/// Called when the watch registers a "tap" with the accelerometer
pub struct AccelTapService;
impl Sealed for AccelTapService {}
impl Service for AccelTapService {
	type Context = NoneT;
	type Handler = fn(accel::Axis, accel::Direction);
	type RawHandler = unsafe extern "C" fn(sys::AccelAxisType, core::ffi::c_int);
	type SubscribeArg = ();

	fn subscribe(f: Self::RawHandler, _a: Self::SubscribeArg) {
		crate::log::debug!("Registering AccelTapService handler;");
		unsafe { sys::accel_tap_service_subscribe(Some(f)) }
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! AccelTapService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_AccelTapService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(axis: $crate::sys::AccelAxisType, direction: i32) {
			$f(axis.into(), direction.into())
		}
	};
}

/// Called with a batch of processed accelerometer samples, every `SubscribeArg` samples.
/// Shares the OS subscription slot with [`AccelRawDataService`].
pub struct AccelDataService;
impl Sealed for AccelDataService {}
impl Service for AccelDataService {
	type Context = NoneT;
	type Handler = fn(&[accel::AccelData]);
	type RawHandler = unsafe extern "C" fn(*mut sys::AccelData, u32);
	type SubscribeArg = core::num::NonZeroU32;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering AccelDataService handler;");
		unsafe { sys::accel_data_service_subscribe(a.get(), Some(f)) }
	}
}
impl Peekable for AccelDataService {
	type Output = Option<accel::AccelData>;
	type PeekArgs = ();

	/// Peek one accel sample if available.
	/// Older watches may return Some(garbage) if accel is not running, as they never flag an error case
	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		let mut dat = MaybeUninit::uninit();
		let ret = unsafe { sys::accel_service_peek(dat.as_mut_ptr()) };
		// SAFETY: 0 == OK, dat is populated
		(ret == 0).then(|| unsafe { accel::AccelData::from(dat.assume_init()) })
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! AccelDataService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_AccelData")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(data: *mut $crate::sys::AccelData, num_samples: u32) {
			$f(if data.is_null() {
				&[]
			} else {
				::core::slice::from_raw_parts(data.cast(), num_samples as usize)
			})
		}
	};
}

/// Called with a batch of raw accelerometer samples and the timestamp (in ms) of the
/// first sample, every `SubscribeArg` samples.
/// Shares the OS subscription slot with [`AccelDataService`].
pub struct AccelRawDataService;
impl Sealed for AccelRawDataService {}
impl Service for AccelRawDataService {
	type Context = NoneT;
	type Handler = fn(&[accel::Sample], u64);
	type RawHandler = unsafe extern "C" fn(*mut sys::AccelRawData, u32, u64);
	type SubscribeArg = core::num::NonZeroU32;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering AccelRawDataService handler;");
		unsafe { sys::accel_raw_data_service_subscribe(a.get(), Some(f)) }
	}
}
impl Peekable for AccelRawDataService {
	type Output = Option<(accel::Sample, u64)>;
	type PeekArgs = ();

	/// Peek one accel sample if available.
	/// Older watches may return Some(garbage) if accel is not running, as they never flag an error case
	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		let mut dat = MaybeUninit::uninit();
		let ret = unsafe { sys::accel_service_peek(dat.as_mut_ptr()) };
		// SAFETY: 0 == OK, dat is populated
		(ret == 0)
			.then(|| unsafe { accel::AccelData::from(dat.assume_init()) })
			.map(|d| (d.sample(), d.timestamp()))
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! AccelRawDataService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_AccelData")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(data: *mut $crate::sys::AccelRawData, num_samples: u32, timestamp: u64) {
			$f(
				if data.is_null() {
					&[]
				} else {
					::core::slice::from_raw_parts(data.cast(), num_samples as usize)
				},
				timestamp,
			)
		}
	};
}

/// Called when the heading changes by more than the supplied `DeltaAngle`
pub struct CompassService;
impl Sealed for CompassService {}
impl Service for CompassService {
	type Context = NoneT;
	type Handler = fn(compass::HeadingData);
	type RawHandler = unsafe extern "C" fn(sys::CompassHeadingData);
	type SubscribeArg = compass::DeltaAngle;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering CompassService handler;");
		unsafe {
			sys::compass_service_set_heading_filter(a.inner().abs());
			sys::compass_service_subscribe(Some(f));
		}
	}
}
impl Peekable for CompassService {
	type Output = compass::HeadingData;
	type PeekArgs = ();

	/// Peek the compass service
	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		let mut data = MaybeUninit::uninit();

		assert!(
			unsafe { sys::compass_service_peek(data.as_mut_ptr()) } == 0,
			"Expected infallible, please report bug uptstream"
		);

		// SAFETY: peek deocumented to be infallible, we panic if it lied
		compass::HeadingData::from(unsafe { data.assume_init() })
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! CompassService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_CompassService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(headings: $crate::sys::CompassHeadingData) {
			$f(headings.into())
		}
	};
}

/// Called on app focus gain/loss
pub struct AppFocusService;
impl Sealed for AppFocusService {}
impl Service for AppFocusService {
	type Context = NoneT;
	type Handler = fn(focus::Event);
	type RawHandler = fn() -> (unsafe extern "C" fn(bool), unsafe extern "C" fn(bool));
	type SubscribeArg = focus::SubscribeTo;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering AppFocusService handler;");
		let handlers = f();
		let handlers = sys::AppFocusHandlers {
			will_focus: a.contains(focus::SubscribeTo::WILL).then_some(handlers.0),
			did_focus:  a.contains(focus::SubscribeTo::DID).then_some(handlers.1),
		};
		unsafe { sys::app_focus_service_subscribe_handlers(handlers) }
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! AppFocusService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_AppFocusService")]
			static GUARD: u8 = 0;
		};
		fn $r() -> (unsafe extern "C" fn(bool), unsafe extern "C" fn(bool)) {
			unsafe extern "C" fn __gneiss_app_focus_will(gaining: bool) {
				if gaining {
					$f(::gneiss::focus::Event::Gaining)
				} else {
					$f(::gneiss::focus::Event::Losing)
				}
			}
			unsafe extern "C" fn __gneiss_app_focus_did(gained: bool) {
				if gained {
					$f(::gneiss::focus::Event::Gained)
				} else {
					$f(::gneiss::focus::Event::Lost)
				}
			}
			(__gneiss_app_focus_will, __gneiss_app_focus_did)
		}
	};
}

/// Called on connection state changes for the phone app and/or `PebbleKit` companion
pub struct ConnectionService;
impl Sealed for ConnectionService {}
impl Service for ConnectionService {
	type Context = NoneT;
	type Handler = fn(connection::Event);
	type RawHandler = fn() -> (unsafe extern "C" fn(bool), unsafe extern "C" fn(bool));
	type SubscribeArg = connection::SubscribeTo;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		crate::log::debug!("Registering ConnectionService handler;");
		let handlers = f();
		let handlers = sys::ConnectionHandlers {
			pebble_app_connection_handler: a
				.contains(connection::SubscribeTo::APP)
				.then_some(handlers.0),
			pebblekit_connection_handler:  a
				.contains(connection::SubscribeTo::PEBBLEKIT)
				.then_some(handlers.1),
		};
		unsafe { sys::connection_service_subscribe(handlers) }
	}
}
impl Peekable for ConnectionService {
	type Output = connection::State;
	type PeekArgs = ();

	/// Return the current [`connection::State`] of the watch
	fn peek(_arg: Self::PeekArgs) -> Self::Output {
		unsafe {
			connection::State {
				app:       sys::connection_service_peek_pebble_app_connection(),
				pebblekit: sys::connection_service_peek_pebblekit_connection(),
			}
		}
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! ConnectionService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_ConnectionService")]
			static GUARD: u8 = 0;
		};
		fn $r() -> (unsafe extern "C" fn(bool), unsafe extern "C" fn(bool)) {
			unsafe extern "C" fn __gneiss_connection_app(connected: bool) {
				if connected {
					$f(::gneiss::connection::Event::AppConnected)
				} else {
					$f(::gneiss::connection::Event::AppDisconnected)
				}
			}
			unsafe extern "C" fn __gneiss_connection_pebblekit(connected: bool) {
				if connected {
					$f(::gneiss::connection::Event::PebbleKitConnected)
				} else {
					$f(::gneiss::connection::Event::PebbleKitDisconnected)
				}
			}
			(__gneiss_connection_app, __gneiss_connection_pebblekit)
		}
	};
}

/// Called when a wakeup this app scheduled fires (`sys::wakeup_schedule`), with its id and the
/// cookie it was scheduled with.
pub struct WakeupService;
impl Sealed for WakeupService {}
impl Service for WakeupService {
	type Context = NoneT;
	type Handler = fn(wakeup::WakeupId, wakeup::Cookie);
	type RawHandler = unsafe extern "C" fn(sys::WakeupId, i32);
	type SubscribeArg = ();

	fn subscribe(f: Self::RawHandler, _a: Self::SubscribeArg) {
		crate::log::debug!("Registering WakeupService handler;");
		unsafe { sys::wakeup_service_subscribe(Some(f)) }
	}
}
#[macro_export]
#[doc(hidden)]
#[allow(clippy::crate_in_macro_def)]
macro_rules! WakeupService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_WakeupService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(id: $crate::sys::WakeupId, reason: i32) {
			$f(
				::gneiss::wakeup::WakeupId(id),
				::gneiss::wakeup::Cookie(reason),
			)
		}

		#[::gneiss::linkme::distributed_slice(crate::GNEISS_LAUNCH_HOOKS)]
		#[linkme(crate = ::gneiss::linkme)]
		fn __gneiss_wakeup_launch() {
			unsafe {
				let (mut id, mut cookie) = (0, 0);
				// False when not a wakeup, writes (id, cookie) when true.
				if ::gneiss::sys::wakeup_get_launch_event(&mut id, &mut cookie) {
					$f(
						::gneiss::wakeup::WakeupId(id),
						::gneiss::wakeup::Cookie(cookie),
					)
				}
			}
		}
	};
}

/// Called with each touch event: a finger landing, moving, or lifting. `T` is optional
/// [context](self#context).
///
/// Only on watches with a touch panel (`cfg(pbl_touch)`).
#[cfg(pbl_touch)]
pub struct TouchService<T = NoneT>(PhantomData<T>);
#[cfg(pbl_touch)]
impl<T> Sealed for TouchService<T> {}
#[cfg(pbl_touch)]
impl<T> Service for TouchService<T> {
	type Context = T;
	type Handler = for<'a> fn(touch::Event, Option<&'a mut T>);
	type RawHandler = unsafe extern "C" fn(*const sys::TouchEvent, *mut core::ffi::c_void);
	type SubscribeArg = Option<T>;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		extern crate alloc;
		use alloc::boxed::Box;
		// Leak to the watch's heap providing 'static independent of app's choice of alloc
		let ctx: *mut Self::Context = a
			.map(|c| Box::new_in(c, crate::alloc::PebbleAlloc))
			.map_or(core::ptr::null_mut(), |b| Box::into_raw_with_allocator(b).0);
		Self::__subscribe(f, ctx);
	}
}
#[cfg(pbl_touch)]
impl<T> TouchService<T> {
	fn __subscribe(f: <Self as Service>::RawHandler, ctx: *mut T) {
		crate::log::debug!("Registering TouchService handler;");
		// SAFETY: It's always safe to cast to c_void.
		unsafe { sys::touch_service_subscribe(Some(f), ctx.cast()) }
	}
}
#[cfg(pbl_touch)]
#[macro_export]
#[doc(hidden)]
macro_rules! TouchService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_TouchService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(event: *const $crate::sys::TouchEvent, ctx: *mut core::ffi::c_void) {
			// SAFETY: *const event is valid for the duration of the callback, after which it is
			// cleaned up - reading now is valid and correct, passing as a ref could escape. We know
			// ctx is valid because *we* leaked it.
			$f(
				::gneiss::touch::Event::from(unsafe { event.read() }),
				ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
					.as_mut(),
			)
		}
	};
}

/// Called when the health tracker has new data: activity, sleep, heart rate. `T` is optional
/// [context](self#context).
pub struct HealthService<T>(PhantomData<T>);
impl<T> Sealed for HealthService<T> {}
impl<T> Service for HealthService<T> {
	type Context = T;
	type Handler = for<'a> fn(health::Event, Option<&'a mut T>);
	type RawHandler = unsafe extern "C" fn(u8, *mut core::ffi::c_void);
	type SubscribeArg = Option<T>;

	fn subscribe(f: Self::RawHandler, a: Self::SubscribeArg) {
		extern crate alloc;
		use alloc::boxed::Box;
		// Leak to the watch's heap providing 'static independent of app's choice of alloc
		let ctx: *mut Self::Context = a
			.map(|c| Box::new_in(c, crate::alloc::PebbleAlloc))
			.map_or(core::ptr::null_mut(), |b| Box::into_raw_with_allocator(b).0);
		Self::__subscribe(f, ctx);
	}
}

impl<T> HealthService<T> {
	fn __subscribe(f: <Self as Service>::RawHandler, ctx: *mut T) {
		crate::log::debug!("Registering HealthService handler;");
		// SAFETY: It's always safe to cast to c_void.
		if !unsafe { sys::health_service_events_subscribe(Some(f), ctx.cast()) } {
			crate::log::error!("Health service subscribe failed, not enough heap.");
		}
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! HealthService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_HealthService")]
			static GUARD: u8 = 0;
		};
		unsafe extern "C" fn $r(event: u8, ctx: *mut core::ffi::c_void) {
			// SAFETY: We leaked ctx, we know it's valid
			$f(
				::gneiss::health::Event::from(event),
				ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
					.as_mut(),
			)
		}
	};
}

/// The inbox and outbox callbacks [`AppMessageService`] registers, built by
/// `#[gneiss::service]`.
pub struct AppMessageHandlers {
	pub received: sys::AppMessageInboxReceived,
	pub dropped:  sys::AppMessageInboxDropped,
	pub sent:     sys::AppMessageOutboxSent,
	pub failed:   sys::AppMessageOutboxFailed,
}

/// Called with AppMessage traffic: messages received, dropped, sent, or failed to send. See
/// [`crate::link`]. Takes the events to deliver ([`link::SubscribeTo`]), the inbox and outbox
/// sizes ([`link::BufferSizes`]), and optional [context](self#context).
pub struct AppMessageService<T>(PhantomData<T>);
impl<T> Sealed for AppMessageService<T> {}
impl<T> Service for AppMessageService<T> {
	type Context = T;
	type Handler = for<'a> fn(link::AppMessageEvent<'a>, Option<&'a mut T>);
	type RawHandler = fn() -> AppMessageHandlers;
	type SubscribeArg = (link::SubscribeTo, link::BufferSizes, Option<T>);

	fn subscribe(f: Self::RawHandler, mut a: Self::SubscribeArg) {
		extern crate alloc;
		use alloc::boxed::Box;
		// Leak to the watch's heap providing 'static independent of app's choice of alloc
		let ctx: *mut Self::Context =
			a.2.take()
				.map(|c| Box::new_in(c, crate::alloc::PebbleAlloc))
				.map_or(core::ptr::null_mut(), |b| Box::into_raw_with_allocator(b).0);
		Self::__subscribe(f, &a, ctx);
	}
}
impl<T> AppMessageService<T> {
	fn __subscribe(
		f: <Self as Service>::RawHandler,
		a: &<Self as Service>::SubscribeArg,
		ctx: *mut T,
	) {
		crate::log::debug!("Registering AppMessageService handler;");
		let mut handlers = f();
		// SAFETY: ctx is null or a pointer we leaked; the handlers are valid fn pointers.
		unsafe {
			sys::app_message_set_context(ctx.cast());
			sys::app_message_register_inbox_received(
				handlers
					.received
					.take_if(|_| a.0.contains(link::SubscribeTo::RECEIVED)),
			);
			sys::app_message_register_inbox_dropped(
				handlers
					.dropped
					.take_if(|_| a.0.contains(link::SubscribeTo::DROPPED)),
			);
			sys::app_message_register_outbox_sent(
				handlers
					.sent
					.take_if(|_| a.0.contains(link::SubscribeTo::SENT)),
			);
			sys::app_message_register_outbox_failed(
				handlers
					.failed
					.take_if(|_| a.0.contains(link::SubscribeTo::FAILED)),
			);
			let buf_sizes = a.1;
			let in_max = sys::app_message_inbox_size_maximum();
			let out_max = sys::app_message_outbox_size_maximum();

			let inbox_size = buf_sizes
				.inbox
				.unwrap_or(in_max)
				.max(sys::APP_MESSAGE_INBOX_SIZE_MINIMUM)
				.min(in_max);
			let outbox_size = buf_sizes
				.outbox
				.unwrap_or(out_max)
				.max(sys::APP_MESSAGE_OUTBOX_SIZE_MINIMUM)
				.min(out_max);

			#[allow(
				unused_unsafe,
				reason = "log macro emits its own `unsafe`, trips if called in an unsafe block"
			)]
			if sys::app_message_open(inbox_size, outbox_size) != sys::AppMessageResult_APP_MSG_OK {
				crate::log::error!("AppMessage open failed");
			}
		}
	}
}
#[macro_export]
#[doc(hidden)]
macro_rules! AppMessageService_raw {
	(handler: $f: ident, raw_handler: $r: ident, service: $s: ty) => {
		const _: () = {
			#[unsafe(export_name = "__gneiss_slot_AppMessageService")]
			static GUARD: u8 = 0;
		};
		fn $r() -> AppMessageHandlers {
			// SAFETY: *DictionaryIterator is INVALID past the end of this callback, pass a short
			// borrow to the gneiss handler, not the raw ptr
			unsafe extern "C" fn __gneiss_appmessage_receive(
				dict: *mut $crate::sys::DictionaryIterator,
				ctx: *mut core::ffi::c_void,
			) {
				$f(
					::gneiss::link::AppMessageEvent::Received {
						inbox: unsafe { ::gneiss::dict::DictHandle::from_raw(dict) },
					},
					ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
						.as_mut(),
				)
			}
			unsafe extern "C" fn __gneiss_appmessage_dropped(
				reason: $crate::sys::AppMessageResult,
				ctx: *mut core::ffi::c_void,
			) {
				$f(
					::gneiss::link::AppMessageEvent::Dropped {
						reason: reason.into(),
					},
					ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
						.as_mut(),
				)
			}
			unsafe extern "C" fn __gneiss_appmessage_sent(
				dict: *mut $crate::sys::DictionaryIterator,
				ctx: *mut core::ffi::c_void,
			) {
				$f(
					::gneiss::link::AppMessageEvent::Sent {
						outbox: unsafe { ::gneiss::dict::DictHandle::from_raw(dict) },
					},
					ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
						.as_mut(),
				)
			}
			unsafe extern "C" fn __gneiss_appmessage_failed(
				dict: *mut $crate::sys::DictionaryIterator,
				reason: $crate::sys::AppMessageResult,
				ctx: *mut core::ffi::c_void,
			) {
				$f(
					::gneiss::link::AppMessageEvent::Failed {
						outbox: unsafe { ::gneiss::dict::DictHandle::from_raw(dict) },
						reason: reason.into(),
					},
					ctx.cast::<<$s as ::gneiss::service::Service>::Context>()
						.as_mut(),
				)
			}
			AppMessageHandlers {
				received: Some(__gneiss_appmessage_receive),
				dropped:  Some(__gneiss_appmessage_dropped),
				sent:     Some(__gneiss_appmessage_sent),
				failed:   Some(__gneiss_appmessage_failed),
			}
		}
	};
}
/// Private trait bound means external modules cannot implement Service, but can still see it.
mod seal {
	pub trait Sealed {}
}
use seal::Sealed;