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

197
wayneko.c
View file

@ -33,10 +33,9 @@ const char usage[] =
" --outline-colour 0xRRGGBB[AA]\n" " --outline-colour 0xRRGGBB[AA]\n"
" --type neko|inu|random\n" " --type neko|inu|random\n"
" --idle-sleep num\n" " --idle-sleep num\n"
" --sleepiness num\n" " --phase-sleep num-num\n"
" --sleepiness-night num\n" " --phase-awake num-num\n"
" --layer background|bottom|top|overlay\n" " --layer background|bottom|top|overlay\n"
" --follow-pointer true|false\n"
" --survive-close\n" " --survive-close\n"
"\n"; "\n";
@ -73,19 +72,31 @@ enum Neko
NEKO_RUN_LEFT_2, 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; size_t animation_ticks_until_next_frame = 10;
enum Neko current_neko = NEKO_STARE; enum Neko current_neko = NEKO_STARE;
enum Type type = NEKO; enum Type type = NEKO;
bool follow_pointer = true; bool follow_pointer = false; // TODO: implement this again
bool recreate_surface_on_close = false; bool recreate_surface_on_close = false;
enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct ext_idle_notifier_v1 *idle_notifier = NULL; struct ext_idle_notifier_v1 *idle_notifier = NULL;
uint32_t neko_idle_timeout_ms = 180000; /* 3 minutes. */ uint32_t neko_idle_timeout_ms = 180000; /* 3 minutes. */
int sleepiness = 4; enum Phase {
int sleepiness_night = 5; 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 struct Seat
{ {
@ -167,15 +178,15 @@ static void handle_error (int signum)
{ {
const char *msg = const char *msg =
"\n" "\n"
"🐈 🐈 🐈\n"
"┌──────────────────────────────────────────┐\n" "┌──────────────────────────────────────────┐\n"
"│ │\n" "│ │\n"
"│ wayneko has crashed. \n" "│ wayneko has crashed :(\n"
"│ │\n" "│ │\n"
"│ This is likely a bug, so please │\n" "│ This is likely a bug in the fork │\n"
"│ report this to the mailing list. │\n" "│ you are running, so feel free to │\n"
"│ report your stuff there. │\n"
"│ │\n" "│ │\n"
"~leon_plickat/public-inbox@lists.sr.ht\n" " https://copeberg.org/virt/wayneko \n"
"│ │\n" "│ │\n"
"└──────────────────────────────────────────┘\n" "└──────────────────────────────────────────┘\n"
"\n"; "\n";
@ -823,11 +834,8 @@ static bool animtation_neko_wants_sleep (void)
} }
/** Returns true if new frame is needed. */ /** Returns true if new frame is needed. */
static bool animation_next_state_with_idle (void) static bool animation_next_state_sleep(void) {
{ switch (current_neko) {
/* If no one is there (system is idle), neko gets bored and will sleep. */
switch (current_neko)
{
case NEKO_SLEEP_1: case NEKO_SLEEP_1:
case NEKO_SLEEP_2: case NEKO_SLEEP_2:
case NEKO_YAWN: case NEKO_YAWN:
@ -919,39 +927,9 @@ static bool animation_next_state_with_hotspot (uint32_t x)
} }
/** Returns true if new frame is needed. */ /** Returns true if new frame is needed. */
static bool animation_next_state_normal (void) static bool animation_next_state_awake (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)
{ {
switch (current_neko) {
case NEKO_STARE: case NEKO_STARE:
switch (rand() % 24) switch (rand() % 24)
{ {
@ -960,9 +938,6 @@ static bool animation_next_state_normal (void)
break; break;
case 1: case 1:
animation_neko_do_sleep();
break;
case 2: case 2:
animation_neko_do_yawn(); animation_neko_do_yawn();
break; break;
@ -1014,15 +989,10 @@ static bool animation_next_state_normal (void)
case NEKO_SLEEP_1: case NEKO_SLEEP_1:
case NEKO_SLEEP_2: case NEKO_SLEEP_2:
if ( rand() % sleepiness == 0 )
{
if ( rand() % 2 == 0 ) if ( rand() % 2 == 0 )
animation_neko_do_shock(); animation_neko_do_shock();
else else
animation_neko_do_stare(false); animation_neko_do_yawn();
}
else
animation_neko_do_sleep();
return true; return true;
case NEKO_SCRATCH_1: case NEKO_SCRATCH_1:
@ -1041,12 +1011,6 @@ static bool animation_next_state_normal (void)
return true; return true;
case NEKO_YAWN: case NEKO_YAWN:
if ( rand() % 2 == 0 )
animation_neko_do_stare(false);
else
animation_neko_do_sleep();
return true;
case NEKO_SHOCK: case NEKO_SHOCK:
animation_neko_do_stare(true); animation_neko_do_stare(true);
return true; return true;
@ -1056,27 +1020,61 @@ static bool animation_next_state_normal (void)
return false; 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. */ /** Returns true if new frame is needed. */
static bool animation_next_state (void) static bool animation_next_state (void)
{ {
check_phase();
if ( animation_ticks_until_next_frame > 0 ) if ( animation_ticks_until_next_frame > 0 )
{ {
animation_ticks_until_next_frame--; animation_ticks_until_next_frame--;
return false; return false;
} }
bool idle = false;
struct Seat *seat; struct Seat *seat;
wl_list_for_each(seat, &seats, link) wl_list_for_each(seat, &seats, link) {
{ if (seat->currently_idle) idle = true;
if (seat->currently_idle)
return animation_next_state_with_idle();
} }
wl_list_for_each(seat, &seats, link)
{ if (false) {
wl_list_for_each(seat, &seats, link) {
if (seat->on_surface) if (seat->on_surface)
return animation_next_state_with_hotspot(seat->surface_x); 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 ) 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); const char *t = get_argument(argc, argv, &i);
if ( t == NULL ) if ( t == NULL )
return EXIT_FAILURE; return EXIT_FAILURE;
@ -1435,27 +1436,49 @@ int main (int argc, char *argv[])
return EXIT_FAILURE; 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); char* a = get_argument(argc, argv, &i);
int i = atoi(a); char* b = a;
if (i != 0)
sleepiness = abs(i) + 1; while (*b != '-' && *b != 0) b++;
else if (*b == 0) {
{ fprintf(stderr, "ERROR: Values in '%s' for flag '--awake-phase' are must be split by a dash.\n", a);
fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--sleepiness'.\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; 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); char* a = get_argument(argc, argv, &i);
int i = atoi(a); char* b = a;
if (i != 0)
sleepiness_night = abs(i) + 1; while (*b != '-' && *b != 0) b++;
else if (*b == 0) {
{ fprintf(stderr, "ERROR: Values in '%s' for flag '--sleep-phase' are must be split by a dash.\n", a);
fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--sleepiness-night'.\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; return EXIT_FAILURE;
} }
} }