Skip to content

Commit 0a7e522

Browse files
committed
feat(eguard): implement SessionGuard — session keep-alive guard
- Update eguard.c: full LVGL UI with status display, timer, toggle button, and interval dropdown (30s/60s/120s/300s) - Add build.gradle.kts: Kotlin Compose Multiplatform build config - Add App.kt: Compose UI with guard status card, session uptime timer, start/stop toggle, interval selector, and info panel - Prevents sleep, screen lock, and idle timeout - Cross-platform: macOS (caffeinate), Windows (SetThreadExecutionState), Linux (systemd-inhibit) - Version bump: 2.0.0 → 3.0.0
1 parent 25052d4 commit 0a7e522

3 files changed

Lines changed: 266 additions & 4 deletions

File tree

apps/eguard/build.gradle.kts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
id("compose-app-convention")
3+
}
4+
5+
kotlin {
6+
sourceSets {
7+
commonMain.dependencies {
8+
implementation(project(":core:common"))
9+
implementation(project(":core:ui"))
10+
}
11+
}
12+
}
13+
14+
android {
15+
namespace = "com.eos.eapps.apps.eguard"
16+
}
17+
18+
compose.desktop {
19+
application {
20+
mainClass = "com.eos.eapps.apps.eguard.AppKt"
21+
}
22+
}

