leblog

workman layout

The quick brown fox jumps over the lazy dog.

Introduction

I was thinking for a long time about changing my keyboard layout I realized that it will take a significant amount of time to get used to it. So choosing the correct one for my usage was key.

Back-story

During high-school, I was going back and forth between QWERTY and AZERTY, due to the fact that every keyboards of my school were QWERTY and I've been taught computers in France so the layout was AZERTY... At 16, I got a surgery for a rupture of the flexor tendons of my index finger. So finding a way to avoid typing stress pain was always in my mind. I did search for convenient keyboard layouts, moving from staggered to matrix and ergonomic keyboards, there was always something that were creating stress and pain to my tendons. I kept the previous QWERTY layout for years until now though. I've ended up choosing workman as the new layout but this choice results from trials and errors that I'll explain in the following section.

Reflection

This post is the first time I'm using the workman layout as a full commitment, and I must say, I still struggle with it, usual thing that happened before when I tried Colemak. But I think it will be beneficial in the long run, as it is designed to reduce finger movement. Even though my typing speed as decreased, I can feel this the typing became less painful for my tendons. Workman is specifically optimized for typing English and code, though I am French, I still am able to type accents using Linux's compose key. I'll see how it goes in the future.

Resources

Summary

I was always fond of learning new languages, got to learn Spanish and English at school. Those were tedious to learn and tend to take a long time to get used to and perfect the usage. With time, I ended up loosing most of my Spanish and daily-using the English, but I was always thirsty of learning new languages. The issue was that having engineering studies and then a full time job doesn't let me a lot of time to have a professor in a new language. But non the less, I've tried some other languages, Russian, Japanese, Portuguese and no cigar. But one day while I was doom-scrolling on Youtube, I've ended up looking a video of a random developer that was sharing it's process of making a quiz for a language that I have no idea it existed. Toki pona. And this is about this language that I want to talk you about. It's name can be translated by the language of good, and what caught my attention was it's small learning curve and it's simplicity, it's dictionary is composed of 131 words as the date of today.

short history

The language was created in 2001 by Sonja Lang a linguist and translator whilst she were is depression. The language is minimalist by design and offers wide variations of writing is making it a fun language.

learning process

I was surprised how many references and instructional there was on a language that doesn't have that many interlocutors. The thing is that since it has the same ideas as the Esperanto and it's words origins are wider spread, a lot of persons have found interest in it through time. Some of the main resources I've found allowed me to learn the basic vocabulary in the span of a week and a half. Here is some of my recommendations if the adventure tempts you : – wasona : A simple but mostly complete site that allows you to learn how to form sentences and learn the words in an interactive manner. This site is available in multiple languages (and french was there !!!) – jan Kekan San's courses : An English Toki Pona teacher that provides well made lessons that allow you to form vague sentences to conversations with more complex and precise subjects. – The Toki Pona android APK : This one is good to train vocabulary or search for pre-made sentences, I've seen it being more and more complete with features.

mi tawa.

Summary

This explains my steps into creating a “lightweight” Linux kernel development environment using qemu.

Setup base system

Create the installation image

qemu-img create -f qcow2 <image-name>.qcow2 <size>

Install an Operating system of choice ( I use debian for this )

qemu-system-x86_64 -cpu host     \
                    -vga virtio  \
                    -enable-kvm  \
                    -usb -device usb-tablet \
                    -hda <qemu image> -boot d \
                    -cdrom <debian iso file> \
                    -serial stdio -m <ram> \
                    -smp <cpu>

Perform os installation then reboot using this command

qemu-system-x86_64 -nic user,hostfwd=tcp::1234-:22 \
                    -cpu host \
                    -vga virtio \
                    -enable-kvm \
                    -usb -device usb-tablet \
                    -hda <qemu image> -boot d \
                    -serial stdio \
                    -m <ram> -smp <cpu>

Prepare the system

On the graphical output do this

su
<input root password>
apt install ssh

You can now ssh into the vm to setup the required packages

ssh -p 1234 <username>@localhost
apt update
apt install build-essential \
			neovim \
  			libncurses-dev \
    		libssl-dev \
     		bc \
			libelf-dev \
  			git \
    		flex \
     		bison \
      		fakeroot \
       		libperl-dev \
        	qt5-qmake \
        	qtbase5-dev \
        	rsync

Build a new kernel

Now download the Linux sources

Execute as user

git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git -b <kernel_version>
cd linux

Next is to build a kernel compatible with your vm

make mrproper && make localmodconfig

Compile it

make -j$(nrpoc)

Executed as root

make modules_install && make install

Ensure you have access to sbin to update grub in order to boot onto the new kernel.

echo "export PATH=/usr/sbin:$PATH" >> ~/.bashrc
source ~/.bashrc

