summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenji Dial <benji@benjidial.net>2024-05-19 04:34:40 -0400
committerBenji Dial <benji@benjidial.net>2024-05-19 04:34:40 -0400
commite60fa7740cd7d245d1b22a25fea9df0768d32668 (patch)
tree728fa422d3a2abc66a3e2d89e4ef03b72074bb3e
parentb1a912a8a6ff472a49b2e0a09cfd433adfc2cb24 (diff)
downloadhilbert-os-e60fa7740cd7d245d1b22a25fea9df0768d32668.tar.gz
mouse support (working in qemu, semi-working in virtualbox)
-rw-r--r--applications/init/source/main.cpp124
-rw-r--r--documentation/kernel-interface/keys.txt (renamed from documentation/keys.txt)0
-rw-r--r--documentation/kernel-interface/sockets.txt (renamed from documentation/sockets.txt)0
-rw-r--r--documentation/kernel-interface/syscalls.txt (renamed from documentation/syscalls.txt)14
-rw-r--r--euler/include/euler/syscall.hpp15
-rw-r--r--euler/source/euler/syscall.asm28
-rw-r--r--kernel/include/hilbert/kernel/input.hpp21
-rw-r--r--kernel/include/hilbert/kernel/utility.hpp5
-rw-r--r--kernel/source/input.cpp4
-rw-r--r--kernel/source/interrupts.asm102
-rw-r--r--kernel/source/interrupts.cpp49
-rw-r--r--kernel/source/syscall.cpp19
-rw-r--r--libraries/daguerre/include/daguerre.hpp34
-rw-r--r--libraries/daguerre/source/daguerre.cpp15
-rw-r--r--skeleton/assets/pointer.ppmbin0 -> 1571 bytes
-rw-r--r--skeleton/assets/readme.txt2
16 files changed, 365 insertions, 67 deletions
diff --git a/applications/init/source/main.cpp b/applications/init/source/main.cpp
index ed0ef8e..6aa85a4 100644
--- a/applications/init/source/main.cpp
+++ b/applications/init/source/main.cpp
@@ -1,28 +1,120 @@
#include <daguerre.hpp>
+void overlay_encode(
+ daguerre::hilbert_color &dest, const daguerre::rgb24 &src) {
+ if (src.r != 0xff || src.g != 0x00 || src.b != 0xff)
+ daguerre::encode(dest, src);
+}
+
+void invert(daguerre::rgb24 &dest, const daguerre::rgb24 &src) {
+ dest.r = 255 - src.r;
+ dest.g = 255 - src.g;
+ dest.b = 255 - src.b;
+}
+
int main(int, char **) {
- auto hfb = daguerre::get_hilbert_framebuffer();
- daguerre::image<daguerre::rgb24> bim;
+ auto framebuffer = daguerre::get_hilbert_framebuffer();
- std::FILE *burdon = std::fopen("/assets/burden.ppm", "r");
- daguerre::try_load_ppm(burdon, bim);
- std::fclose(burdon);
+ daguerre::image<daguerre::rgb24> burden;
+ std::FILE *burden_file = std::fopen("/assets/burden.ppm", "r");
+ daguerre::try_load_ppm(burden_file, burden);
+ std::fclose(burden_file);
- unsigned width = bim.width < hfb.width ? bim.width : hfb.width;
- unsigned height = bim.height < hfb.height ? bim.height : hfb.height;
- unsigned x = (hfb.width - width) / 2;
- unsigned y = (hfb.height - height) / 2;
+ daguerre::image<daguerre::rgb24> pointer;
+ std::FILE *pointer_file = std::fopen("/assets/pointer.ppm", "r");
+ daguerre::try_load_ppm(pointer_file, pointer);
+ std::fclose(pointer_file);
+
+ int32_t width = burden.width < framebuffer.width
+ ? burden.width : framebuffer.width;
+ int32_t height = burden.height < framebuffer.height
+ ? burden.height : framebuffer.height;
+ unsigned x = (framebuffer.width - width) / 2;
+ unsigned y = (framebuffer.height - height) / 2;
+
+ int32_t new_mouse_x = width / 2;
+ int32_t new_mouse_y = height / 2;
+ int32_t old_mouse_x = new_mouse_x;
+ int32_t old_mouse_y = new_mouse_y;
+ bool was_left_mouse_down = false;
+
+ daguerre::overlay_region<
+ daguerre::hilbert_color, daguerre::rgb24, daguerre::encode>(
+ framebuffer, x, y, burden, 0, 0, width, height);
while (1) {
- daguerre::copy_region<>(hfb, bim, 0, 0, x, y, width, height);
- while ((__euler_read_key_packet() & 0x0400ff) != 0x00005a)
- ;
- for (unsigned i = 0; i < bim.width * bim.height; ++i) {
- bim.buffer[i].r = 255 - bim.buffer[i].r;
- bim.buffer[i].g = 255 - bim.buffer[i].g;
- bim.buffer[i].b = 255 - bim.buffer[i].b;
+
+ __euler_mouse_buttons mouse_buttons;
+ int16_t mouse_change_x, mouse_change_y;
+ uint32_t key_packet;
+ __euler_input_packet_type packet_type = __euler_get_input_packet(
+ mouse_buttons, mouse_change_x, mouse_change_y, key_packet);
+
+ bool anything_changed = false;
+ bool should_invert = false;
+
+ if (packet_type == __EULER_IPT_MOUSE) {
+
+ if (mouse_change_x != 0 || mouse_change_y != 0) {
+
+ old_mouse_x = new_mouse_x;
+ old_mouse_y = new_mouse_y;
+ new_mouse_x += mouse_change_x;
+ new_mouse_y += mouse_change_y;
+
+ if (new_mouse_x < 0)
+ new_mouse_x = 0;
+ else if ((unsigned)new_mouse_x > width - pointer.width)
+ new_mouse_x = width - pointer.width;
+
+ if (new_mouse_y < 0)
+ new_mouse_y = 0;
+ else if ((unsigned)new_mouse_y > height - pointer.height)
+ new_mouse_y = height - pointer.height;
+
+ anything_changed = true;
+
+ }
+
+ if (!was_left_mouse_down && (mouse_buttons & __EULER_MB_LEFT))
+ should_invert = true;
+
+ was_left_mouse_down = mouse_buttons & __EULER_MB_LEFT;
+
+ }
+
+ else if (packet_type == __EULER_IPT_KEYBOARD)
+ if ((key_packet & 0x0400ff) == 0x00005a)
+ should_invert = true;
+
+ if (should_invert) {
+
+ //this works with the current implementation of overlay_region, but
+ //maybe it would be better to have a dedicated function for when the
+ //two regions are exactly the same, given the comment on overlay_region.
+ daguerre::overlay_region<daguerre::rgb24, daguerre::rgb24, invert>(
+ burden, 0, 0, burden, 0, 0, width, height);
+
+ daguerre::overlay_region<
+ daguerre::hilbert_color, daguerre::rgb24, daguerre::encode>(
+ framebuffer, x, y, burden, 0, 0, width, height);
+
+ anything_changed = true;
+
}
+
+ if (anything_changed) {
+ daguerre::overlay_region<
+ daguerre::hilbert_color, daguerre::rgb24, daguerre::encode>(
+ framebuffer, old_mouse_x + x, old_mouse_y + y, burden,
+ old_mouse_x, old_mouse_y, pointer.width, pointer.height);
+ daguerre::overlay_region<
+ daguerre::hilbert_color, daguerre::rgb24, overlay_encode>(
+ framebuffer, new_mouse_x + x, new_mouse_y + y,
+ pointer, 0, 0, pointer.width, pointer.height);
+ }
+
}
}
diff --git a/documentation/keys.txt b/documentation/kernel-interface/keys.txt
index 1b92afa..1b92afa 100644
--- a/documentation/keys.txt
+++ b/documentation/kernel-interface/keys.txt
diff --git a/documentation/sockets.txt b/documentation/kernel-interface/sockets.txt
index 73dade6..73dade6 100644
--- a/documentation/sockets.txt
+++ b/documentation/kernel-interface/sockets.txt
diff --git a/documentation/syscalls.txt b/documentation/kernel-interface/syscalls.txt
index 52e909d..792c300 100644
--- a/documentation/syscalls.txt
+++ b/documentation/kernel-interface/syscalls.txt
@@ -63,9 +63,19 @@ get new pages:
rax out: start of first page
the allocated pages are next to each other, writable, and not executable.
-read key packet:
+get input packet:
rax in: 5
- eax out: key packet
+ al out:
+ bit 7: was mouse packet
+ if bit 7:
+ bit 0: left mouse button down
+ bit 1: right mouse button down
+ bit 2: middle mouse button down
+ if bit 7:
+ di out: mouse x change (signed)
+ si out: mouse y change (signed)
+ else:
+ edi out: key packet
create private socket:
rax in: 6
diff --git a/euler/include/euler/syscall.hpp b/euler/include/euler/syscall.hpp
index 9255642..761dbcc 100644
--- a/euler/include/euler/syscall.hpp
+++ b/euler/include/euler/syscall.hpp
@@ -58,4 +58,17 @@ extern "C" uint32_t *__euler_get_framebuffer(
extern "C" uint32_t __euler_encode_color(uint8_t r, uint8_t g, uint8_t b);
-extern "C" uint32_t __euler_read_key_packet();
+enum __euler_mouse_buttons : uint8_t {
+ __EULER_MB_LEFT = 1,
+ __EULER_MB_RIGHT = 2,
+ __EULER_MB_MIDDLE = 4
+};
+
+enum __euler_input_packet_type : uint8_t {
+ __EULER_IPT_MOUSE = 1,
+ __EULER_IPT_KEYBOARD = 2
+};
+
+extern "C" __euler_input_packet_type __euler_get_input_packet(
+ __euler_mouse_buttons &buttons_out, int16_t &x_change_out,
+ int16_t &y_change_out, uint32_t &keyboard_packet_out);
diff --git a/euler/source/euler/syscall.asm b/euler/source/euler/syscall.asm
index 34f2735..41bd05c 100644
--- a/euler/source/euler/syscall.asm
+++ b/euler/source/euler/syscall.asm
@@ -11,7 +11,7 @@ global __euler_end_this_thread
global __euler_read_from_stream
global __euler_get_framebuffer
global __euler_encode_color
-global __euler_read_key_packet
+global __euler_get_input_packet
section .text
@@ -96,7 +96,31 @@ __euler_encode_color:
syscall
ret
-__euler_read_key_packet:
+__euler_get_input_packet:
+ push rdi
+ push rsi
+ push rdx
+ push rcx
mov rax, 5
syscall
+
+ test al, 0x80
+ jnz .mouse_packet
+
+ pop rcx
+ mov dword [rcx], edi
+ add rsp, 24
+ mov al, 2
+ ret
+
+.mouse_packet:
+ add rsp, 8
+ pop rdx
+ mov word [rdx], si
+ pop rdx
+ mov word [rdx], di
+ pop rdx
+ and al, 0x7f
+ mov byte [rdx], al
+ mov al, 1
ret
diff --git a/kernel/include/hilbert/kernel/input.hpp b/kernel/include/hilbert/kernel/input.hpp
index 2209ddc..d1b7ca2 100644
--- a/kernel/include/hilbert/kernel/input.hpp
+++ b/kernel/include/hilbert/kernel/input.hpp
@@ -18,7 +18,26 @@ namespace hilbert::kernel::input {
BREAK = 1 << 18,
};
- extern utility::queue<uint32_t> *key_queue;
+ enum buttons_t : uint8_t {
+ LEFT_BUTTON = 1,
+ RIGHT_BUTTON = 2,
+ MIDDLE_BUTTON = 4
+ };
+
+ struct input_packet {
+ union {
+ struct {
+ int16_t x_change;
+ int16_t y_change;
+ buttons_t buttons;
+ } mouse;
+ uint32_t keyboard;
+ };
+ bool is_mouse;
+ };
+
+ extern utility::queue<input_packet> *input_queue;
+
//notify a process waiting for input
void got_input();
diff --git a/kernel/include/hilbert/kernel/utility.hpp b/kernel/include/hilbert/kernel/utility.hpp
index 47f78ea..c0b8c19 100644
--- a/kernel/include/hilbert/kernel/utility.hpp
+++ b/kernel/include/hilbert/kernel/utility.hpp
@@ -342,6 +342,11 @@ namespace hilbert::kernel::utility {
return move(ret);
}
+ //assumes not empty
+ value_t &last_inserted() const {
+ return buffer[(count - 1 + next_read_index) % buffer_len];
+ }
+
};
}
diff --git a/kernel/source/input.cpp b/kernel/source/input.cpp
index 696cb13..921ae7b 100644
--- a/kernel/source/input.cpp
+++ b/kernel/source/input.cpp
@@ -5,10 +5,10 @@
namespace hilbert::kernel::input {
- utility::queue<uint32_t> *key_queue;
+ utility::queue<input_packet> *input_queue;
void init_input() {
- key_queue = new utility::queue<uint32_t>();
+ input_queue = new utility::queue<input_packet>();
}
void got_input() {
diff --git a/kernel/source/interrupts.asm b/kernel/source/interrupts.asm
index babc020..e0c3cdb 100644
--- a/kernel/source/interrupts.asm
+++ b/kernel/source/interrupts.asm
@@ -227,14 +227,6 @@ section .bss
section .text
-write_keyboard_byte:
- in al, 0x64
- test al, 0x02
- jnz write_keyboard_byte
- mov al, dil
- out 0x60, al
- ret
-
extern on_keyboard_interrupt
keyboard_isr:
@@ -249,6 +241,7 @@ keyboard_isr:
push rcx
push rax
+ call wait_read_ps2
in al, 0x60
mov dil, al
@@ -269,6 +262,54 @@ keyboard_isr:
iretq
+extern on_mouse_interrupt
+
+mouse_isr:
+
+ push r11
+ push r10
+ push r9
+ push r8
+ push rsi
+ push rdi
+ push rdx
+ push rcx
+ push rax
+
+ call wait_read_ps2
+ in al, 0x60
+ mov dil, al
+
+ call on_mouse_interrupt
+
+ mov al, 0x20
+ out 0x20, al
+ out 0xa0, al
+
+ pop rax
+ pop rcx
+ pop rdx
+ pop rdi
+ pop rsi
+ pop r8
+ pop r9
+ pop r10
+ pop r11
+
+ iretq
+
+wait_send_ps2:
+ in al, 0x64
+ test al, 0x02
+ jnz wait_send_ps2
+ ret
+
+wait_read_ps2:
+ in al, 0x64
+ test al, 0x01
+ jz wait_send_ps2
+ ret
+
load_gdt_and_idt:
;fill exception entries in idt
@@ -294,7 +335,7 @@ load_gdt_and_idt:
out 0x21, al
mov al, 0x01
out 0x21, al
- mov al, 0xfd ;mask all but irq 1
+ mov al, 0xf9 ;mask all but irq 1 and 2
out 0x21, al
mov al, 0x11
@@ -305,19 +346,54 @@ load_gdt_and_idt:
out 0xa1, al
mov al, 0x01
out 0xa1, al
- mov al, 0xff ;mask all
+ mov al, 0xef ;mask all but irq 12
out 0xa1, al
+ ;register keyboard and mouse interrupts
+
mov rdi, 0x21
mov rsi, keyboard_isr
call set_isr
- ;set keyboard config
+ mov rdi, 0x2c
+ mov rsi, mouse_isr
+ call set_isr
+
+ ;set ps2 config
+ call wait_send_ps2
mov al, 0x60
out 0x64, al
- mov dil, 0x01
- call write_keyboard_byte
+
+ call wait_send_ps2
+ mov al, 0x03
+ out 0x60, al
+
+ ;set mouse defaults
+
+ call wait_send_ps2
+ mov al, 0xd4
+ out 0x64, al
+
+ call wait_send_ps2
+ mov al, 0xf6
+ out 0x60, al
+
+ call wait_read_ps2
+ in al, 0x60
+
+ ;enable mouse reporting
+
+ call wait_send_ps2
+ mov al, 0xd4
+ out 0x64, al
+
+ call wait_send_ps2
+ mov al, 0xf4
+ out 0x60, al
+
+ call wait_read_ps2
+ in al, 0x60
;make tss entry in gdt
diff --git a/kernel/source/interrupts.cpp b/kernel/source/interrupts.cpp
index 6e22121..9b6495e 100644
--- a/kernel/source/interrupts.cpp
+++ b/kernel/source/interrupts.cpp
@@ -1,3 +1,4 @@
+#include <hilbert/kernel/framebuffer.hpp>
#include <hilbert/kernel/input.hpp>
#include <hilbert/kernel/panic.hpp>
@@ -54,7 +55,9 @@ static uint32_t current_flags = 0;
static void got_key(uint32_t key) {
- input::key_queue->insert(current_flags | key);
+ input::input_queue->insert({
+ .keyboard = current_flags | key, .is_mouse = false});
+
input::got_input();
if (key == (input::BREAK | 0x77))
@@ -90,7 +93,7 @@ static void got_key(uint32_t key) {
}
static uint8_t key_so_far[8];
-uint8_t key_so_far_len = 0;
+static uint8_t key_so_far_len = 0;
extern "C" void on_keyboard_interrupt(uint8_t byte) {
@@ -150,3 +153,45 @@ extern "C" void on_keyboard_interrupt(uint8_t byte) {
}
}
+
+static uint8_t mouse_packet_so_far[3];
+static uint8_t mouse_packet_so_far_len = 0;
+
+extern "C" void on_mouse_interrupt(uint8_t byte) {
+
+ if (mouse_packet_so_far_len == 0 && byte == 0xfa)
+ //dirty hack
+ return;
+
+ mouse_packet_so_far[mouse_packet_so_far_len++] = byte;
+ if (mouse_packet_so_far_len < 3)
+ return;
+
+ mouse_packet_so_far_len = 0;
+
+ int16_t x_change = mouse_packet_so_far[1];
+ int16_t y_change = -(int16_t)mouse_packet_so_far[2];
+ if (mouse_packet_so_far[0] & 0x10)
+ x_change -= 0x100;
+ if (mouse_packet_so_far[0] & 0x20)
+ y_change += 0x100;
+
+ input::input_packet packet = {
+ .mouse = {
+ .x_change = x_change, .y_change = y_change,
+ .buttons = (input::buttons_t)(mouse_packet_so_far[0] & 7) },
+ .is_mouse = true };
+
+ if (input::input_queue->count > 0 &&
+ input::input_queue->last_inserted().is_mouse &&
+ input::input_queue->last_inserted().mouse.buttons ==
+ packet.mouse.buttons) {
+ input::input_queue->last_inserted().mouse.x_change = packet.mouse.x_change;
+ input::input_queue->last_inserted().mouse.y_change = packet.mouse.y_change;
+ }
+ else
+ input::input_queue->insert(packet);
+
+ input::got_input();
+
+}
diff --git a/kernel/source/syscall.cpp b/kernel/source/syscall.cpp
index 768ff0d..5d9714c 100644
--- a/kernel/source/syscall.cpp
+++ b/kernel/source/syscall.cpp
@@ -157,7 +157,7 @@ namespace hilbert::kernel::syscall {
}
- void read_key_packet_syscall(
+ void get_input_packet_syscall(
uint64_t &rax, uint64_t &rdi, uint64_t &rsi, uint64_t &rdx
) {
@@ -165,8 +165,17 @@ namespace hilbert::kernel::syscall {
auto *t = application::running_thread;
do
- if (input::key_queue->count > 0) {
- rax = (uint64_t)input::key_queue->take();
+ if (input::input_queue->count > 0) {
+ input::input_packet packet = input::input_queue->take();
+ if (packet.is_mouse) {
+ rax = packet.mouse.buttons | 0x80;
+ rdi = (uint16_t)packet.mouse.x_change;
+ rsi = (uint16_t)packet.mouse.y_change;
+ }
+ else {
+ rax = 0;
+ rdi = packet.keyboard;
+ }
return;
}
while (application::save_thread_state(t->cpu));
@@ -504,7 +513,7 @@ namespace hilbert::kernel::syscall {
&open_file_syscall,
&end_this_thread_syscall,
&get_new_pages_syscall,
- &read_key_packet_syscall,
+ &get_input_packet_syscall,
&create_private_socket_syscall,
&create_socket_listener_syscall,
&stop_socket_listener_syscall,
@@ -520,7 +529,7 @@ namespace hilbert::kernel::syscall {
&set_stream_length_syscall
};
- static constexpr int max_syscall_number = 18;
+ static constexpr int max_syscall_number = 19;
}
diff --git a/libraries/daguerre/include/daguerre.hpp b/libraries/daguerre/include/daguerre.hpp
index 274e257..62d10f0 100644
--- a/libraries/daguerre/include/daguerre.hpp
+++ b/libraries/daguerre/include/daguerre.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <stdint.h>
+#include <cstring>
#include <cstdio>
namespace daguerre {
@@ -13,14 +14,6 @@ namespace daguerre {
uint8_t b;
};
- template <class to_type, class from_type>
- to_type convert_color(const from_type &from);
-
- template <>
- inline hilbert_color convert_color<hilbert_color, rgb24>(const rgb24 &from) {
- return __euler_encode_color(from.r, from.g, from.b);
- }
-
template <class color_t>
class image {
@@ -88,15 +81,15 @@ namespace daguerre {
//from [from_x, from_x + width) x [from_y, from_y + height).
template <class color_t>
void copy_region(
- image<color_t> &to, const image<color_t> &from, unsigned from_x,
- unsigned from_y, unsigned to_x, unsigned to_y, unsigned width,
- unsigned height) {
+ image<color_t> &to, unsigned to_x, unsigned to_y,
+ const image<color_t> &from, unsigned from_x, unsigned from_y,
+ unsigned width, unsigned height) {
color_t *to_start = to.buffer + to.pitch * to_y + to_x;
const color_t *from_start = from.buffer + from.pitch * from_y + from_x;
for (unsigned y = 0; y < height; ++y)
- memcpy(
+ std::memcpy(
to_start + to.pitch * y, from_start + from.pitch * y,
width * sizeof(color_t));
@@ -107,12 +100,11 @@ namespace daguerre {
//from [from_x, from_x + width) x [from_y, from_y + height).
template <
class to_color_t, class from_color_t,
- to_color_t converter(const from_color_t &) =
- convert_color<to_color_t, from_color_t>>
- void copy_region(
- image<to_color_t> &to, const image<from_color_t> &from, unsigned from_x,
- unsigned from_y, unsigned to_x, unsigned to_y, unsigned width,
- unsigned height) {
+ void overlay(to_color_t &dest, const from_color_t &src)>
+ void overlay_region(
+ image<to_color_t> &to, unsigned to_x, unsigned to_y,
+ const image<from_color_t> &from, unsigned from_x, unsigned from_y,
+ unsigned width, unsigned height) {
to_color_t *to_start = to.buffer + to.pitch * to_y + to_x;
const from_color_t *from_start =
@@ -120,8 +112,12 @@ namespace daguerre {
for (unsigned y = 0; y < height; ++y)
for (unsigned x = 0; x < width; ++x)
- to_start[to.pitch * y + x] = converter(from_start[from.pitch * y + x]);
+ overlay(to_start[to.pitch * y + x], from_start[from.pitch * y + x]);
+
+ }
+ static inline void encode(hilbert_color &dest, const rgb24 &src) {
+ dest = __euler_encode_color(src.r, src.g, src.b);
}
image<hilbert_color> get_hilbert_framebuffer();
diff --git a/libraries/daguerre/source/daguerre.cpp b/libraries/daguerre/source/daguerre.cpp
index fb3ddc7..7c13a88 100644
--- a/libraries/daguerre/source/daguerre.cpp
+++ b/libraries/daguerre/source/daguerre.cpp
@@ -8,15 +8,22 @@ namespace daguerre {
return image<hilbert_color>(ptr, width, height, pitch, false);
}
+ //TODO: make this more robust
unsigned read_text_int(std::FILE *input) {
unsigned n = 0;
char ch;
- while (true) {
+ std::fread(&ch, 1, 1, input);
+ if (ch == '#') {
+ do
+ std::fread(&ch, 1, 1, input);
+ while (ch != '\n');
std::fread(&ch, 1, 1, input);
- if (ch < '0' || ch > '9')
- return n;
- n = n * 10 + ch - '0';
}
+ do {
+ n = n * 10 + ch - '0';
+ std::fread(&ch, 1, 1, input);
+ } while (ch >= '0' && ch <= '9');
+ return n;
}
bool try_load_ppm(std::FILE *input, image<rgb24> &into) {
diff --git a/skeleton/assets/pointer.ppm b/skeleton/assets/pointer.ppm
new file mode 100644
index 0000000..7169033
--- /dev/null
+++ b/skeleton/assets/pointer.ppm
Binary files differ
diff --git a/skeleton/assets/readme.txt b/skeleton/assets/readme.txt
index 56b1a9b..72476c5 100644
--- a/skeleton/assets/readme.txt
+++ b/skeleton/assets/readme.txt
@@ -2,3 +2,5 @@ the photo in burden.ppm is "selective focus photography snowflakes" by aaron
burden. it can be found online at
https://unsplash.com/photos/selective-focus-photography-snowflakes-9yhy1FXlKwI.
its license can be found online at https://unsplash.com/license.
+
+the icon in pointer.ppm is by me :)