make neko have somewhat of a sleep schedule

This commit is contained in:
Virt 2025-03-09 00:11:06 +01:00
commit 0917c3f9aa
3 changed files with 148 additions and 111 deletions

17
README
View file

@ -1,5 +1,20 @@
wayneko - Neko on Wayland
wayneko - Neko on Wayland but with phases
Display an animated neko cat on the bottom of an output. Requires the Wayland
server to implement zwlr-layer-shell-unstable-v1. All code is licensed under
the GPLv3. The neko bitmaps were taken from public domain.
This fork changes the behaviour of neko to be more calm and less random. Neko
will now sleep and be awake in phases, whose duration is randomized but has
a minimum and maximum duration which can be changed. This means it can no
longer happen that Neko wakes up just to go to sleep immediately again and
makes Neko being awake something you can look forward to.
Refer to the man page or `--help` command to see how to use this fork and which
options are new or no longer available.
The following features are missing from this fork for now:
- Neko does not yet behave differently at night.
- Idle detection is working but implemented sloppily.
- Pointer following is currently removed.
- Waking up Neko with a signal is something that should be possible.

View file

@ -2,7 +2,7 @@
.
.SH NAME
.P
wayneko \- Neko on Wayland
wayneko \- Neko on Wayland but with phases
.
.
.SH SYNOPSIS
@ -17,10 +17,9 @@ wayneko \- Neko on Wayland
.OP \-\-outline\-colour 0xRRGGBB[AA]
.OP \-\-type neko|inu|random
.OP \-\-idle-sleep seconds
.OP \-\-sleepiness num
.OP \-\-sleepiness-night num
.OP \-\-phase-sleep seconds-seconds
.OP \-\-phase-awake seconds-seconds
.OP \-\-layer background|bottom|top|overlay
.OP \-\-follow\-pointer true|false
.OP \-\-survive\-close
.YS
.
@ -70,18 +69,19 @@ idle.
.RE
.
.P
\fB\-\-sleepiness\fR \fInum\fR
\fB\-\-phase-awake\fR \fIseconds-seconds\fR
.RS
Set neko's sleepiness as an integer (greater than 0). Higher values make neko
more sleepy. Defaults to 4.
Set the duration of how long neko will be awake at a time. Takes a minimum
and a maximum amount of seconds, separated by a dash. Actual duration will
be drawn uniformly at random between the two values. Defaults to 120-600.
.RE
.
.P
\fB\-\-sleepiness-night\fR \fInum\fR
\fB\-\-phase-sleep\fR \fIseconds-seconds\fR
.RS
Set neko's sleepiness at night as an integer (greater than 0). Higher values
make neko more sleepy. This setting acts as an additional sleepiness on top of
the normal sleepiness. Defaults to 5.
Set the duration of how long neko will sleep at a time. Takes a minimum
and a maximum amount of seconds, separated by a dash. Actual duration will
be drawn uniformly at random between the two values. Defaults to 450-1800.
.RE
.
.P
@ -95,12 +95,6 @@ Background may conflict with wallpaper clients.
.RE
.
.P
\fB\-\-follow\-pointer\fR \fBtrue\fR|\fBfalse\fR
.RS
Set whether neko follows the pointer when it approaches the bottom of the output
.RE
.
.P
\fB\-\-survive\-close\fR
.RS
If this flag is used, wayneko will recreate the surface after it has been closed.
@ -112,10 +106,15 @@ close the surface for a different reason.
.
.SH AUTHOR
.P
Code by
Original code by
.MT leonhenrik.plickat@stud.uni-goettingen.de
Leon Henrik Plickat
.ME .
.P
Fork with phases made by
.MT virtinstance@gmail.com
Virt
.ME .
.
.P
Neko bitmaps taken from public domain.

197
wayneko.c
View file