Writing the export in the shell profile allow to not worry about exporting it again later

update-grub or grub-update

Now you’re done, reboot to start with the new kernel !

Compacting the qcow2 image

First off, install zerofree on the host

On the host

# Load nbd kernel module
sudo modprobe nbd max_part=8

# Connect the QCOW2 image to a /dev/nbd device
sudo qemu-nbd --connect=/dev/nbd0 working-image.qcow2

# Create partition mappings
sudo kpartx -av /dev/nbd0

# Identify partition mappings
sudo blkid /dev/mapper/nbd0p*

# Check and clear partition if needed
sudo e2fsck -f /dev/mapper/nbd0p1

# Ensure the partition isn't mounted
mount | grep /dev/mapper/nbd0p1  # should be empty

# Apply zerofree
sudo zerofree /dev/mapper/nbd0p1

# Cleanup mounts
sudo kpartx -dv /dev/nbd0
sudo qemu-nbd --disconnect /dev/nbd0

Now your image should be as minimal as possible !

Qemu keyboard emulation

To emulate a keyboard with qemu, you can add this flag to the launch command

qemu ... --monitor stdio ...

This will enable the qemu monitor on the current terminal

To trigger a keyboard plugged in

device_add usb-kbd,id=keyboard1

To trigger a keyboard removed

device_del keyboard1

Retrospective

Since then, I have tried many other methods, and even though qemu is practical, I rather used a more commonly use method like a dockerfile ( used in this project ). At the time this post was made, I was still studying at a school where there were no best practices and hand-holding.

Summary

This was a project that I’ve itterated mutiple times without event trying to go further that the first rendering. This is my ways on improving a simple game of life editor but with plenty of editing features.

Core ideas & application:

The first things that I wanned to add on the basic project is a setting and a pause/play option.

In order to do this, I used dearImGUI on top of raylib rendering, the whole project is implemented in C++.

This is what I’ve implemented so far.

The following steps are what will take me a lot of brain process. In order to add more advanced features, I need to optimize this base.

As for now, the grid is stored in a vector of booleans, this causes an issue of memory as we can set the current cells sizes really small. The first topic I will bring here for now is the data compression. This will allow me to create saves of the current grid in a memory efficient way.

Optimisation & patern reproduction

I wanned to implement a way to start from an initial state and step forward & backward. This means storing an history. If done like it is implemented right now (as vectors of booleans), this will take too much space in memory. Here is my though on the improvments.

Data compression :

Packed data:

  • The initial state (to reduce load time or file size).
  • Undo/redo snapshots (for compact state history) with history buffer of packed data.
  • Any export/serialization (like RLE or bitstream).

Unpacked data:

  • Only once, when loading the initial grid to GPU.
  • When restoring an undo/redo state.

RLE(Lossless compression) :

The first type of compression that I've implemented was the RLE and it just compacts the data that is represented multiple times into a shorter representation.

As the game of life contains only 2 types, it is easier to implement.

There could be multiple dead or alive cells that are stored into a 1D array.

To achieve my goal, I want to compressed any grid sizes to have a shorter representation.

Here is my first iteration on this.

<grid_width>.<grid_height>.<initial_state>.<nbr>.<nbr>.<…>

// _data is a vector of bool that contains each cell states
std::string World::getCompressed() {
  if (!_data || _data->empty()) {
    return {};
  }
  bool previous_state = (*_data)[0];
  std::vector<uint32_t> compressed_out;
  compressed_out.push_back(_width);
  compressed_out.push_back(_height);
  compressed_out.push_back((previous_state) ? 1 : 0);
  unsigned int count = 0;
  for (auto state : *_data) {
    if (previous_state != state) {
      compressed_out.push_back(count++);
      previous_state = state;
      count = 1;
      continue;
    }
    count++;
  }
  compressed_out.push_back(count);
  return compressed_out;
}

// Ex for a grid of 33 x 18
// std vector<bool> _data = {true, true, true, false, false, true, true, false, true, <false until the end>};
// the compressed method results in 33.18.1.3.2.2.1.1.585

This seems easy enough, now the decompression algorythm.

void World::loadCompressed(const std::vector<uint32_t> &compressed) {
  if (compressed.empty()) {
    return;
  }
  auto it = compressed.begin();
  int width = *it++;
  int height = *it++;
  bool actual_state = (*it++ == 1);
  auto new_data = new std::vector<bool>(width * height, false);
  while (it != compressed.end()) {
    unsigned int count = *it++;
    for (unsigned int i = 0; i < count; ++i) {
      new_data->push_back(actual_state);
    }
    actual_state = !actual_state;
  }
  delete this->_data;
  this->_data = new_data;
  this->_width = width;
  this->_height = height;
}