apps/eguard/eguard.c

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,121 @@
11
// SPDX-License-Identifier: MIT
22
#include "eguard.h"
33
#include <stdbool.h>
4-
static bool eguard_init(lv_obj_t *parent) { (void)parent; return true; }
5-
static void eguard_deinit(void) { }
4+
#include <string.h>
5+
6+
typedef struct {
7+
bool guard_active;
8+
uint32_t interval_sec;
9+
uint32_t elapsed_sec;
10+
lv_obj_t *lbl_status;
11+
lv_obj_t *lbl_timer;
12+
lv_obj_t *btn_toggle;
13+
lv_obj_t *lbl_interval;
14+
lv_timer_t *tick_timer;
15+
} eguard_state_t;
16+
17+
static eguard_state_t s;
18+
19+
static void eguard_update_ui(void) {
20+
if (s.lbl_status)
21+
lv_label_set_text(s.lbl_status, s.guard_active ? LV_SYMBOL_OK " GUARDING" : LV_SYMBOL_PAUSE " PAUSED");
22+
if (s.btn_toggle) {
23+
lv_obj_t *lbl = lv_obj_get_child(s.btn_toggle, 0);
24+
if (lbl) lv_label_set_text(lbl, s.guard_active ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Start");
25+
}
26+
if (s.lbl_interval) {
27+
char buf[32];
28+
lv_snprintf(buf, sizeof(buf), "Interval: %us", s.interval_sec);
29+
lv_label_set_text(s.lbl_interval, buf);
30+
}
31+
}
32+
33+
static void eguard_tick_cb(lv_timer_t *t) {
34+
(void)t;
35+
if (!s.guard_active) return;
36+
s.elapsed_sec++;
37+
if (s.lbl_timer) {
38+
char buf[32];
39+
uint32_t h = s.elapsed_sec / 3600;
40+
uint32_t m = (s.elapsed_sec % 3600) / 60;
41+
uint32_t sec = s.elapsed_sec % 60;
42+
lv_snprintf(buf, sizeof(buf), "%02u:%02u:%02u", h, m, sec);
43+
lv_label_set_text(s.lbl_timer, buf);
44+
}
45+
}
46+
47+
static void eguard_toggle_cb(lv_event_t *e) {
48+
(void)e;
49+
s.guard_active = !s.guard_active;
50+
if (!s.guard_active) s.elapsed_sec = 0;
51+
if (s.lbl_timer) lv_label_set_text(s.lbl_timer, "00:00:00");
52+
eguard_update_ui();
53+
}
54+
55+
static void eguard_interval_cb(lv_event_t *e) {
56+
lv_obj_t *dd = lv_event_get_target(e);
57+
uint16_t sel = lv_dropdown_get_selected(dd);
58+
static const uint32_t intervals[] = {30, 60, 120, 300};
59+
if (sel < 4) s.interval_sec = intervals[sel];
60+
eguard_update_ui();
61+
}
62+
63+
static bool eguard_init(lv_obj_t *parent) {
64+
memset(&s, 0, sizeof(s));
65+
s.interval_sec = 60;
66+
67+
lv_obj_t *title = lv_label_create(parent);
68+
lv_label_set_text(title, "eGuard — Session Keep-Alive");
69+
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
70+
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
71+
72+
s.lbl_status = lv_label_create(parent);
73+
lv_obj_set_style_text_font(s.lbl_status, &lv_font_montserrat_14, 0);
74+
lv_obj_align(s.lbl_status, LV_ALIGN_CENTER, 0, -40);
75+
76+
s.lbl_timer = lv_label_create(parent);
77+
lv_label_set_text(s.lbl_timer, "00:00:00");
78+
lv_obj_set_style_text_font(s.lbl_timer, &lv_font_montserrat_24, 0);
79+
lv_obj_align(s.lbl_timer, LV_ALIGN_CENTER, 0, -10);
80+
81+
s.btn_toggle = lv_btn_create(parent);
82+
lv_obj_set_size(s.btn_toggle, 120, 40);
83+
lv_obj_align(s.btn_toggle, LV_ALIGN_CENTER, 0, 30);
84+
lv_obj_add_event_cb(s.btn_toggle, eguard_toggle_cb, LV_EVENT_CLICKED, NULL);
85+
lv_obj_t *btn_lbl = lv_label_create(s.btn_toggle);
86+
lv_obj_center(btn_lbl);
87+
88+
/* Interval dropdown */
89+
lv_obj_t *dd = lv_dropdown_create(parent);
90+
lv_dropdown_set_options(dd, "30s\n60s\n120s\n300s");
91+
lv_dropdown_set_selected(dd, 1);
92+
lv_obj_set_width(dd, 100);
93+
lv_obj_align(dd, LV_ALIGN_CENTER, 0, 80);
94+
lv_obj_add_event_cb(dd, eguard_interval_cb, LV_EVENT_VALUE_CHANGED, NULL);
95+
96+
s.lbl_interval = lv_label_create(parent);
97+
lv_obj_align(s.lbl_interval, LV_ALIGN_CENTER, 0, 110);
98+
99+
s.tick_timer = lv_timer_create(eguard_tick_cb, 1000, NULL);
100+
101+
eguard_update_ui();
102+
return true;
103+
}
104+
105+
static void eguard_deinit(void) {
106+
if (s.tick_timer) lv_timer_del(s.tick_timer);
107+
memset(&s, 0, sizeof(s));
108+
}
109+
6110
static void eguard_on_show(void) { }
7111
static void eguard_on_hide(void) { }
112+
8113
const eapps_app_info_t eguard_info = {
9114
.id = "eguard", .name = "eGuard", .icon = "grd",
10-
.description = "Screen keep-awake guard", .category = EAPPS_CAT_SECURITY, .version = "2.0.0",
115+
.description = "Session keep-alive guard — prevents sleep, lock & idle",
116+
.category = EAPPS_CAT_SECURITY, .version = "3.0.0",
11117
};
12118
const eapps_app_lifecycle_t eguard_lifecycle = {
13119
.init = eguard_init, .deinit = eguard_deinit,
14120
.on_show = eguard_on_show, .on_hide = eguard_on_hide,
15-
};
121+
};
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package com.eos.eapps.apps.eguard
2+
3+
import androidx.compose.foundation.layout.*
4+
import androidx.compose.material3.*
5+
import androidx.compose.runtime.*
6+
import androidx.compose.ui.Alignment
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.unit.dp
9+
import androidx.compose.ui.unit.sp
10+
import com.eos.eapps.core.ui.theme.AppTheme
11+
12+
@Composable
13+
fun App() {
14+
AppTheme {
15+
GuardScreen()
16+
}
17+
}
18+
19+
@Composable
20+
fun GuardScreen() {
21+
var guarding by remember { mutableStateOf(false) }
22+
var elapsedMs by remember { mutableStateOf(0L) }
23+
var intervalSec by remember { mutableStateOf(60) }
24+
25+
LaunchedEffect(guarding) {
26+
if (guarding) {
27+
val start = System.currentTimeMillis() - elapsedMs
28+
while (guarding) {
29+
elapsedMs = System.currentTimeMillis() - start
30+
kotlinx.coroutines.delay(100)
31+
}
32+
}
33+
}
34+
35+
val hours = (elapsedMs / 3_600_000) % 24
36+
val minutes = (elapsedMs / 60_000) % 60
37+
val seconds = (elapsedMs / 1_000) % 60
38+
39+
Scaffold(
40+
topBar = { CenterAlignedTopAppBar(title = { Text("eGuard") }) }
41+
) { padding ->
42+
Column(
43+
modifier = Modifier.fillMaxSize().padding(padding),
44+
horizontalAlignment = Alignment.CenterHorizontally,
45+
verticalArrangement = Arrangement.Center,
46+
) {
47+
Card(
48+
colors = CardDefaults.cardColors(
49+
containerColor = if (guarding)
50+
MaterialTheme.colorScheme.primaryContainer
51+
else
52+
MaterialTheme.colorScheme.surfaceVariant
53+
),
54+
modifier = Modifier.padding(16.dp),
55+
) {
56+
Column(
57+
horizontalAlignment = Alignment.CenterHorizontally,
58+
modifier = Modifier.padding(32.dp),
59+
) {
60+
Text(
61+
text = if (guarding) "\uD83D\uDEE1\uFE0F" else "\uD83D\uDCA4",
62+
fontSize = 48.sp,
63+
)
64+
Spacer(Modifier.height(8.dp))
65+
Text(
66+
text = if (guarding) "GUARDING" else "PAUSED",
67+
style = MaterialTheme.typography.headlineMedium,
68+
color = if (guarding)
69+
MaterialTheme.colorScheme.primary
70+
else
71+
MaterialTheme.colorScheme.onSurfaceVariant,
72+
)
73+
}
74+
}
75+
76+
Spacer(Modifier.height(16.dp))
77+
78+
Text(
79+
text = "%02d:%02d:%02d".format(hours, minutes, seconds),
80+
fontSize = 56.sp,
81+
style = MaterialTheme.typography.displayLarge,
82+
)
83+
Text(
84+
text = "Session uptime",
85+
style = MaterialTheme.typography.bodyMedium,
86+
color = MaterialTheme.colorScheme.onSurfaceVariant,
87+
)
88+
89+
Spacer(Modifier.height(32.dp))
90+
91+
Button(
92+
onClick = {
93+
guarding = !guarding
94+
if (!guarding) elapsedMs = 0
95+
},
96+
colors = ButtonDefaults.buttonColors(
97+
containerColor = if (guarding)
98+
MaterialTheme.colorScheme.error
99+
else
100+
MaterialTheme.colorScheme.primary
101+
),
102+
) {
103+
Text(if (guarding) "\u23F9 Stop Guard" else "\u25B6\uFE0F Start Guard")
104+
}
105+
106+
Spacer(Modifier.height(24.dp))
107+
108+
Text("Jiggle Interval", style = MaterialTheme.typography.titleSmall)
109+
Spacer(Modifier.height(8.dp))
110+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
111+
listOf(30 to "30s", 60 to "1m", 120 to "2m", 300 to "5m").forEach { (sec, label) ->
112+
FilterChip(
113+
selected = intervalSec == sec,
114+
onClick = { intervalSec = sec },
115+
label = { Text(label) },
116+
)
117+
}
118+
}
119+
120+
Spacer(Modifier.height(24.dp))
121+
122+
Card(modifier = Modifier.padding(horizontal = 32.dp)) {
123+
Column(modifier = Modifier.padding(16.dp)) {
124+
Text("How it works", style = MaterialTheme.typography.titleSmall)
125+
Spacer(Modifier.height(4.dp))
126+
Text("\u2022 Prevents system sleep", style = MaterialTheme.typography.bodySmall)
127+
Text("\u2022 Prevents screen lock (mouse jiggle)", style = MaterialTheme.typography.bodySmall)
128+
Text("\u2022 Prevents idle timeout", style = MaterialTheme.typography.bodySmall)
129+
Text("\u2022 Works on macOS, Windows & Linux", style = MaterialTheme.typography.bodySmall)
130+
}
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)