@ -33,10 +33,9 @@ const char usage[] =
" --outline-colour 0xRRGGBB[AA]\n"
" --type neko|inu|random\n"
" --idle-sleep num\n"
" --sleepiness num\n"
" --sleepiness-night num\n"
" --phase-sleep num-num\n"
" --phase-awake num-num\n"
" --layer background|bottom|top|overlay\n"
" --follow-pointer true|false\n"
" --survive-close\n"
"\n";
@ -73,19 +72,31 @@ enum Neko
NEKO_RUN_LEFT_2,
};
const uint16_t animation_timeout = 200;
const uint16_t animation_timeout = 200; /* milliseconds */
size_t animation_ticks_until_next_frame = 10;
enum Neko current_neko = NEKO_STARE;
enum Type type = NEKO;
bool follow_pointer = true;
bool follow_pointer = false; // TODO: implement this again
bool recreate_surface_on_close = false;
enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct ext_idle_notifier_v1 *idle_notifier = NULL;
uint32_t neko_idle_timeout_ms = 180000; /* 3 minutes. */
int sleepiness = 4;
int sleepiness_night = 5;
enum Phase {
PHASE_AWAKE,
PHASE_SLEEP
};
typedef enum Phase Phase;
/* wayneko will wake up at startup */
Phase current_phase = PHASE_SLEEP;
size_t ticks_until_phase_change = 0;
uint32_t phase_sleep_min = 450;
uint32_t phase_sleep_max = 1800;
uint32_t phase_awake_min = 120;
uint32_t phase_awake_max = 600;
struct Seat
{
@ -167,15 +178,15 @@ static void handle_error (int signum)
{
const char *msg =
"\n"
"🐈 🐈 🐈\n"
"┌──────────────────────────────────────────┐\n"
"│ │\n"
"│ wayneko has crashed. \n"
"│ wayneko has crashed :(\n"
"│ │\n"
"│ This is likely a bug, so please │\n"
"│ report this to the mailing list. │\n"
"│ This is likely a bug in the fork │\n"
"│ you are running, so feel free to │\n"
"│ report your stuff there. │\n"
"│ │\n"
"~leon_plickat/public-inbox@lists.sr.ht\n"
" https://copeberg.org/virt/wayneko \n"
"│ │\n"
"└──────────────────────────────────────────┘\n"
"\n";
@ -823,11 +834,8 @@ static bool animtation_neko_wants_sleep (void)
}
/** Returns true if new frame is needed. */
static bool animation_next_state_with_idle (void)
{
/* If no one is there (system is idle), neko gets bored and will sleep. */
switch (current_neko)
{
static bool animation_next_state_sleep(void) {
switch (current_neko) {
case NEKO_SLEEP_1:
case NEKO_SLEEP_2:
case NEKO_YAWN:
@ -919,39 +927,9 @@ static bool animation_next_state_with_hotspot (uint32_t x)
}
/** Returns true if new frame is needed. */
static bool animation_next_state_normal (void)
{
/* Sleep at night, but with a small chance to wake up and do something.
* If the neko is already awake, slightly higher chance to stay awake.
*/
const bool neko_is_sleeping = current_neko == NEKO_SLEEP_1 || current_neko == NEKO_SLEEP_2;
if ( animtation_neko_wants_sleep() && (( neko_is_sleeping && rand() % sleepiness_night != 0 ) || ( !neko_is_sleeping && rand() % 2 != 0 )) )
{
switch (current_neko)
{
case NEKO_RUN_RIGHT_1:
case NEKO_RUN_RIGHT_2:
case NEKO_RUN_LEFT_1:
case NEKO_RUN_LEFT_2:
animation_neko_do_stare(true);
return true;
case NEKO_SLEEP_1:
case NEKO_SLEEP_2:
animation_neko_do_sleep();
return true;
default:
if (rand() % 3 == 0)
animation_neko_do_yawn();
else
animation_neko_do_stare(false);
return true;
}
}
switch (current_neko)
static bool animation_next_state_awake (void)
{
switch (current_neko) {
case NEKO_STARE:
switch (rand() % 24)
{
@ -960,9 +938,6 @@ static bool animation_next_state_normal (void)
break;
case 1:
animation_neko_do_sleep();
break;
case 2:
animation_neko_do_yawn();
break;
@ -1014,15 +989,10 @@ static bool animation_next_state_normal (void)
case NEKO_SLEEP_1:
case NEKO_SLEEP_2:
if ( rand() % sleepiness == 0 )
{
if ( rand() % 2 == 0 )
animation_neko_do_shock();
else
animation_neko_do_stare(false);
}
else
animation_neko_do_sleep();
animation_neko_do_yawn();
return true;
case NEKO_SCRATCH_1:
@ -1041,12 +1011,6 @@ static bool animation_next_state_normal (void)
return true;
case NEKO_YAWN:
if ( rand() % 2 == 0 )
animation_neko_do_stare(false);
else
animation_neko_do_sleep();
return true;
case NEKO_SHOCK:
animation_neko_do_stare(true);
return true;
@ -1056,27 +1020,61 @@ static bool animation_next_state_normal (void)
return false;
}
/** called once per tick, checks for a phase change, and changes the phase if required */
static void check_phase(void) {
if (ticks_until_phase_change > 0)
ticks_until_phase_change--;
else {
// change phase
uint32_t min = 0, max = 0;
if (current_phase == PHASE_AWAKE) {
current_phase = PHASE_SLEEP;
min = phase_sleep_min;
max = phase_sleep_max;
} else {
current_phase = PHASE_AWAKE;
min = phase_awake_min;
max = phase_awake_max;
}
uint32_t phase_change_seconds = min + (((uint32_t) rand()) % (max - min));
ticks_until_phase_change = phase_change_seconds * 1000 / animation_timeout;
}
}
/** Returns true if new frame is needed. */
static bool animation_next_state (void)
{
check_phase();
if ( animation_ticks_until_next_frame > 0 )
{
animation_ticks_until_next_frame--;
return false;
}
bool idle = false;
struct Seat *seat;
wl_list_for_each(seat, &seats, link)
{
if (seat->currently_idle)
return animation_next_state_with_idle();
wl_list_for_each(seat, &seats, link) {
if (seat->currently_idle) idle = true;
}
wl_list_for_each(seat, &seats, link)
{
if (false) {
wl_list_for_each(seat, &seats, link) {
if (seat->on_surface)
return animation_next_state_with_hotspot(seat->surface_x);
}
return animation_next_state_normal();
}
Phase phase = current_phase;
if (idle) phase = PHASE_SLEEP;
if (phase == PHASE_SLEEP)
return animation_next_state_sleep();
else
return animation_next_state_awake();
}
/*************
@ -1397,6 +1395,9 @@ int main (int argc, char *argv[])
}
else if ( strcmp(argv[i], "--follow-pointer") == 0 )
{
fprintf(stderr, "ERROR: Functionality for flag '--follow-pointer' is not yet implemented in this fork.\n");
return EXIT_FAILURE;
const char *t = get_argument(argc, argv, &i);
if ( t == NULL )
return EXIT_FAILURE;
@ -1435,27 +1436,49 @@ int main (int argc, char *argv[])
return EXIT_FAILURE;
}
}
else if ( strcmp(argv[i], "--sleepiness") == 0 )
else if ( strcmp(argv[i], "--awake-phase") == 0 )
{
const char *a = get_argument(argc, argv, &i);
int i = atoi(a);
if (i != 0)
sleepiness = abs(i) + 1;
else
{
fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--sleepiness'.\n", a);
char* a = get_argument(argc, argv, &i);
char* b = a;
while (*b != '-' && *b != 0) b++;
if (*b == 0) {
fprintf(stderr, "ERROR: Values in '%s' for flag '--awake-phase' are must be split by a dash.\n", a);
return EXIT_FAILURE;
}
*b++ = 0;
int min = atoi(a);
int max = atoi(b);
if (min != 0 && max != 0 && max != min) {
phase_awake_min = (uint32_t) abs(min);
phase_awake_max = (uint32_t) abs(max);
} else {
fprintf(stderr, "ERROR: Invalid numbers in argument '%s-%s' for flag '--awake-phase'.\n", a, b);
return EXIT_FAILURE;
}
}
else if ( strcmp(argv[i], "--sleepiness-night") == 0 )
else if ( strcmp(argv[i], "--sleep-phase") == 0 )
{
const char *a = get_argument(argc, argv, &i);
int i = atoi(a);
if (i != 0)
sleepiness_night = abs(i) + 1;
else
{
fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--sleepiness-night'.\n", a);
char* a = get_argument(argc, argv, &i);
char* b = a;
while (*b != '-' && *b != 0) b++;
if (*b == 0) {
fprintf(stderr, "ERROR: Values in '%s' for flag '--sleep-phase' are must be split by a dash.\n", a);
return EXIT_FAILURE;
}
*b++ = 0;
int min = atoi(a);
int max = atoi(b);
if (min != 0 && max != 0 && max != min) {
phase_sleep_min = (uint32_t) abs(min);
phase_sleep_max = (uint32_t) abs(max);
} else {
fprintf(stderr, "ERROR: Invalid numbers in argument '%s-%s' for flag '--sleep-phase'.\n", a, b);
return EXIT_FAILURE;
}
}