Warning: there may be occasional oddness due to css and blog edits. **remodel of posts done. Overhaul of pages (distilled) in progress 3/9

Tuesday, June 30, 2026

scfb our last resort

It seems that I am cursed to revisit the use of scfb as my graphics driver once more.  I ran into problems with my graphics again and I cannot say exactly why everything remained broken.  I looked over the Broke GUI recovery blog post I made to be sure I hadn't missed anything.  I did very similarly to how I described the steps at the end, but the amdgpu driver would not tell xlibre-server that there were screens for it.

pkg install 11vm
graphics/mesa-dri
graphics/drm-kmod
x11-drivers/xlibre-drivers
x11/xinit
x11/xauth
x11/xterm
pkg install rust
pkg install cargo-c
x11/libxkbcommon
x11/xrdb
x11/xset
x11/xrandr
fvwm

And later I added a few more things, three of them fill gaps in my fvwm environment.

graphics/feh
audio/umix
x11-clocks/dclock
www/librewolf

It used to be that our last resort graphics driver was vesa, but on a 4k monitor, this is truly a waste and as in the past, makes a GUI almost unusable.  Now our unwanted but functional option is scfb which has considerably better resolution but with some quirks.  If you switch to another tty for any reason, when you return your desktop may have a layer of black covering it, concealing any desktop graphic you may have.  This seems to be the only oddity and it may not even be due to scfb, I cannot be sure.

When I ran into by broken graphics issue, I assumed it was purely due to a large update of pkgs and perhaps something got uninstalled, or there may have been a conflict of versions in some way.  What confused me much longer than it ought to have, since I had been down this road before, was that I could get back to a simple text-only interface if I disabled the automatic load of the amdgpu kernel object.  Every time it tried to load it during boot, I would have a panic and reboot.  This made me believe there were other problems.  When I finally came to my senses and disabled the amdgpu from loading, I would no longer need to use single user for any changes.

The good and bad news became that even after the complete removal via pkg delete -af as I have done a few times now, and the reinstall of that list of things above, allowing amdgpu to load didn't cause a panic and reboot.  Now, if I try to startx all I get is the famously frustrating "no screens found" error.  I tried reinstalling different things, different versions of a few things, but no change in my situation.  So once again I wait for whatever it is that was broken outside of my control to be fixed again, but until then I use scfb our last resort.

Sunday, June 28, 2026

sylve makes gentoo bhyve vm

I have decided to try using Sylve and rather than create "Just another BSD on Disk" even though I may at a later date, I am deciding to put Gentoo in a VM.  A long time ago, I had it setup and ready to use on a now non-booting host using Virtualbox.  Shortly before, I had installed ubuntu successfully after a few small difficulties but then discovered that the OS itself seemed to have problems.  I believe that even if I have some difficulties with gentoo, I will be able to find my way out of them.  As before, the process is rather involved, and I am going to follow the same youtuber with his newer friendlier to new users, more descriptive Gentoo installer video. The youtube video I followed for the bhyve setup was from BSD Jedi called FreeBSD - Installing and Using Sylve and helped me quite a bit, but I will make a few small clarifications to it.  I believe it will be much better for me to use bhyve than Virtualbox, this new VM will be for a similar purpose, to use Linux software and to help me with porting it.

The initial step, pkg install sylve works fine for me as I use the latest branch of FreeBSD ports with 15-stable.  The next commands are for preliminary setup, sysrc sylve_enable=yes and then service sylve start to get it running.  Finally, an open browser window to the localhost IP, with port 8181 will bring us to the sylve interface.

Click image to enlarge

Since I am using a real machine and my machine name is ichigo, I click that dropdown which is shown below one named Data Center.  After I click ichigo, the summary window opens, and note the two dark buttons in the upper right, Create VM and Create Jail.  Before we begin with setting up the Virtual Machine, it is easier to download an iso using the host machine, a seperate tab on the gentoo downloads page can be opened.  Initially I had chosen the liveGUI image but have since downloaded the minimal iso.  This is the part of sylve that is a little bit less clear.  I had less success with downloading from a remote site with the sylve mechanism, so I download with the host machine.  Making this new iso available to sylve is done by downloading with a pathname.  I downloaded install-amd64-minimal-20260524T170105Z.iso into /home/tigersharke/Emulators so my next steps are as follows.

Click the ichigo dropdown button, click the utilities dropdown which is visible beneath the 'Node -- ichigo' header, and between the storage and settings buttons.  Under the utilities dropdown it reveals 'Cloud Init Templates' and 'Downloader' so I click on Downloader.  When the downloader displays to the right of the dropdowns, it lists any files that sylve has been informed of existing.  I need to add the gentoo iso, so I click the new button which opens a Download window asking for Magnet (torrent) or Web URL or path.  Below is a box with example magnet text but as soon as anything is typed there, the example text is gone.

I copy and paste the path, add a / and then paste the file name.  The Download type is already Uncategorized and leaving the filename alone means it will default to what was already entered for the file name.  It is an iso, so no need to extract or convert it.  I click the download button and wait for it to complete.  Now the listing in the Downloader shows install-amd64-minimal-20260524T170105Z.iso with uncategorized, a size of 988.73 MiB and 100% for progress.  All of this means that sylve knows that the iso exists and can be assigned to a virtual machine.  Doing this at the outset is one way to get it done, but if, as I had done, the virtual machine is created with the livegui-amd64-20260510T170106Z.iso file attached, the way to get the iso is a similar process which I describe later.

The next step I would recommend before creating the VM is to create the network device.  The network device is part of the host machine, so I click ichigo, to show similar settings dropdowns as may be shown for a VM.  The difference is that a network device cannot be defined by the VM, it can assign a defined device from the host.  Just as BSD Jedi tells us, click network, and then click switches, and choose standard from the dropdown.  Open the dialogs to create a new standard switch by clicking new.  Give the switch a meaningful name that will not lead to future confusion, something like bhyve-LAN would be distinct from LAN if ever looking at other network configurations at home.  For the ports selector, choose the ethernet adapter of the host machine, mine is igb0.  Tick DHCP and SLAAC and then click create.  The other details do not need to be specifically defined, sylve or bhyve will handle them.

Right now all that has been done is to prepare for creating the VM.  Click the host machine name, it may be directly below the 'Data Center' tab, which for me is ichigo.  A summary of your host machine will be displayed.  Now the button in the upper right, Create VM is clicked for its dialogs.  First the basic details, such as a name and a unique VM ID number.  Storage defaults to ZFS Volume which I use, then select my storage pool, zroot and give the VM 20GiB of space.  The installation media is where we choose the iso that was setup earlier.  The network dialog will show the bhyve-LAN that was configured, and the choice of none, which means those same steps to create the switch would be done later.  In the hardware dialog, this has to be enough capability and memory for the machine to succeed.  I use 1 socket, 4 cores, and 1 thread.  I don't pin any CPU but I give the VM 32GiB of memory (I have 128GiB).  Another important step is in the Advanced panel, VNC Resolution should be set reasonably now because I am unsure how it gets defined otherwise, though likely with a config file that could be edited.  Since I have a 4k monitor, I can choose 1920x1200 for the VNC resolution.  A previous VM creation was left at 640x480 which is extremely inadequate.  Once all choices are made for how the VM is configured, click the Create Virtual Machine button.

The situation with an already existing Virtual Machine which now has the wrong iso attached and a different iso needs to be used is not a difficult problem to solve but it is a bit different than how it was done at the beginning.  Once the host machine has the file, go to the storage tab, then to the right, where it shows the ZFS volume, above that, click the new button.  The box for name that shows "DB Storage" can be filled with the actual file name, or something simpler but its a good idea to give it the iso suffix if it is an iso file.  Choose Import from the type dropdown, Image from the Disk type dropdown, select the correct filename in the ISO/Image dropdown, choose AHCI CD-ROM from the Emulation selection, and if this is for a first boot install, give it boot order number 0.  If it complains, try again after making any other changes needed, such as to detach an old iso.

Under 'Data Center' is our host machine, mine is 'ichigo', and below 'ichigo' is my 'gentoo' Virtual Machine.  To the right of the highlighted 'summary' box, is the start button.  I click the start button to activate the VM configured for gentoo with the installer iso starting first.  Now I have the gentoo video to follow for all the necessary steps.

What I see is slightly more graphical and failing to choose quickly enough gives me the default and same choice as in the video.  For this gentoo install, internet access is required so choosing not to wait for setting up the switch is a good idea.  After a quick boot and accepting the default keymap, I can verify internet as was done in the video.

One of the first things that is done is to identify the hard drive with lsblk, it should be sda just like in the video, but if you mistakenly fail to set the installer iso as an ahci CDROM, your virtual HDD will show up as sdb.  This is not a problem, everything should still work just fine but be sure to substitute sdb where the video references sda.  It may just be simpler to shutdown the virtual machine to fix the iso designation.

When following along with using fdisk, and he says how the second value is the size.  If you are listening, it is not mentioned, but what is shown on the screen is a prefix of the plus sign (+) so when he says to set one gig for the size, you type +1G.  Making the rest of the partitions is simple, just follow your plan which may coincide with what is said.  I chose to include a swap and home partition, so mine was 1G, 9G, 2G, 20G, although the last partition is easy with fdisk, just choose the defaults which becomes what is remaining on the virtual HDD.  I tried cfdisk following a different video, and either cfdisk has problems or I did something wrong somehow, so I went to the other video and discovered fdisk was explained.

He explains how to make a new directory like mkdir -p with the gentoo equivalent, mkdir --parents to create a mount point for our root filesystem to be able to install files upon it.  The df -h command shows how my virtual filesystem looks:

Filesystem  	Size	Used	Avail	Use%	Mounted on
devtempfs   	 10M	   0	  10M	  0%	/dev
tmpfs       	472M	   0	 472M	  0%	/dev/shm
/dev/sr0    	989M	989M	   0	100%	/run
/dev/loop0  	859M	859M	   0	100%	/run/rootfsbase
LiveOS_rootfs	472M	1.2M	 470M	  1%	/
efivarfs    	256K	 20K	 232K	  8%	/sys/firmware/efi/efivars
/dev/sda4   	 20G	 32K	  19G	  1%	/mnt/gentoo

There is mention of going to a new tty to be able to do multiple things at once.  With sylve there is a small tab along the left edge of the terminal window at the middle, click on this and you will see six icons.  The one you need to be able to sent sylve the control-alt-f2 is by using the box with an A.  Clicking on that icon will open another panel with control, alt, windows, tab, escape, and ctrl-alt-del.  Click on the control and then the delete, then press your F key to open the new tty.  Deselect the control and alt keys in the panel to clear their use.

After the stage tarball is downloaded using links in the gentoo vm, the command to untar it properly is somewhat hidden but can be found on in the gentoo handbook wiki for the stage install.
tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner -C /mnt/gentoo

Once its all done unpacking, we set the compile flags to help speed up that process when software is built.  Either use nano or vi, both are available, but as he says, be sure to edit the correct file location.  Use the path ./etc/portage/make.conf from within /mnt/gentoo which is where your cwd should be after the untar was completed.  The changes to the make.conf file are meant to improve compile time but one addition, candy, adds a small animation while a process like emerge is running.  I changed or added all the lines mentioned.

COMMON_FLAGS="-march=native -O2 -pipe"
FEATURES="candy parallel-fetch parallel-install"
MAKEOPTS="-j4"

Next we need to make the live iso accessible for the later installations that are done from a chroot into the unfinished install.  It is a crazy bunch of mounts and a sort of control or flow instruction to help maintain access into the chroot environment.  I am only guessing now at exactly what this does but I think its something like that.

mount --types proc /proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --make-rslave /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
mount --make-rslave /mnt/gentoo/dev
mount --bind /run /mnt/gentoo/run
mount --make-slave /mnt/gentoo/run

We enter the chroot with a simple command which seems to also choose our shell.
chroot /mnt/gentoo /bin/bash

Next we fix our prompt, first we source what is in our profile, then we modify it to help us see (remind us) we are in a chroot.
source /etc/profile
export PS1="(chroot) ${PS1}"

The important step after that is to mount the filesystem, each partition, one at a time.  Warning, it seems that the efi partition is not mentioned in the video, so I believe first we make a new directory in the chroot, and then we mount the boot partition to the /efi directory, this is what the handbook wiki describes, but mounting /boot is for BIOS or legacy (non-efi).

mkdir /efi
mount /dev/sda1 /efi
mount /dev/sda2 /home
mount /dev/sda4 /

Before anything more is installed, we need to do the equivalent of initializing the ports tree on FreeBSD, we fetch a current snapshot with one simple command, emerge-webrsync In the video, he mentions how it is described to set the best mirror, this cannot be done until after the emerge-webrsync populates the emerge database.  The tool allows for multiple mirrors to be selected, possibly nearer ones than would otherwise be default.
emerge --ask --verbose --oneshot app-portage/mirrorselect

I selected this long command from the gentoo handbook wiki and then remembered the clipboard icon on the panel I saw when I had used the other keys icon.  I was successful with pasting it into the window that opened for the clipboard, but the text is white on a white background and so it remained invisible until I did a select all first control-a which didn't work, and again with the mouse and I saw the text, but unfortunately it didn't work at all as I hoped.

What we get with the eselect news read is something like our /usr/ports/UPDATING log, but we will only see the more recent postings. Earlier we grabbed a snapshot of the emerge database, next we need to update it to today.
emerge --sync

As a sort of shortcut for preparing the world in gentoo, there are numerous profiles that help to configure settings for how things are built or installed.  Choosing one is easy, first we check what the whole list shows us, then we set our selection to one of them and verfy it after, there is an asterisk (*) on the line of our choice.  Then after this is set, we tell gentoo to install what it describes.

eselect profile list
eselect profile set 3
eselect profile list
emerge --ask --verbose --update --deep --newuse @world

This seemed like the correct choice, generic desktop oriented configuration.  However, this ended every time in an unsolvable circular dependency.  When I switched to profile 1 and used a bit different emerge command, I finally got a successful emerge @world process result.  I believe this is because of one or both of the additional things in the command because a previous attempt using profile 1 had difficulties.
USE='-wayland' emerge --autounmask-write --verbose --update --deep --newuse @world

The first five profile options it listed.

[1]	default/linux/amd64/23.0 (stable)
[2]	default/linux/amd64/23.0/systemd (stable)
[3]	default/linux/amd64/23.0/desktop (stable)
[4]	default/linux/amd64/23.0/desktop/systemd (stable)
[5]	default/linux/amd64/23.0/desktop/gnome (stable)

After that finished, we clean up the dependencies, I think this may be like pkg autoremove on FreeBSD.
emerge --depclean

In the video, nothing is removed but since I had to do things a little differently, there are five things removed.  It looks like two were different (older) versions, and at least two, maybe three, possibly all of them were because I used the mirrorselect application.  Where or why doesn't matter, they were removed and we can go on to the next step.

dev-lang/rust-bin-1.94.1
app-portage/mirrorselect-2.6.4
net-analyzer/netselect-0.4-r2
dev-util/dialog-1.3.20250817

When we get to the next step, we no longer have vi, so as nano is present, we use that to edit /etc/portage/make.conf a second time.  For those like me who do not normally use nano, we may not even have a clue how to save and exit from it.  Someone made a handy cheat cheat for nano that is helpful.  Editing the simple line is easy enough to accomplish, then a control-x will let me out, and it asks if I want to save, and then accept the given filename by pressing return.  You can bet that I will be adding vi as soon as I can if it doesn't get included some other way before we're done.  Only one short line was added to the make.conf file.
USE="-wayland -gnome -kde alsa pulseaudio"

First we need to add a new application to discover what gentoo can see related to the cpu capabilities.
emerge -av cpuid2cpuflags

What it shows me is different than what is in the video.

CPU_FLAGS_X86: aes avx2 bmi1 bmi2 f16c fma3 mmx mmxext pclmul popcnt rdrand sha sse sse2 sse3 sse4_1 sse4_2 sse4a ssse3

We can direct the output of the command into a file I think a bit differently on gentoo than we might have done on FreeBSD but the effect is the same.
echo "*/* $(cpuid2cpuflags)" > /etc/portage/package.use/00cpu-flags

The result we can see when we cat the file is below.

*/* CPU_FLAGS_X86: aes avx2 bmi1 bmi2 f16c fma3 mmx mmxext pclmul popcnt rdrand sha sse sse2 sse3 sse4_1 sse4_2 sse4a ssse3

Once more, we go back to edit the make.conf file, this time to add the license.  We can save a bunch of typing by going backwards in history by pressing the up arrow key, the edit was five or six commands earlier.  I agree with his perspective on licenses, not the somewhat political free versus non-free that is an arguement in Linux, so I edit to add an accept all licenses setting.

ACCEPT_LICENSE="*"

Our next task is to set the timezone, so first we list the available options, assuming America rather than Europe or Asia, Australia, but to see the whole category list, omit America from below.
ls /usr/share/zoneinfo/America

We set this in a new file we create with an echo directed into the file and it can be verified if we cat the timez file.
echo "America/Chicago" > /etc/timez

We are about halfway through the video now, and our timezone will be finalized with one more step.  He has a typo to correct, but ours in bhyve seems to already be solved with a symlink to /etc/localtime.  If it had not been, the command below is what we are told to use.
emerge --config sys-libs/timezone-data

The locale is our next file edit, to be certain at least one UTF8 locale is set.  This is done by uncommenting a line in the file.  As opposed to how it is shown in the video, we will need to search for the line because it is alphabetized with no handy commonly chosen line at the top.  In nano, this is control-f for a forward search, and we get a prompt for the search term, en_US.  Another thing is different in the video compared to how we see it, there is no UTF or ISO-8859 mentioned.  Consulting the handbook wiki tells us there is a few more steps and one includes choosing UTF8 or ISO-8859 or others.  We first uncomment en_US and save the file.The locale-gen command builds and installs two UTF8 locales, C.UTF-8 and en_US.UTF-8, but there is one further step to choose locale.  We can do as he did and choose the en_US.UTF-8.  First show the options with eselect locale list and then finalize our choice with eselect locale set 4 if that is en_US.UTF-8 or your desired locale.  Verify the setting like before with eselect locale list which indicates the selection with an asterisk (*).

[1]	C
[2]	C.UTF-8
[3]	POSIX
[4]	en_US.UTF-8 *
[ ]	(free form)

I chose to copy what was shown on the handbook wiki in the video, it may be the same now but I did not verify.
env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

Now we add the firmware kernel patch file, which actually involves three files for my situation.
emerge --ask --verbose sys-kernel/linux-firmware

The three packages are shown as their file and version and I believe the sort of dependency configuration that relates to it.

app-arch/cpio-2.15::gentoo USE="nls" 1,613 KiB
app-alternatives/cpio-0::gentoo USE="gnu -libarchive (-split-usr)" 0 KiB
sys-kernel/linux-firmware-20260519::gentoo USE="initramfs redistributable -bindist -compress-xz -compress-zstd -duplicate -dist-kernel -saveconfig (-unknown-license)" 608,343 KiB

The recommendation to use a binary kernel seems like the best option for me, since I am very unlikely to play with a custom or gen kernel, plus it is the most foolproof for my needs.  Once again, I had to do things a little bit different in order to finally succeed.  The command that I issued is prefixed with the USES assignment again, and there is the --autounmask-write as well.  As confusing and challenging as this is, there are messages in the feedback when things fail, that instruct me to what needs to be adjusted.
USE='>=sys-kernel/installkernel-68 dracut' emerge -av --autounmask-write sys-kernel/gentoo-kernel-bin

It seems like we are nearly done with the bulk of the preliminary 'get it to boot on its own' setup stuff, but the next step is to check the symbolic link to the installed kernel so other things can find it.  First we need to show what the kernel is that is presently in use, and then we look at what the link currently looks like, and if its correct that is done.

eselect kernel list
[1]	linux-6.18.35-gentoo-dist-bin
ls -lah /usr/src/linux
lrwxrwxrwx 1 root root 29 Jun 27 23:59 /usr/src/linux -> linux-6.18.35-gentoo-dist-bin

We need to edit the /etc/fstab file, to be sure that all partitions are automatically mounted correctly.  My install onto four partitions for my setup means that I add four new lines.

/dev/sda1		/efi		vfat	defaults	0 2	
/dev/sda2		/home		ext4	defaults	1 0
/dev/sda3		swap		swap	sw			0 0
/dev/sda4		/			ext4	defaults	1 0

We set the hostname by simply echo VirtualGentoo >/etc/hostname and then we need to get the dhcp service, although it is spelled slightly different and I had to re-enter the correct name so that dhcpcd would be installed.  Once that is installed, we need to update the rc scripts, or as it says to add service dhcpcd to runlevel default.  For the network interface we need to add netifrc and finally configure to use the network interface itself.  The command ifconfig is the same as for FreeBSD and it gives us the ethernet interface to use, enp0s3.

emerge -av net-misc/dhcpcd
rc-update add dhcpcd default
emerge -av --noreplace net-misc/netifrc
ifconfig

We create the /etc/conf.d/net file to add the line config_enp0s3="dhcp" so that the gentoo vm can obtain an IP address.  Next we change to the /etc/init.d directory to create a symbolic link from net.lo to net.enp0s3 and then we add the interface with rc-update add net.enp0s3 default so that it starts with default runlevel.

The /etc/hosts file needs to be modified to add the hostname to the left of localhost on the 127.0.0.1 line.

Since we are setting up the root password, we must be getting close to finished with all of the initial boot requirements for the gentoo virtual machine.  It is the same command, passwd, and it should be a reasonable password though this is only a virtual machine afterall, though the conditions of the password seem outdated as it expects different characters or classes of character as a function of security while true security involves length as being most effective.  Making a password difficult for the owner to remember will promote possibly insecure ways of remembering like carving it into the frame of the keyboard.

We need to add a system logger by first choosing and installing and then adding it to the default runlevel so it will start at boot.
emerge -av app-admin/sysklogd>br> rc-update add sysklogd default

The cron daemon is skipped but could be added.  I agree that locate is a very useful tool, so I will be installing the gentoo equivalent, mlocate which means I may be adding an alias later so the name difference won't trip me up.  The one thing that is simpler on gentoo, which I could just as easily ease on FreeBSD with an alias, is that the command is just updatedb, but his quick test tells me that I won't need an alias since its also locate.

emerge -av sys-apps/mlocate

I might want the flexibility of secure shell into the virtual machine so I will add that to the default (boot) runlevel.

rc-update add sshd default

Just in case I will add the time synchronization tool, and then add it to boot.
emerge -av net-misc/chrony
rc-update add chronyd default

I agree with him that is would be useful to have the filesystem tools, just in case, installing them now if not already present.

emerge -av sys-fs/e2fsprogs
emerge -av sys-fs/dosfstools

Finally we reach the point of configuring the bootloader.  Since I chose to use an efi boot, the step described to ensure it functions correctly, is an adjustment to the make.conf with an echo.  And after that is set we need to add grub itself, since I did not typo to add an extra quote (") I have nothing to scrutinize.
echo 'GRUB_PLATFORMS="efi-64"' >> /etc/portage/make.conf
emerge -av sys-boot/grub

Now that everything for the boot loader is setup, we just need to finalize it with the last few steps, first the efi portion and then make the configuration.
grub-install --target=x86_64-efi --efi-directory=/efi --removable

This line seems wrong, only because in the video for an efi booted system, he named the partition /boot rather than /efi.  This line refers to a different location in the install, so this is accurate.
grub-mkconfig -o /boot/grub/grub.cfg

We are done.  The last step is to reboot, but I think I will remove the iso from the virtual machine first.  The steps I should follow are below.

exit
cd
umount -l /mnt/gentoo/dev{/shm,/pts,}
umount -R /mnt/gentoo
reboot

Hmm.  Shortly after I typed exit in the virtual machine to leave the chroot, sylve lost access to the console but the machine was still running.  I could see no way to reinitiate the console, and the only options were either shutdown or reboot.  I chose to shutdown, and so I will remove the installer iso, then see what I get when I start it back up.  Whoops, I was dumb.  In order to see more of the console, I collapsed both sidebars to the left.  Since those were collapsed, I was unable to see the console icon or anything else, which magnified the strangeness of what happened after I typed exit.  I have no way to undo what I did, so I will still remove the iso and start up to see what I get.

A simple click on the storage icon, then select the iso from the list, and choose detach and agree to the action.

Success.  Everything worked for me to boot VirtualGentoo and login as root.  Now I have an option to explore the linux side of things to see how those things work when I struggle to solve a problem with porting software to FreeBSD.

Afterthoughts

One thing I noticed while I was configuring the Gentoo virtual mashine install, was that sylve reported only 1GiB of RAM, which I was sure I had set to much larger.  Now that everything is finalized so that I am not stuck in a place that I cannot interrupt, with the vm shutdown I can modify the RAM to 40 GiB and then after I boot VirtualGentoo I can adjust the process number.  Since I gave the virtual machine 40 GiB of RAM, I can increase the MAKEOPTS="-j4" to -j16 since the rule of thumb is 2 GiB per job, which still leaves 8 GiB allocated to the VM that is unused.  Be sure to double-check your Virtual machine configuration in sylve before you commit to the long install process for Gentoo.  Certainly if I had set RAM to more than 1 GiB, all of those emerge steps would have been much speedier, even the 4 jobs I set would have been more efficient.

Gentoo is interesting.  My first install or attempted install was in a VirtualBox.  This install was much the same except for the setup, the interface, and likely the speed.  I began following a newer youtube video but felt that the older video worked fine for me and seemed more familiar with the choices or options and methods described.  My own discussion about what I did, as well as I could annotate the step by step process, hopefully will help those who wish to do the same as I just did.

It seems there is no such thing as either xlibre or true real actual vi, so I will have to make due with what is available to me.  The Gentoo wiki mentions vim and using a symbolic link to it for the same vi function.  I think I can do the exact same thing but with the better yet neovim and a symbolic link.  I am installing FVWM3 which is version 1.1.4 and is only slightly behind as I just discovered that 1.1.5 has been released.  I made my first contribution to FVWM3 just prior to this release! I doubt it could have been much smaller, just one character in one file, but it was a coordinate for an svg image.  Contributions start small, I don't know what the future holds, I am also hoping to contribute to Mudlet to make it more FreeBSD friendly or to help it build more properly for it.  I am not sure if the emerge for FVWM3 on Gentoo is working faster but I expect that it is.  Of course there is much more than just FVWM to get it up and running.  The next emerge is xinit and I will keep at it until I get this VM perfectly usable.  I still have plenty to learn.  Surely following another video (about xorg install) may grant me more success than trying to do it all myself based on what I know on FreeBSD.

About sylve

I definitely can continue to use sylve to open my virtual machine but I might consider a very bare-bones light weight web browser for the purpose, or I may use another method to access the bhyve VM for my use.  Sylve is definitely well featured for making the virtual machine setup an easy task to complete.  The clipboard feature needs to be improved, I saw white on white text and it was not a helpful tool for me the way I hoped it would be.  Sylve did not crash, it was easy enough to understand, and overall it seems to have all of the features necessary for working with a bhyve VM.

Saturday, June 6, 2026

Arduous blog styling overhaul

The incredible arduous task of revising all of my blog posts once more, is and even greater project than it was the last time.  I can only guess that the total number of pots has grown by 15 to 30, but that is not the only reason I have struggled.  I wanted to significantly improve my blog to make it more readable and be a bit more clear about what kind of file or the origins of details.  I began the task with the newset posts first.

My initial idea was to add some images to as many posts as I could, but as a technical blog it is not always possible to tie an image to a subject.  The last time I tried to add images to my blog posts, the battle was against software and how blogger handles its image files.  I had figured it out once before, and I thought I had a way to avoid the online blogger "add image" function in the editor.  Last time my web browser conspired with the blogger site to deny me the ability to upload or if I used google drive, I was unable to reference the files to insert them into the blog.  This time, I had no troubles with librewolf and I chose to simply use the online blogger editor to add the images, then I cut and pasted the chunk of html into bluefish to build around or to insert it in the appropriate location.

Fiddling with CSS can be a challenge but its slightly more complex with blogger, since the theme is also the layout and that includes some links and content that is part of the sections that surround the blog post.  I have known for a while that the layout and the stylesheets were combined, after the first few times that links I added to a sidebar widget either vanished entirely or lost relatively new links.  I have been much more careful since.

After each successive blog entry, going further backwards in time, I would recognize another area that I could improve, a new feature to add into my custom CSS style for the site.  One of the first fancy things I added was the representation of my conversation with Grok about mudlet.  Getting the main div elements styled was easy enough, but then I wanted to create a similar box for the make edits grok reminded me about.  My initial plan to handle it was to use tables, and it did seem to work but getting it to appear precisely styled the way I wished was challenging.  I found mention of using a number of divs and something that felt like brand new tech, the grid system comprised of multiple div containers.

Fiddling with CSS can be a challenge

It took time but eventually I had multiple containers with at least four different purposes and each with a little different color and styling.  Some of my early efforts lead me to struggles later on when my choice to re-style the pre tag and the div tag, although at the time I didn't know it would later bite me.  It seemed like more often than not, I was unable to get an element within my blog post to be positioned as I wished, and sometimes even if the positioning was good, there was size or spacing issues.

The worst mistake was one I couldn't see and that lead me to other changes and attempts to allieviate the issue which failed.  A large chunk of my efforts was accomplished during third shift, so while everyone else slept I was depriving myself of sleep.  This was fine and dandy until while editing both the content of a blog post and the CSS for the blog, I mistakenly pasted the CSS into the blog content online editor tab.  Suddenly the page wouldn't render.  I was reminded of when I used to edit a wiki, and an error broke all rendering.  I looked at the files, the text and the CSS to see if there was a weird typo that would explain it.  I may have corrected things that didn't need correction but luckily I don't think I made things worse.  When I finally discovered that I pasted the CSS into the wrong place, and reverted it with another editor, it was all ecstatically restored.  I think I did that CSS paste into the wrong editor tab at least twice, but after I knew what I did it was easier to correct it.

That was one mistake.  Another mistake is even worse but it went a long time before it was discovered and when I made the adjustment it took a bit to get things back to normal.  What I was trying to do, was to style the sizes of the fonts and was fighting a seemingly impossible battle to see any results.  It took a lot longer than it should have for me to remember that there is a setting in the browser to control font sizes.  This was not quite what I needed to change, but in the settings, in an advanced toggle underneath the font size, is what I had to change.  In the advanced menu there it includes proportional, serif, sans-serif, monospace, sizes for serif and monospace, a tickbox to allow pages to choose their own, and a dropdown for minimum font size.  This minimum font size dropdown goes from 9 to 72, but most importantly has an option for 'none' which cured my problem.  My br9owser was continuously showing a size 9 or 12 or something font which I had configured, and this prevented me from seeing any changes to the font sizes on my blog.

My mistakes of redefining the div and pre tags to any degree but especially regarding formatting such as spacing or padding or margin, are what caused other css adjustments to surreptitiously inherit those changes.  I eventually noticed both, and slowly changed both to affect less and then completely removed the css that modified them, curing it completely.

As much time as it took to setup the div container grid system for a named box, and a series of different named boxes, it took me all that time and more to get a fancy blockquote to look how I wanted.  I found a site online that gave many examples of blockquote styling.  I grabbed an svg quote symbol from one to add to other styling of my own.  I was able to add the svg image to my css, and even could get it to appear.  This was only a small temporary success, since I soon after wanted to have two quote symbols with their curled bits turned toward the inside of the quote at the corners of the blockquote.  This example below is technically gratuitous but illustrates one of the fancier additions to the blog, the other is a revision of it above for a pull quote.  I realized while I was writing this and inserting that block quote, that I could create a left or right positioned pull quote.

I found another site that allowed me to manipulate the svg to flip it along two axis to get the desired effect and then later to enlarge it.  What I discovered shortly after was that the syntax it used was not exactly the right style for my purposes.  Eventually I figured out that I should swap double quotes for single ones, and then it would work fine.  This was only getting me all of the components for my fancy blockquote.  I still had, I would discover, about 8 hours of fiddling to finally perfect the layout to be consistent.  When I had the size of the blockquote box how I preferred, then somehow it was no longer centered, and then when I finally got the blockquote box correct, I struggled to get the quote symbols where I wanted them.  It took a bit, but I eventually went from three columns, to five columns to a grid with fifteen boxes and then simplified down to a three by three grid of nine boxes.

As I write this, I am hopeful that I have perfected enough fancy stylish things that I can adapt them for further needs, or that I have exhausted need of further fancy things.  One fancy thing that I added long before I touched the blockquote styling, was to add a hover text for my embedded images, so that viewers can be subtly be informed that the image can be clicked to enlarge.  Other subtle changes are all over the blog posts that use them, such as changes to colors of the text or changes to the highlight or the notice or other indicators.  I am not yet done with all the potential changes, but I am maybe twentyfive done out of eighty five, and could get inspired for more fancy things.  Surely if I can determine an appropriate image, I would add it to a post.

I have to edit what I thought was a completed post about my efforts to overhaul the style of my blog to add this paragraph to describe one more thing I solved.  I was consistently struggling to get all of the non-text content to be centered, I suspect even the purportedly centered images were not actually properly centered.  This is because after all of this effort, I finally decided to check to see if there could be anything "off" with the layout which I did not yet determine.  What I discovered was a padding difference for the post-outer class that is applied to a div surrounding the body of the text for the blog.  Instead of this having equal left and right padding, it had 15 on the left and 20 on the right.  This small difference is large enough to be noticed, and causes the standard method of centering to fail visually.  This centering method is setting left-margin and right margin to auto.  Since this is now solved, I had to find all of my unequal adjustments and convert any appropriate things to use the auto margin method.

A number of resources have helped me in my quest to use CSS effectively but none more so than the CSS and html resources at W3Schools, and the browser support and testing results at the caniuse site.  Specifically recent sites and sites with tools related to the block quote examples and svg adjustments were also quite helpful.  There are plenty of resources all over the web, W3Schools has a lot more than just CSS and HTML, and color pickers or other references or related tools, there are other web related languages described with nearly the same amount of detail.  Even the humble inspector built into the firefox/mozilla family of browsers has even greater capability than I thought.

The last time I did an overhaul of all the posts of my blog, it was mostly to cleanup the formatting so that it was a bit more consistent.  I made some of the scattered stylish bits more uniform, more consistently applied.  It was a much less involved process and took a lot less time.  I was mostly fixing manually broken lines to be a series to strung together sentences exploiting the auto 'wrap text' feature of my editor.  Right now it is entirely possible that the CSS has changed in ways that older posts are broken or do not display properly.  You may view or refresh a blog post to discover it no longer looks the same, possibly a little different with each refresh of the page.  I hope to get the arduous task accomplished sooner than later but it is a long process.  Some of the older posts may be broken or display improperly for a while, I can only say that I am working on it.  I hope that this round of improvements makes the blog more enjoyable or easier to follow.

Just because the site that hosts your blog has templates, has a way to make limitied adjustments to those theme templates, doesn't mean that there may not also be a way to be even more creative.  If there is an option like on blogger, to edit the theme or CSS (and possibly html) directly, or to import your own, then maybe it is something to consider.  My blog began by using a theme, adjusting it with the built-in mechanisms of sliders and tickboxes, eventually choosing to remove the background image because of its non-uniform colors.  I went from there to refine some adjustments inside the CSS theme editor, and after many more changes and additions, and now further refinements and embellishments, we have what A little Daemon on my Desktop has become.  I still write about what I have been doing, but lately given much more free time to tinker, I have had more opportunity to record it all in my blog.  My improvements are not yet complete, but check back in a while and surely I will have finished.

Saturday, May 30, 2026

Adjusted look at 4k

Right as I was getting ready to write this blog post, I decided to look at the post I made about 6 years ago about High DPI.  I saw a line pertaining to firefox which I wanted to check and test, and then mistyped.  I made the fonts for firefox (librewolf) 85 instead of .85 which meant I had to do some new magic to reduce it back to normal so I could undo the change I just made.  What I did was to adjust one environment variable down to 0.05 and then restart my X session to be sure it took effect.  When I started up my browser, everything was still much too large, but a single F from the File menu item did not fill nearly the whole screen.  So much unexpected fun, as a prelude to writing about the mess that is DPI related settings in the realm of Linux.

Click image to enlarge

We all know that there are multiple standards and multiple methods, and more variation on everything than we can name, which is all crying out for a standard that does not exist or is unheeded.  Some weeks ago I chose to look for any and all environment variables which had any bearing on dpi or dimensionality of applications, their GUI, or how they were sized on the desktop.  Using FVWM means that there is no integrated method that is nearly universal for my entire desktop or apps specifically written for use within it.  KDE has its method primarily involving Qt, and GNOME primarily uses GTK, and there is also SDL which mostly relates to games but could affect multimedia applications.  There is a decently capable configurator for Qt in ports at misc/qt5ct or misc/qt6ct that can be used to adjust font sizes and more.  Firefox, Luanti, Bluefish and likely many other applications have settings for font sizes easily visible, firefox has one more buried in its about:config.

If you are wondering which method is likely to govern how dpi or font sizes might be set, all that is necessary is to check the pkg info for the application, such as pkg info www/librewolf which results with plenty of useful details, but note libgdk.

pkg info
librewolf-151.0.1
Name           : librewolf
Version        : 151.0.1
Installed on   : Fri May 29 00:33:55 2026 CDT
Origin         : www/librewolf
Architecture   : FreeBSD:15:amd64
Prefix         : /usr/local
Categories     : wayland www
Licenses       : MPL20
Maintainer     : freebsd@sysctl.cz
WWW            : https://librewolf.net/
Comment        : Custom version of Firefox, focused on privacy, security and freedom
Options        :
	ALSA           : off
	CANBERRA       : off
	DBUS           : on
	DEBUG          : off
	FFMPEG         : on
	JACK           : on
	LIBPROXY       : off
	LTO            : off
	OPTIMIZED_CFLAGS: on
	PROFILE        : on
	PULSEAUDIO     : on
	SNDIO          : on
	TEST           : off
Shared Libs required:
	libX11-xcb.so.1
	libX11.so.6
	libXcomposite.so.1
	libXdamage.so.1
	libXext.so.6
	libXfixes.so.3
	libXrandr.so.2
	libatk-1.0.so.0
	libc++.so.1
	libc.so.7
	libcairo-gobject.so.2
	libcairo.so.2
	libcxxrt.so.1
	libdbus-1.so.3
	libdl.so.1
	libevent-2.1.so.7
	libffi.so.8
	libfontconfig.so.1
	libfreetype.so.6
	libgcc_s.so.1
	libgdk-3.so.0
	libgdk_pixbuf-2.0.so.0
	libgio-2.0.so.0
	libglib-2.0.so.0
	libgobject-2.0.so.0
	libgtk-3.so.0
	libharfbuzz.so.0
	libjpeg.so.8
	libm.so.5
	libnspr4.so
	libnss3.so
	libnssutil3.so
	libpango-1.0.so.0
	libpixman-1.so.0
	libplc4.so
	libpng16.so.16
	libsmime3.so
	libssl3.so
	libthr.so.3
	libutil.so.10
	libvpx.so.12
	libwebp.so.7
	libwebpdemux.so.2
	libxcb-shm.so.0
	libxcb.so.1
	libz.so.6
Annotations    :
	FreeBSD_version: 1500068
	build_timestamp: 2026-05-26T22:04:59+0000
	built_by       : poudriere-git-3.4.8
	cpe            : cpe:2.3:a:mozilla:librewolf:151.0.1:::::freebsd15:x64
	no_provide_shlib: yes
	port_checkout_unclean: no
	port_git_hash  : b8257e1e1598db33ed01c9850a7218d07ba3fcd7
	ports_top_checkout_unclean: no
	ports_top_git_hash: 04e1cd789391e25aa7c4687d407d2abbdd992286
	repo_type      : binary
	repository     : FreeBSD-ports
Flat size      : 339MiB
Description    :
LibreWolf is a free and open source web browser descended from the
Mozilla Application Suite. It is small, fast and easy to use, and offers
many advanced features:

 o Popup Blocking
 o Tabbed Browsing
 o Live Bookmarks (ie.  RSS)
 o Extensions
 o Themes
 o FastFind
 o Improved Security

As I mentioned before, firefox and librewolf determine their scaling with a GDK variable and the same output for mudlet verifies that its a Qt application.

pkg info
Mudlet-dev-g20260525
Name           : Mudlet-dev
Version        : g20260525
Installed on   : Fri May 29 02:01:23 2026 CDT
Origin         : games/Mudlet-dev
Architecture   : FreeBSD:15:amd64
Prefix         : /usr/local
Categories     : games
Licenses       : GPLv2+
Maintainer     : nope@nothere
WWW            : https://mudlet.org/
Comment        : Cross-platform, open source, super fast MUD client with lua scripting
Shared Libs required:
	libGLU.so.1
	libGLX.so.0
	libOpenGL.so.0
	libQt6Concurrent.so.6
	libQt6Core.so.6
	libQt6Core5Compat.so.6
	libQt6DBus.so.6
	libQt6Gui.so.6
	libQt6Multimedia.so.6
	libQt6MultimediaWidgets.so.6
	libQt6Network.so.6
	libQt6OpenGL.so.6
	libQt6OpenGLWidgets.so.6
	libQt6TextToSpeech.so.6
	libQt6UiTools.so.6
	libQt6Widgets.so.6
	libassimp.so.6
	libc++.so.1
	libc.so.7
	libcxxrt.so.1
	libdl.so.1
	libexecinfo.so.1
	libgcc_s.so.1
	libhunspell-1.7.so.0
	libkvm.so.7
	liblua-5.1.so
	libm.so.5
	libonig.so.5
	libpcre2-8.so.0
	libpugixml.so.1
	libqt6keychain.so.1
	librt.so.1
	libsqlite3.so.0
	libsysinfo.so.0
	libthr.so.3
	libyajl.so.2
	libz.so.6
	libzip.so.5
Annotations    :
	FreeBSD_version: 1501500
Flat size      : 60.9MiB
Description    :
Mudlet is a platform for gaming and enhancing game-play primarily with MUDs.
Mudlet provides a toolkit and supports a wide variety of protocols for players
and creators to tailor an immersive game-playing experience. MUD creators can
use Mudlet to add visual flair or build features into their text games. MUD
players can utilize the Mudlet toolkit to script and automate parts of their
gameplay or add their own visual customization for game data.

Outside the realm of MUD games, Mudlet has even been used to provide
automation and features in 3D games which support in-game chat and a Telnet or
similar server-console protocols.

The group of environment variables that have a bearing upon various UI elements or dimensionality are easily set, but finding a group of settings that is most appealing or mostly comparable may take some fiddling.  I have begun to prefer small favicons on my browser tabs and relatively small font.  What I have configured as a group of settings works fairly nice.  I have read that antialiasing on 4k or above is not so pertinent.  I choose to let Qt control the dpi automatically, mostly, some applications might not follow it or only use it for some portions or only use the qt scale factor method.  These settings attempt to have as much uniformity as possible.  The settings are good for a 4k screen.

csh
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
setenv FC_ANTIALIAS false
setenv FC_SCALE 2.0
setenv GDK_BACKEND x11
setenv GDK_DPI_SCALE 1.0
setenv GDK_SCALE 1.5 
setenv GDK_USE_XFT 1
setenv PLASMA_USE_QT_SCALING 1
setenv QT_AUTO_SCREEN_SCALE_FACTOR 1
setenv QT_ENABLE_HIGHDPI_SCALING 1
setenv QT_QPA_PLATFORM xcb
setenv QT_QPA_PLATFORMTHEME qt6ct
setenv QT_SCALE_FACTOR 2.0
setenv QT_XFT 1
setenv SDL_RENDER_DRIVER opengl
setenv SDL_RENDER_VSYNC 1
setenv SDL_VIDEODRIVER x11
setenv SDL_VIDEO_FULLSCREEN_DISPLAY 0
setenv SDL_VIDEO_X11_SCALING_FACTOR 2.0
setenv SDL_VIDEO_X11_XRANDR 1
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Firefox and librewolf have an about:config setting, layout.css.devPixelsPerPx which I have set to 0.85, and within the browser settings I have fonts set to 14 with minimum of 12, and allow sites to choose their own fonts ticked.  The UI font in bluefish I set to 9, the Editor settings font is set for 9 with a cursor/font ratio% of 20.  Mudlet I have the main display font size set at 9 with antialias ticked.  The qt6ct has General and Fixed width font size 9, with no fonts.conf file kept.  Sometimes fonts get scaled or fail to be scaled, while other parts or the UI fit a high dpi screen decently using one variable or another.

In addition to these environment variables and individual application configurations, I have also added to my .Xdefaults or .Xresources file, Xft.dpi: 150 which affects some of the other things like xterm font size.

The settings as a whole works well for me.  Aside from trying to search for environment variables generally, a large chunk of the HiDPI information is from Arch. We can dream of a day when everything on Linux can handle scaling identically across GTK/GDK and Qt and every other toolkit, with at most an evironment variable to select the system or toolkit, and the two or three for that system or toolkit that determine the scale or dpi of fonts and objects.  Until that day arrives, but it may never arrive, we will have to periodically monitor to see what has changed among all the many methods, so we can get a coherent mostly consistent UI.

Note about minimum font size in firefox
This is a useful feature to force all websites to display a larger font at all times, if their smallest becomes unreadable.  However, this will hide all other attempts to adjust font size such as when developing a web page or changing the appearance of a blog using CSS.  This is something I learned later, when I cleaned up the blog a second time.

Tuesday, May 26, 2026

Sledgehammer mosquito swatter replaced

Before I get the revised Makefile formulated for following upstream releases of mudlet, I tried to have my sledgehammer mosquito swatter solution added to upstream.  What I discovered which I already knew but I needed to be reminded, was that my cure for the problem I had on FreeBSD was both too big of a change and also caused more problems even if it fixed some for myself.  I cancelled the commit pull for my obscene patch and was intending to keep using it myself anyways.  What I ended up doing is spending eight to twelve hours overnight chatting with Grok, applying a proposed fix, rebuilding and testing.

It seemed like the process was a lot of repetitious change-test-fail cycles.  It took a little reinforced focus by telling Grok to look back at what had been done before, to reassess and reevaluate, and eventually move ever closer to a solution.  When it was finally done, Grok gave an overall assessment of why there seems to be more difficulties for FreeBSD.

Excellent! 🎉

The Mudlet client is finally working as desired — the tutorial loads, the UI is functional, and the main Geyser Locale crashes are gone. This is a solid success after a very long and tricky debugging session.

Current Working State

You now have a working combination:

  • C++ early initialization (TLuaInterpreter.cpp)
  • Early dummy Locale in LuaGlobal.lua
  • Constructor-level safety net in GeyserAdjustableContainer.lua

The remaining minor errors (like deleteNamedTimer, registerNamedEventHandler, etc.) are secondary — they are common when core Lua modules load in a non-standard order, but they are not breaking the main interface.

Recommended Final Cleanup for Your Port

Since it works, let's make the patches cleaner and more maintainable.

  1. Keep these three patches (minimal working set):
    • patch-src_TLuaInterpreter.cpp — C++ early Locale init (most important)
    • patch-src_mudlet-lua_lua_LuaGlobal.lua — Early dummy table
    • patch-src_mudlet-lua_lua_geyser_GeyserAdjustableContainer.lua — Constructor safety net

It asked me if I wanted to revise things a bit but I decided not to do that because after all the long hours of trying, it was essentially solved, and solved in a much less destructive way. It replied with the last assessment.

Great news! Mudlet is finally working on your FreeBSD system.

After many hours of debugging, the combination of:

  • Early C++ initialization (TLuaInterpreter.cpp)
  • Early dummy Locale table (LuaGlobal.lua)
  • Constructor-level safety net in GeyserAdjustableContainer.lua

has resolved the critical blocking issues.

Final Recommendations

Since it is now functional, I suggest you keep the current working state and only do light cleanup:

  1. Reduce debug prints (optional but nice for users) — comment out or remove most of the [Mudlet FreeBSD] print statements so they don't spam the main window on every startup.
  2. Ensure your post-stage reliably copies the patched Lua files:
  3. make
    post-stage:
        @${ECHO_MSG} "==> Installing patched Lua files and translations..."
        @${MKDIR} ${STAGEDIR}${PREFIX}/share/mudlet/lua/geyser
        @${MKDIR} ${STAGEDIR}${PREFIX}/share/mudlet/translations
    
        @${CP} -Rp ${WRKDIR}/.build/src/mudlet-lua/lua/* \
                 ${STAGEDIR}${PREFIX}/share/mudlet/lua/ 2>/dev/null || true
    
        @${CP} -p ${WRKDIR}/.build/translations/translated/*.qm \
                 ${STAGEDIR}${PREFIX}/share/mudlet/translations/ 2>/dev/null || true
    			
  4. Keep the three key patches you have now (they are the minimal set that works).

Closing Thoughts

The root cause was a combination of:

  • Qt resource embedding (.qm files) not working reliably on FreeBSD+Ninja builds.
  • Geyser creating AdjustableContainer objects extremely early — before normal Lua initialization completed.

This is a known fragile area on non-Linux platforms with out-of-source builds.

You can now enjoy Mudlet. If you want, later you can refine the patches (remove debug prints, make the dummy table more complete, etc.), but there is no urgent need — it is working.

Congratulations on getting it running after such a long and difficult process! Let me know if anything else comes up.

The patches that were finalized after all of the effort are for three files, mentioned above in the Grok narrative.

patch-src_TLuaInterpreter.cpp
--- src/TLuaInterpreter.cpp.orig	2026-05-25 20:30:41 UTC
+++ src/TLuaInterpreter.cpp
@@ -5988,6 +5988,15 @@ void TLuaInterpreter::loadGlobal()
 #endif
 
     setupLanguageData();
+    // === FreeBSD ports workaround: Force mudlet.Locale very early ===
+    // This runs before any Lua scripts (including Geyser) are loaded
+    luaL_dostring(pGlobalLua, R"lua(
+        mudlet = mudlet or {}
+        mudlet.Locale = mudlet.Locale or {}
+        print("[Mudlet FreeBSD] C++ early Locale initialization successful")
+    )lua");
+    // === End FreeBSD workaround ===
+
 
     const QString executablePath{QCoreApplication::applicationDirPath()};
     // Initialise the list of path and file names so that
patch-src_mudlet-lua_lua_LuaGlobal.lua
--- src/mudlet-lua/lua/LuaGlobal.lua.orig	2026-05-20 20:30:31 UTC
+++ src/mudlet-lua/lua/LuaGlobal.lua
@@ -1,4 +1,46 @@ -- Mudlet Lua packages loader
 -- Mudlet Lua packages loader
+-- === FreeBSD ULTIMATE EARLY LOCALE FIX (Combined) ===
+print("[Mudlet FreeBSD] Installing ULTIMATE early Locale protection in LuaGlobal.lua")
+
+mudlet = mudlet or {}
+mudlet.Locale = mudlet.Locale or {}
+
+-- Comprehensive dummy entries for Geyser / UI
+local dummyEntries = {
+    packageInstallSuccess = { message = "Package '%s' installed successfully." },
+    packageInstallFail    = { message = "Failed to install package '%s': %s" },
+    moduleInstallSuccess  = { message = "Module '%s' installed successfully." },
+    moduleInstallFail     = { message = "Failed to install module '%s': %s" },
+    prefixOk              = { message = "[  OK  ] " },
+    prefixWarn            = { message = "[ WARN ] " },
+    prefixInfo            = { message = "[ INFO ] " },
+    attach                = { message = "Attach" },
+    detach                = { message = "Detach" },
+    lock                  = { message = "Lock" },
+    unlock                = { message = "Unlock" },
+    close                 = { message = "Close" },
+    minimize              = { message = "Minimize" },
+}
+
+for k, v in pairs(dummyEntries) do
+    mudlet.Locale[k] = mudlet.Locale[k] or v
+end
+
+-- Global protection for Geyser AdjustableContainer
+if Geyser and Geyser.AdjustableContainer and Geyser.AdjustableContainer.new then
+    local oldNew = Geyser.AdjustableContainer.new
+    Geyser.AdjustableContainer.new = function(self, ...)
+        local obj = oldNew(self, ...)
+        if not obj.Locale or type(obj.Locale) ~= "table" then
+            obj.Locale = mudlet.Locale
+        end
+        return obj
+    end
+    print("[Mudlet FreeBSD] Geyser AdjustableContainer constructor protected from LuaGlobal")
+end
+
+print("[Mudlet FreeBSD] Early Locale protection completed")
+-- === End ULTIMATE EARLY FIX ===
 
 if package.loaded["rex_pcre2"] then
   rex = require "rex_pcre2"

Remember this one?  Its a very tiny thing compared to that extreme solution I had a little while ago.

patch-src_mudlet-lua_lua_geyser_GeyserAdjustableContainer.lua
--- src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua.orig	2026-05-20 20:30:31 UTC
+++ src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua
@@ -606,6 +606,11 @@ local function createMenus(self, parent, name, func)
 -- @param onClick function which will be executed onClick
 local function createMenus(self, parent, name, func)
     local label = self.adjLabel
+    -- Extra per-call safety
+    self.Locale = self.Locale or mudlet.Locale or {}
+    self.min_restore = self.min_restore or {}
+    self.max_restore = self.max_restore or {}
+	--end Extra per-call safety
     local menuTxt = self.Locale[name] and self.Locale[name].message or name
     label:addMenuLabel(name, parent)
     label:findMenuElement(parent.."."..name):echo(menuTxt, "nocolor")
@@ -1080,11 +1085,51 @@ function Adjustable.Container:new(cons,container)
     me:createLabels()
     me:createRightClickMenu()
 
+    -- === FreeBSD CONSTRUCTOR SAFETY NET ===
+    -- This runs every time a new AdjustableContainer is created
+    if not mudlet.Locale or type(mudlet.Locale) ~= "table" then
+        mudlet.Locale = {}
+    end
+
+    me.Locale = me.Locale or mudlet.Locale
+
+    -- Ensure required sub-tables exist
+    me.min_restore = me.min_restore or {}
+    me.max_restore = me.max_restore or {}
+    me.att = me.att or {}
+
+    -- Ensure common translation keys exist
+    local defaultMsgs = {
+        lock     = { message = "Lock" },
+        min_restore = { message = "Minimize" },
+        save     = { message = "Save" },
+        load     = { message = "Load" },
+        attach   = { message = "Attach" },
+        lockstyle = { message = "Lock Style" },
+        custom   = { message = "Custom Items" },
+    }
+    for k, v in pairs(defaultMsgs) do
+        if not me.Locale[k] or type(me.Locale[k]) ~= "table" then
+            me.Locale[k] = v
+        end
+    end
+    -- === End Constructor Safety Net ===
+
     me:globalLockStyles()
     me.minimized =  me.minimized or false
     me.locked =  me.locked or false
 
     me.adjLabelstyle = me.adjLabelstyle..[[ qproperty-alignment: 'AlignLeft | AlignTop';]]
+
+    -- === FreeBSD Line 1123 Defensive Fix ===
+    if not self.Locale or type(self.Locale) ~= "table" then
+        self.Locale = mudlet.Locale or {}
+    end
+    if not self.min_restore or type(self.min_restore) ~= "table" then
+        self.min_restore = {}
+    end
+    -- === End Line 1123 Fix ===
+
     me.lockLabel.txt = me.lockLabel.txt or [[<font size="5" face="Noto Emoji">🔒</font>]] .. self.Locale.lock.message
     me.minLabel.txt = me.minLabel.txt or [[<font size="5" face="Noto Emoji">🗕</font>]] ..self.Locale.min_restore.message
     me.saveLabel.txt = me.saveLabel.txt or [[<font size="5" face="Noto Emoji">💾</font>]].. self.Locale.save.message

It is possible that these much smaller patches might be accepted by upstream mudlet developers.  They provide a sort of temporary fix for FreeBSD users who would like to use mudlet and enjoy the complete experience.  I was told that I may be one of the very few FreeBSD users who have chosen to use mudlet, but compared to the person who was mentioned, FreeBSD is my primary OS and its on my primary box.

The patches may not be perfect, or possibly the upstream code still has some rough edges, regardless, it works for our "beastie" now.  The sledgehammer I used to swat a mosquito is retired, small simpler patches have appeared in its place.  The solution may touch multiple files but they cause much less disruption.  I can try to submit a PR and see what happens.

Its always best to assist upstream with getting software to function on FreeBSD than to continuously maintain patches and work-arounds which can break unexpectedly at some future date.  Pushing to get better FreeBSD upstream support helps upstream also, because they may learn how to adapt for our ways of doing things.  Our way is our way, it is not the only way nor is it necessarily the best way (though we likely believe so) but the best way forward is to do as much as we can to help upstream see it and us.

Sunday, May 24, 2026

Fixed mudlet UI capability

I successfully built mudlet using the FreeBSD ports tree framework and mechanisms.  It installed and ran, but what I didn't know until after I was trying to play on a mud server, was that some capablity was missing.  What I saw was not very friendly, and I fought with the mapping system to try to understand that as well.  The mud showed me its fancy ansi colors and the mapping mechanism allowed me to use the numpad to move in all those cardinal directions.  What I saw then was nothing compared to what I see now and the function mudlet has for me now.

Click image to enlarge

Above is what I gained, but to get there I went back to hacking on the build of mudlet to see if anything I did was the reason the interface was lacking and different scripts gave errors instead of function.  I had to figure out why all that I could see was what I discovered to be a broken UI.  I had to figure out what was actually wrong.

Click image to enlarge

That grey box I could cause to be hidden, and I could use the map that was present but I didn't know how to create more maps or if I could.  I fought with mudlet while playing on the server anyway, knowing something was incomplete or missing.  I went through my Makefile and switched things from luarocks provided over to obtained by ports, I changed numerous things in the Makefile, back and forth, and either I caused more scripts to show as a bug or the same one.

Click image to enlarge

I saw the error "Lua syntax error:...hare/mudlet/lua/geyser/GeyserAdjustableContainer.lua:609: attempt to index field 'Locale' (a nil value)" and put all focus on one word, Locale.  I believed it was related to localization but it turned out that after all effort to cure that issue, I was wrong.  After more banging my head against a brick wall, I decided that maybe a code checker site for lua would help me.  I don't know lua, but possibly the issue really is a syntax error as the message says.  I found a site that helped me, with AI, and gave plenty of instruction about why each bit that it fixed was not best practice or apt to cause the nil value error.

I used the corrected code result the site provided to me as the revised version of the file GeyserAdjustableContainer.lua and used GeyserAdjustableContainer.lua.orig as the untouched original file, generated a massive patch with make makepatch and then cleaned up to reinstall.

patch-src_mudlet-lua_lua_geyser_GeyserAdjustableContainer.lua
--- src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua.orig	2026-05-14 17:24:18 UTC
+++ src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua
@@ -10,30 +10,44 @@ Adjustable = Adjustable or {}
 
 Adjustable = Adjustable or {}
 
-Adjustable.Container = Adjustable.Container or Geyser.Container:new({name = "AdjustableContainerClass"})
+Adjustable.Container = Adjustable.Container or Geyser.Container:new({
+    name = "AdjustableContainerClass",
+    padding = 10,
+    buttonsize = 20,
+    adjLabelstyle = "background-color:rgba(0,0,0,100); border: 1px solid grey;",
+    titleTxtColor = "green",
+    titleFormat = "l",
+    attachedMargin = 0,
+})
 
-local adjustInfo = {}
+-- Static/Default Locale Table
+Adjustable.Container.Locale = Adjustable.Container.Locale or {
+    connectTo = { message = "Connect To: " },
+    disconnect = { message = "Disconnect " },
+    top = { message = "Top" },
+    bottom = { message = "Bottom" },
+    left = { message = "Left" },
+    right = { message = "Right" }
+}
 
+-- Registry for attached containers
+Adjustable.Container.Attached = Adjustable.Container.Attached or {
+    top = {}, bottom = {}, left = {}, right = {}
+}
+
 -- Internal function to add "%" to a value and round it
--- Resulting percentage has five precision points to ensure accurate 
--- representation in pixel space.
--- @param num Any float.  For 0-100% output, use 0.0-1.0
 local function make_percent(num)
     return string.format("%.5f%%", (num * 100))
 end
 
--- Internal function: checks where the mouse is at on the Label
--- and saves the information for further use at resizing/repositioning
--- also changes the mousecursor for easier use of the resizing/repositioning functionality
--- @param self the Adjustable.Container it self
--- @param label the Label which allows the Container to be adjustable
--- @param event Mouse Click event and its infomations
+-- Internal function: checks mouse position and sets state
 local function adjust_Info(self, label, event)
     local x, y = getMousePosition()
     local w, h = self.adjLabel:get_width(), self.adjLabel:get_height()
     local x1, y1 = x - event.x, y - event.y
     local x2, y2 = x1 + w, y1 + h
     local left, right, top, bottom = event.x <= 10, x >= x2 - 10, event.y <= 3, y >= y2 - 10
+    
     if right and left then left = false end
     if top and bottom then top = false end
 
@@ -51,25 +65,29 @@ local function adjust_Info(self, label, event)
         end
     end
 
-    adjustInfo = {name = adjustInfo.name, top = top, bottom = bottom, left = left, right = right, x = x, y = y, move = adjustInfo.move}
+    self.adjustInfo = {
+        name = self.name, 
+        top = top, 
+        bottom = bottom, 
+        left = left, 
+        right = right, 
+        x = x, 
+        y = y, 
+        move = (self.adjustInfo and self.adjustInfo.move)
+    }
 end
 
---- function to give your adjustable container a new title
--- @param text new title text
--- @param color title text color
--- @param format A format list to use. 'c' - center, 'l' - left, 'r' - right,  'b' - bold, 'i' - italics, 'u' - underline, 's' - strikethrough,  '##' - font size.   For example, "cb18" specifies center bold 18pt font be used.   Order doesn't matter.
 function Adjustable.Container:setTitle(text, color, format)
     self.titleFormat = format or self.titleFormat or "l"
-    self.titleText = text or self.titleText or string.format("%s - Adjustable Container")
+    self.titleText = text or self.titleText or string.format("%s - Adjustable Container", self.name)
     self.titleTxtColor = color or self.titleTxtColor or "green"
+    
     if self.locked and (self.connectedContainers or self.lockStyle == "standard" or self.lockStyle == "border" or self.lockStyle == "full") then
         return
     end
     self.adjLabel:echo(string.format("  %s", self.titleText), self.titleTxtColor, self.titleFormat)
 end
 
-
---- function to reset your adjustable containers title to default
 function Adjustable.Container:resetTitle()
     self.titleText = nil
     self.titleTxtColor = nil
@@ -77,65 +95,58 @@ end
     self:setTitle()
 end
 
--- internal function to handle the onClick event of main Adjustable.Container Label
--- @param label the main Adjustable.Container Label
--- @param event the onClick event and its information
 function Adjustable.Container:onClick(label, event)
+    self.adjustInfo = self.adjustInfo or {}
     if label.cursorShape == "OpenHand" then
         label:setCursor("ClosedHand")
     end
-    if event.button == "LeftButton" and not(self.locked and not self.connectedContainers) then
+    
+    if event.button == "LeftButton" and not (self.locked and not self.connectedContainers) then
         if self.raiseOnClick then
             self:raiseAll()
         end
-        adjustInfo.name = label.name
-        adjustInfo.move = not (adjustInfo.right or adjustInfo.left or adjustInfo.top or adjustInfo.bottom)
-        if self.minimized then adjustInfo.move = true end
+        self.adjustInfo.name = label.name
+        self.adjustInfo.move = not (self.adjustInfo.right or self.adjustInfo.left or self.adjustInfo.top or self.adjustInfo.bottom)
+        if self.minimized then self.adjustInfo.move = true end
         adjust_Info(self, label, event)
     end
+    
     if event.button == "RightButton" then
-        --if not in the Geyser main window attach Label is not needed and will be removed
-        if self.container ~= Geyser and table.index_of(self.rCLabel.nestedLabels, self.attLabel) then
+        if self.container ~= Geyser and self.rCLabel and table.index_of(self.rCLabel.nestedLabels or {}, self.attLabel) then
             label:hideMenuLabel("attLabel")
-            -- if we are back to the Geyser main window attach Label will be re-added
-        elseif self.container == Geyser and not table.index_of(self.rCLabel.nestedLabels, self.attLabel) then
+        elseif self.container == Geyser and self.rCLabel and not table.index_of(self.rCLabel.nestedLabels or {}, self.attLabel) then
             label:showMenuLabel("attLabel") 
         end
 
-        if not self.customItemsLabel.nestedLabels then
+        if self.customItemsLabel and (not self.customItemsLabel.nestedLabels or #self.customItemsLabel.nestedLabels == 0) then
             label:hideMenuLabel("customItemsLabel")
-        else
+        elseif self.customItemsLabel then
             label:showMenuLabel("customItemsLabel")
         end
     end
-    label:onRightClick(event)
+    
+    if label.onRightClick then label:onRightClick(event) end
 end
 
--- internal function to handle the onRelease event of main Adjustable.Container Label
---- raises an event "AdjustableContainerRepositionFinish", passed values (name, width, height, x, y)
--- @param label the main Adjustable.Container Label
--- @param event the onRelease event and its information
-function Adjustable.Container:onRelease (label, event)
-    if event.button == "LeftButton" and adjustInfo ~= {} and adjustInfo.name == label.name then
+function Adjustable.Container:onRelease(label, event)
+    if event.button == "LeftButton" and self.adjustInfo and self.adjustInfo.name == label.name then
         if label.cursorShape == "ClosedHand" then
             label:setCursor("OpenHand")
         end
         raiseEvent(
           "AdjustableContainerRepositionFinish",
           self.name,
-          self.get_width(),
-          self.get_height(),
-          self.get_x(),
-          self.get_y()
+          self:get_width(),
+          self:get_height(),
+          self:get_x(),
+          self:get_y()
         )
-        adjustInfo = {}
+        self.adjustInfo = {}
     end
 end
 
--- internal function to handle the onMove event of main Adjustable.Container Label
--- @param label the main Adjustable.Container Label
--- @param event the onMove event and its information
-function Adjustable.Container:onMove (label, event)
+function Adjustable.Container:onMove(label, event)
+    self.adjustInfo = self.adjustInfo or {}
     if self.locked and not self.connectedContainers then
         if label.cursorShape ~= 0 then
             label:resetCursor()
@@ -143,236 +154,182 @@ function Adjustable.Container:onMove (label, event)
         return
     end
     
-    if adjustInfo.move == nil then
+    if self.adjustInfo.move == nil then
         adjust_Info(self, label, event)
     end
 
     if self.connectedToBorder then
         for k in pairs(self.connectedToBorder) do
-            if adjustInfo[k] then
+            if self.adjustInfo[k] then
                 label:resetCursor()
                 return
             end
         end
     end
 
-    if adjustInfo.x and adjustInfo.name == label.name then
+    if self.adjustInfo.x and self.adjustInfo.name == label.name then
         self:adjustBorder()
         local x, y = getMousePosition()
         local winw, winh = getMainWindowSize()
-        local x1, y1, w, h = self.get_x(), self.get_y(), self:get_width(), self:get_height()
+        local x1, y1, w, h = self:get_x(), self:get_y(), self:get_width(), self:get_height()
+        
         if (self.container) and (self.container ~= Geyser) then
-            x1,y1 = x1-self.container.get_x(), y1-self.container.get_y()
-            winw, winh = self.container.get_width(), self.container.get_height()
+            x1, y1 = x1 - self.container:get_x(), y1 - self.container:get_y()
+            winw, winh = self.container:get_width(), self.container:get_height()
         end
-        local dx, dy = adjustInfo.x - x, adjustInfo.y - y
+        
+        local dx, dy = self.adjustInfo.x - x, self.adjustInfo.y - y
         local max, min = math.max, math.min
         local hasScrollBox = self.windowname and Geyser.parentWindows and Geyser.parentWindows[self.windowname] and Geyser.parentWindows[self.windowname].type == "scrollBox"if adjustInfo.move and not self.connectedContainers then
+        
+        if self.adjustInfo.move and not self.connectedContainers then
             label:setCursor("ClosedHand")
-            local tx, ty = max(0,x1-dx), max(0,y1-dy)
-            -- get rid of move/size limits when in scrollbox (as it is scrollable)
-            if not(hasScrollBox) then
+            local tx, ty = max(0, x1 - dx), max(0, y1 - dy)
+            if not hasScrollBox then
                 tx, ty = min(tx, winw - w), min(ty, winh - h)
             end
-            tx = make_percent(tx/winw)
-            ty = make_percent(ty/winh)
-            self:move(tx, ty)
-            --[[
-            -- automated lock on border deactivated for now
-            if x1-dx <-5 then self:attachToBorder("left") end
-            if y1-dy <-5 then self:attachToBorder("top") end
-            if winw - w < tx+0.1 then self:attachToBorder("right") end
-            if winh - h < ty+0.1 then self:attachToBorder("bottom") end--]]
-        elseif adjustInfo.move == false then
+            self:move(make_percent(tx / winw), make_percent(ty / winh))
+        elseif self.adjustInfo.move == false then
             local w2, h2, x2, y2 = w - dx, h - dy, x1 - dx, y1 - dy
             local tx, ty, tw, th = x1, y1, w, h
-            if adjustInfo.top then
+            if self.adjustInfo.top then
                 ty, th = y2, h + dy
-            elseif adjustInfo.bottom then
+            elseif self.adjustInfo.bottom then
                 th = h2
             end
-            if adjustInfo.left then
+            if self.adjustInfo.left then
                 tx, tw = x2, w + dx
-            elseif adjustInfo.right then
+            elseif self.adjustInfo.right then
                 tw = w2
             end
-            tx, ty, tw, th = max(0,tx), max(0,ty), max(10,tw), max(10,th)
-            if not(hasScrollBox) then
+            tx, ty, tw, th = max(0, tx), max(0, ty), max(10, tw), max(10, th)
+            if not hasScrollBox then
                 tw, th = min(tw, winw), min(th, winh)
-                tx, ty = min(tx, winw-tw), min(ty, winh-th)
+                tx, ty = min(tx, winw - tw), min(ty, winh - th)
             end
-            tx = make_percent(tx/winw)
-            ty = make_percent(ty/winh)
-            self:move(tx, ty)
-            local minw, minh = 0,0
-            if self.container == Geyser and not self.noLimit then minw, minh = 75,25 end
-            tw,th = max(minw,tw), max(minh,th)
-            tw,th = make_percent(tw/winw), make_percent(th/winh)
-            self:resize(tw, th)
+            self:move(make_percent(tx / winw), make_percent(ty / winh))
+            
+            local minw, minh = 0, 0
+            if self.container == Geyser and not self.noLimit then minw, minh = 75, 25 end
+            tw, th = max(minw, tw), max(minh, th)
+            self:resize(make_percent(tw / winw), make_percent(th / winh))
+            
             if self.connectedContainers then
                 self:adjustConnectedContainers()
             end
         end
-        adjustInfo.x, adjustInfo.y = x, y
+        self.adjustInfo.x, self.adjustInfo.y = x, y
     end
 end
 
--- internal function to check which valid attach position the container is at
 function Adjustable.Container:validAttachPositions()
     local winw, winh = getMainWindowSize()
     local found_positions = {}
-    if  (winh*0.8)-self.get_height()<= self.get_y()  then  found_positions[#found_positions+1] = "bottom" end
-    if  (winw*0.8)-self.get_width() <= self.get_x() then  found_positions[#found_positions+1] = "right" end
-    if self.get_y() <= winh*0.2 then found_positions[#found_positions+1] = "top" end
-    if self.get_x() <= winw*0.2 then found_positions[#found_positions+1] = "left" end
+    if (winh * 0.8) - self:get_height() <= self:get_y() then found_positions[#found_positions+1] = "bottom" end
+    if (winw * 0.8) - self:get_width() <= self:get_x() then found_positions[#found_positions+1] = "right" end
+    if self:get_y() <= winh * 0.2 then found_positions[#found_positions+1] = "top" end
+    if self:get_x() <= winw * 0.2 then found_positions[#found_positions+1] = "left" end
     return found_positions
 end
 
--- internal function to adjust the main console borders if needed
 function Adjustable.Container:adjustBorder()
     local winw, winh = getMainWindowSize()
-    local where = false
+    if type(self.attached) ~= "string" then return false end
 
-    if type(self.attached) ~= "string" then
-        return false
-    end
-
-    where = self.attached:lower()
-    if table.contains(self:validAttachPositions(), where) == false or self.minimized or self.hidden then 
+    local where = self.attached:lower()
+    local valid = self:validAttachPositions()
+    if not table.contains(valid, where) or self.minimized or self.hidden then 
         self:detach()
         return
     end
 
-    if  where == "right" then 
-        self.borderSize = winw+self.attachedMargin-self.get_x()
-    elseif  where == "left"    then
-        self.borderSize =  self.get_width()+self.get_x()+self.attachedMargin
-    elseif  where == "bottom"  then 
-        self.borderSize = winh+self.attachedMargin-self.get_y()
-    elseif  where == "top"     then 
-        self.borderSize = self.get_height()+self.get_y()+self.attachedMargin
+    if where == "right" then 
+        self.borderSize = winw + self.attachedMargin - self:get_x()
+    elseif where == "left" then
+        self.borderSize = self:get_width() + self:get_x() + self.attachedMargin
+    elseif where == "bottom" then 
+        self.borderSize = winh + self.attachedMargin - self:get_y()
+    elseif where == "top" then 
+        self.borderSize = self:get_height() + self:get_y() + self.attachedMargin
     else
         self.attached = false
         return
     end
+
     local borderSize = self.borderSize
-    for k,v in pairs(Adjustable.Container.Attached[where]) do
-        if v.borderSize > borderSize then
+    for k, v in pairs(Adjustable.Container.Attached[where] or {}) do
+        if v.borderSize and v.borderSize > borderSize then
             borderSize = v.borderSize
         end
     end
-    local funcname = string.format("setBorder%s", string.title(where))
-    _G[funcname](borderSize)
+    
+    local funcname = string.format("setBorder%s", where:gsub("^%l", string.upper))
+    if _G[funcname] then _G[funcname](borderSize) end
 end
 
--- internal function to adjust connected containers
 function Adjustable.Container:adjustConnectedContainers()
     local where = self.attached
-    local x, y, height, width = self.x, self.y, self.height, self.width
-    if not where or not self.connectedContainers then
-        return false
-    end
-    for k in pairs(self.connectedContainers) do
-        local container = Adjustable.Container.all[k]
-        if container then
-            if container.attached == where then
-                if where == "right" or where == "left" then
-                    height = nil
-                    y = nil
-                end
-                if where == "top" or where == "bottom" then
-                    width = nil
-                    x = nil
-                end
-                container:move(x, y)
-                container:resize(width, height)
-            else
-                if where == "right" then
-                    container:resize(self:get_x() - container:get_x(), nil)
-                end
-                if where == "left" then
-                    local right_x = container:get_x() + container:get_width()
-                    local left_x = self:get_x() + self:get_width()
-                    container:move(left_x, nil)
-                    container:resize(right_x - container:get_x(), nil)
-                end
-                if where == "bottom" then
-                    container:resize(nil, self:get_y() - container:get_y())
-                end
-                if where == "top" then
-                    local bottom_y = container:get_y() + container:get_height()
-                    local top_y = self:get_y() + self:get_height()
-                    container:move(nil, top_y)
-                    container:resize(nil, bottom_y - container:get_y())
-                end
-            end
+    if not where or not self.connectedContainers then return false end
+    -- Implementation of container shifting based on parent movement
+    for k, _ in pairs(self.connectedContainers) do
+        local container = Adjustable.Container.Attached[where][k]
+        if container and container ~= self then
             container:adjustBorder()
         end
     end
 end
 
---- connect your container to a border
--- @param border main border ("top", "bottom", "left", "right")
 function Adjustable.Container:connectToBorder(border)
-    if not self.attached or not Adjustable.Container.Attached[border] then
-        return
-    end
+    if not self.attached or not Adjustable.Container.Attached[border] then return end
     self.connectedToBorder = self.connectedToBorder or {}
     self.connectedToBorder[border] = true
     self.connectedContainers = self.connectedContainers or {}
-    for k,v in pairs(Adjustable.Container.Attached[border]) do
+    for k, v in pairs(Adjustable.Container.Attached[border]) do
         v.connectedContainers = v.connectedContainers or {}
         v.connectedContainers[self.name] = true
         if self.attached == border then
             v.connectedToBorder = v.connectedToBorder or {}
             v.connectedToBorder[border] = true
-            self.connectedContainers[k] = v
+            self.connectedContainers[k] = true
         end
-        v:adjustConnectedContainers()
     end
 end
 
---- adds elements to connect containers to borders into the right click menu
 function Adjustable.Container:addConnectMenu()
     local label = self.adjLabel
-    -- Check if menu already exists to prevent duplicates when called multiple times
-    if label:findMenuElement("Connect To: ") then
-        return
-    end
+    if not label or label:findMenuElement("Connect To: ") then return end
+    
     local menuTxt = self.Locale.connectTo.message
     label:addMenuLabel("Connect To: ")
     label:findMenuElement("Connect To: "):echo(menuTxt, "nocolor", "c")
+    
     local menuParent = self.rCLabel.MenuItems
     menuParent[#menuParent + 1] = {"top", "bottom", "left", "right"}
-    self.rCLabel.MenuWidth3 = self.ChildMenuWidth
-    self.rCLabel.MenuFormat3 = self.rCLabel.MenuFormat2
+    
     label:createMenuItems()
-    for  k,v in ipairs(menuParent[#menuParent]) do
-        menuTxt = self.Locale[v] and self.Locale[v].message or v
-        label:findMenuElement("Connect To: ."..v):echo(menuTxt, "nocolor")
-        label:setMenuAction("Connect To: ."..v, function() closeAllLevels(self.rCLabel) self:connectToBorder(v) end)
+    for _, v in ipairs(menuParent[#menuParent]) do
+        local subTxt = self.Locale[v] and self.Locale[v].message or v
+        label:findMenuElement("Connect To: ."..v):echo(subTxt, "nocolor")
+        label:setMenuAction("Connect To: ."..v, function() 
+            if closeAllLevels then closeAllLevels(self.rCLabel) end
+            self:connectToBorder(v) 
+        end)
     end
-    menuTxt = self.Locale.disconnect.message
+    
     label:addMenuLabel("Disconnect ")
-    label:setMenuAction("Disconnect ", function() closeAllLevels(self.rCLabel) self:disconnect() end)
-    label:findMenuElement("Disconnect "):echo(menuTxt, "nocolor", "c")
+    label:setMenuAction("Disconnect ", function() 
+        if closeAllLevels then closeAllLevels(self.rCLabel) end
+        self:disconnect() 
+    end)
+    label:findMenuElement("Disconnect "):echo(self.Locale.disconnect.message, "nocolor", "c")
 end
 
---- disconnects your container from a border
 function Adjustable.Container:disconnect()
-    if not self.connectedToBorder then
-        return
-    end
-    for k in pairs(self.connectedToBorder) do
-        if Adjustable.Container.Attached[k] then
-            for k1,v1 in pairs(Adjustable.Container.Attached[k]) do
-                if v1.connectedContainers and v1.connectedContainers[self.name] then
-                    v1.connectedContainers[self.name] = nil
-                    if table.is_empty(v1.connectedContainers) then
-                        v1.connectedContainers = nil
-                    end
-                end
+    if not self.connectedToBorder then return end
+    for border, _ in pairs(self.connectedToBorder) do
+        for _, v1 in pairs(Adjustable.Container.Attached[border] or {}) do
+            if v1.connectedContainers then
+                v1.connectedContainers[self.name] = nil
             end
         end
     end
@@ -380,808 +337,135 @@ end
     self.connectedContainers = nil
 end
 
---- gives your MainWindow borders a margin
--- @param margin in pixel
 function Adjustable.Container:setBorderMargin(margin)
     self.attachedMargin = margin
     self:adjustBorder()
 end
 
--- internal function to resize the border automatically if the window size changes
 function Adjustable.Container:resizeBorder()
     local winw, winh = getMainWindowSize()
-    self.timer_active = self.timer_active or true
-    -- Check if Window resize already happened.
-    -- If that is not checked this creates an infinite loop and crashes because setBorder also causes a resize event
-    if (winw ~= self.old_w_value or winh ~= self.old_h_value) and self.timer_active then
-        self.timer_active = false
-        tempTimer(0.2, function() self:adjustBorder() self:adjustConnectedContainers() end)
+    if (winw ~= self.old_w_value or winh ~= self.old_h_value) then
+        if not self.timer_active then
+            self.timer_active = true
+            tempTimer(0.2, function() 
+                self:adjustBorder() 
+                self:adjustConnectedContainers() 
+                self.timer_active = false
+            end)
+        end
     end
-    self.old_w_value = winw
-    self.old_h_value = winh
+    self.old_w_value, self.old_h_value = winw, winh
 end
 
---- attaches your container to the given border
--- attach is only possible if the container is located near the border
--- @param border possible border values are "top", "bottom", "right", "left"
 function Adjustable.Container:attachToBorder(border)
     if self.attached then self:detach() end
     Adjustable.Container.Attached[border] = Adjustable.Container.Attached[border] or {}
     Adjustable.Container.Attached[border][self.name] = self
     self.attached = border
     self:adjustBorder()
-    self.resizeHandlerID=registerAnonymousEventHandler("sysWindowResizeEvent", function() self:resizeBorder() end)
-    closeAllLevels(self.rCLabel)
+    self.resizeHandlerID = registerAnonymousEventHandler("sysWindowResizeEvent", function() self:resizeBorder() end)
 end
 
---- detaches the given container
--- this means the mudlet main window border will be reset
 function Adjustable.Container:detach()
-    if Adjustable.Container.Attached and Adjustable.Container.Attached[self.attached] then
+    if self.attached and Adjustable.Container.Attached[self.attached] then
         Adjustable.Container.Attached[self.attached][self.name] = nil
+        self:resetBorder(self.attached)
     end
     self.borderSize = nil
-    self:resetBorder(self.attached)
-    self.attached=false
+    self.attached = false
     if self.resizeHandlerID then killAnonymousEventHandler(self.resizeHandlerID) end
 end
 
--- internal function to reset the given border
--- @param where possible border values are "top", "bottom", "right", "left"
 function Adjustable.Container:resetBorder(where)
     local resetTo = 0
-    if not Adjustable.Container.Attached[where] then
-        return
-    end
-    for k,v in pairs(Adjustable.Container.Attached[where]) do
-        if v.borderSize > resetTo then
+    for _, v in pairs(Adjustable.Container.Attached[where] or {}) do
+        if v.borderSize and v.borderSize > resetTo then
             resetTo = v.borderSize
         end
     end
-    if        where == "right"   then setBorderRight(resetTo)
-    elseif  where == "left"    then setBorderLeft(resetTo)
-    elseif  where == "bottom"  then setBorderBottom(resetTo)
-    elseif  where == "top"     then setBorderTop(resetTo)
-    end
+    local func = _G["setBorder" ..  where:gsub("^%l", string.upper)]
+    if func then func(resetTo) end
 end
 
--- creates the adjustable label and the container where all the elements will be put in
 function Adjustable.Container:createContainers()
     self.adjLabel = Geyser.Label:new({
-        x = "0",
-        y = "0",
-        height = "100%",
-        width = "100%",
+        x = 0, y = 0, height = "100%", width = "100%",
         name = self.name.."adjLabel"
-    },self)
+    }, self)
+    self.adjLabel:setStyleSheet(self.adjLabelstyle)
+    
     self.Inside = Geyser.Container:new({
-        x = self.padding,
-        y = self.padding*2,
-        height = "-"..self.padding,
-        width = "-"..self.padding,
+        x = self.padding, y = self.padding * 2,
+        height = "-" ..  self.padding, width = "-" ..  self.padding,
         name = self.name.."InsideContainer"
-    },self)
+    }, self)
 end
 
---- locks your adjustable container
---lock means that your container is no longer moveable/resizable by mouse.  
---You can also choose different lockStyles which changes the border or container style.  
---if no lockStyle is added "standard" style will be used 
--- @param lockNr the number of the lockStyle [optional]
--- @param lockStyle the lockstyle used to lock the container, 
--- the lockStyle is the behaviour/mode of the locked state.
--- integrated lockStyles are "standard", "border", "full" and "light" (default "standard")
--- standard:    This is the default lockstyle, with a small margin on top to keep the right click menu usable.
--- light:       Only hides the min/restore and close labels.  Borders and margin are not affected.
--- full:        The container gets fully locked without any margin left for the right click menu.
--- border:      Keeps the borders of the container visible while locked.
-
 function Adjustable.Container:lockContainer(lockNr, lockStyle)
-    closeAllLevels(self.rCLabel)
-
-    if type(lockNr) == "string" then
-      lockStyle = lockNr
-    elseif type(lockNr) == "number" then
-      lockStyle = self.lockStyles[lockNr][1]
-    end
-
-    lockStyle = lockStyle or self.lockStyle
-    if not self.lockStyles[lockStyle] then
-      lockStyle = "standard"
-    end
-
-    self.lockStyle = lockStyle
-
-    if self.minimized == false then
-        self.lockStyles[lockStyle][2](self)
-        self.exitLabel:hide()
-        self.minimizeLabel:hide()
-        self.locked = true
-        self:adjustBorder()
-    end
+    if type(lockNr) == "string" then lockStyle = lockNr end
+    self.lockStyle = lockStyle or "standard"
+    self.locked = true
+    self.exitLabel:hide()
+    self.minimizeLabel:hide()
+    self:adjustBorder()
 end
 
--- internal function to handle the custom Items onClick event
--- @param customItem the item clicked at
-function Adjustable.Container:customMenu(customItem)
-    closeAllLevels(self.rCLabel)
-    if self.minimized == false then
-        self.customItems[customItem][2](self)
-    end
-end
-
---- unlocks your previous locked container
--- what means that the container is moveable/resizable by mouse again 
 function Adjustable.Container:unlockContainer()
-    closeAllLevels(self.rCLabel)
-    self.Inside:resize("-"..self.padding,"-"..self.padding)
+    self.locked = false
+    self.Inside:resize("-"..self.padding, "-"..self.padding)
     self.Inside:move(self.padding, self.padding*2)
-    self.adjLabel:setStyleSheet(self.adjLabelstyle)
     self.exitLabel:show()
     self.minimizeLabel:show()
-    self.locked = false
     self:setTitle()
 end
 
---- sets the padding of your container
--- changes how far the the container is positioned from the border of the container 
--- padding behaviour also depends on your lockStyle
--- @param padding the padding value (standard is 10)
-function Adjustable.Container:setPadding(padding)
-    self.padding = padding
-    if self.locked then
-        self:lockContainer()
-    else
-        self:unlockContainer()
-    end
-end
-
--- internal function: onClick Lock event
-function Adjustable.Container:onClickL()
-    if self.locked == true then
-        self:unlockContainer()
-    else
-        self:lockContainer()
-    end
-end
-
--- internal function: adjusts/sets the borders if an container gets hidden
-function Adjustable.Container:hideObj()
-    self:hide()
-    self:adjustBorder()
-end
-
--- internal function: onClick minimize event
-function Adjustable.Container:onClickMin()
-    closeAllLevels(self.rCLabel)
-    if self.minimized == false then
-        self:minimize()
-    else
-        self:restore()
-    end
-end
-
--- internal function: onClick save event
-function Adjustable.Container:onClickSave()
-    closeAllLevels(self.rCLabel)
-    self:save()
-end
-
--- internal function: onClick load event
-function Adjustable.Container:onClickLoad()
-    closeAllLevels(self.rCLabel)
-    self:load()
-end
-
---- minimizes the container
--- hides everything beside the title
 function Adjustable.Container:minimize()
-    if self.minimized and self.locked then
-        return
-    end
+    if self.minimized then return end
     self.origh = self.height
     self.Inside:hide()
     self:resize(nil, self.buttonsize + 10)
     self.minimized = true
-    if self.connectedToBorder or self.connectedContainers then
-        self:disconnect()
-    end
     self:adjustBorder()
 end
 
---- restores the container after it was minimized
 function Adjustable.Container:restore()
-    if self.minimized == true then
-        self.origh = self.origh or "25%"
-        self.Inside:show()
-        self:resize(nil,self.origh)
-        self.minimized = false
-        self:adjustBorder()
-    end
-end
-
--- internal function to create the menu labels for lockstyle and custom items
--- @param self the container itself
--- @param menu name of the menu
--- @param onClick function which will be executed onClick
-local function createMenus(self, parent, name, func)
-    local label = self.adjLabel
-    local menuTxt = self.Locale[name] and self.Locale[name].message or name
-    label:addMenuLabel(name, parent)
-    label:findMenuElement(parent.."."..name):echo(menuTxt, "nocolor")
-    label:setMenuAction(parent.."."..name, func, self, name)
-end
-
--- internal function: Handler for the onEnter event of the attach menu
--- the attach menu will be created with the valid positions onEnter of the mouse
-function Adjustable.Container:onEnterAtt()
-    local attm = self:validAttachPositions()
-    self.attLabel.nestedLabels = {}
-    for i=1,#attm do
-        if self.att[i].container ~= Geyser then
-            self.att[i]:changeContainer(Geyser)
-        end
-        self.att[i].flyDir = self.attLabel.flyDir
-        self.att[i]:echo("<center>"..self.Locale[attm[i]].message, "nocolor")
-        self.att[i]:setClickCallback("Adjustable.Container.attachToBorder", self, attm[i])
-        self.attLabel.nestedLabels[#self.attLabel.nestedLabels+1] = self.att[i]
-    end
-    doNestShow(self.attLabel)
-end
-
--- internal function to create the Minimize/Close and the right click Menu Labels
-function Adjustable.Container:createLabels()
-    self.exitLabel = Geyser.Label:new({
-        x = -(self.buttonsize * 1.4), y=4, width = self.buttonsize, height = self.buttonsize, fontSize = self.buttonFontSize, name = self.name.."exitLabel"
-
-    },self)
-    self.exitLabel:echo("<center>x</center>")
-
-
-    self.minimizeLabel = Geyser.Label:new({
-        x = -(self.buttonsize * 2.6), y=4, width = self.buttonsize, height = self.buttonsize, fontSize = self.buttonFontSize, name = self.name.."minimizeLabel"
-
-    },self)
-    self.minimizeLabel:echo("<center>-</center>")
-end
-
--- internal function to create the right click menu
-function Adjustable.Container:createRightClickMenu()
-    self.adjLabel:createRightClickMenu(
-        {MenuItems = {"lockLabel", "minLabel", "saveLabel", "loadLabel", "attLabel", {"att1","att2","att3","att4"}, "lockStylesLabel",{}, "customItemsLabel",{}},
-        Style = self.menuStyleMode,
-        MenuStyle = self.menustyle,
-        MenuWidth = self.ParentMenuWidth,
-        MenuWidth2 = self.ChildMenuWidth,
-        MenuHeight = self.MenuHeight,
-        MenuFormat = "l"..self.MenuFontSize,
-        MenuFormat2 = "c"..self.MenuFontSize,
-        }
-        )
-    self.rCLabel = self.adjLabel.rightClickMenu
-    for k,v in pairs(self.rCLabel.MenuLabels) do
-        self[k] = v
-    end
-    for k,v in ipairs(self.rCLabel.MenuLabels["attLabel"].MenuItems) do
-        self.att[k] = self.rCLabel.MenuLabels["attLabel"].MenuLabels[v]
-    end
-end
-
--- internal function to set the text on the right click menu labels
-function Adjustable.Container:echoRightClickMenu()
-    for k,v in ipairs(self.adjLabel.rightClickMenu.MenuItems) do
-        if type(v) == "string" then
-            self[v]:echo(self[v].txt, "nocolor")
-        end
-    end
-end
-
---- function to change the right click menu style
--- there are 2 styles: dark and light
---@param mode the style mode (dark or light)
-function Adjustable.Container:changeMenuStyle(mode)
-    self.menuStyleMode = mode
-    self.adjLabel:styleMenuItems(self.menuStyleMode)
-end
-
--- overridden add function to put every new window to the Inside container
--- @param window derives from the original Geyser.Container:add function
--- @param cons derives from the original Geyser.Container:add function
-function Adjustable.Container:add(window, cons)
-    if self.goInside then
-        if self.useAdd2 == false then
-            self.Inside:add(window, cons)
-        else
-            --add2 inheritance set to true
-            self.Inside:add2(window, cons, true)
-        end
-    else
-        if self.useAdd2 == false then
-           Geyser.add(self, window, cons)
-        else
-            --add2 inheritance set to true
-            self:add2(window, cons, true)
-        end
-    end
-end
-
--- overridden show function to prevent to show the right click menu on show
-function Adjustable.Container:show(auto)
-    Geyser.Container.show(self, auto)
-    closeAllLevels(self.rCLabel)
-end
-
---- saves your container settings
--- like position/size and some other variables in your Mudlet Profile Dir/ AdjustableContainer 
--- to be reliable it is important that the Adjustable.Container has an unique 'name'
--- @param slot defines a save slot for example a number (1,2,3..) or a string "backup" [optional]
--- @param dir defines save directory [optional]
--- @see Adjustable.Container:load
-function Adjustable.Container:save(slot, dir)
-    assert(slot == nil or type(slot) == "string" or type(slot) == "number", "Adjustable.Container.save: bad argument #1 type (slot as string or number expected, got "..type(slot).."!)")
-    assert(dir == nil or type(dir) == "string" , "Adjustable.Container.save: bad argument #2 type (directory as string expected, got "..type(dir).."!)")
-    dir = dir or self.defaultDir
-    local saveDir = string.format("%s%s.lua", dir, self.name)
-    local mainTable = {}
-    mainTable.slot = {}
-    local mytable = {}
-
-    -- check if there are already saved settings and if so load them to the mainTable
-    if io.exists(saveDir) then
-        table.load(saveDir, mainTable)
-    end
-
-    if slot then
-        mainTable.slot[slot] = mytable
-    else
-        mytable = mainTable
-    end
-
-    mytable.x = self.x
-    mytable.y = self.y
-    mytable.height= self.height
-    mytable.width= self.width
-    mytable.minimized= self.minimized
-    mytable.origh= self.origh
-    mytable.locked = self.locked
-    mytable.attached = self.attached
-    mytable.lockStyle = self.lockStyle
-    mytable.padding = self.padding
-    mytable.attachedMargin = self.attachedMargin
-    mytable.hidden = self.hidden
-    mytable.auto_hidden = self.auto_hidden
-    mytable.connectedToBorder = self.connectedToBorder
-    mytable.connectedContainers = self.connectedContainers
-    mytable.windowname = self.windowname
-    if not(io.exists(dir)) then lfs.mkdir(dir) end
-    table.save(saveDir, mainTable)
-    return true
-end
-
---- restores/loads the before saved settings 
--- @param slot defines a load slot for example a number (1,2,3..) or a string "backup" [optional]
--- @param dir defines load directory [optional]
--- @see Adjustable.Container:save
-function Adjustable.Container:load(slot, dir)
-    local mytable = {}
-    mytable.slot = {}
-    assert(slot == nil or type(slot) == "string" or type(slot) == "number", "Adjustable.Container.load: bad argument #1 type (slot as string or number expected, got "..type(slot).."!)")
-    assert(dir == nil or type(dir) == "string" , "Adjustable.Container.load: bad argument #2 type (directory as string expected, got "..type(dir).."!)")
-    dir = dir or self.defaultDir
-    local loadDir = string.format("%s%s.lua", dir, self.name)
-    if not (io.exists(loadDir)) then
-        return string.format("Adjustable.Container.load: Couldn't load settings from %s", loadDir)
-    end
-
-    local ok = pcall(table.load, loadDir, mytable)
-    if not ok then
-        self:deleteSaveFile()
-        debugc(string.format("Adjustable.Container.load: Save file %s got corrupted.  It was deleted so everything else can load properly.", loadDir))
-        return false
-    end
-
-    -- if slot settings not found load default settings
-    if slot then
-        mytable = mytable.slot[slot] or mytable
-    end
-
-    mytable.windowname = mytable.windowname or "main"
-    
-    -- send Adjustable Container to a UserWindow/ScrollBox if saved there
-    if mytable.windowname ~= self.windowname then
-        if mytable.windowname == "main" then
-            self:changeContainer(Geyser)
-        else
-            self:changeContainer(Geyser.parentWindows[mytable.windowname])
-        end
-    end
-
-    self.lockStyle = mytable.lockStyle or self.lockStyle
-    self.padding = mytable.padding or self.padding
-    self.attachedMargin = mytable.attachedMargin or self.attachedMargin
-
-
-    if mytable.x then
-        self:move(mytable.x, mytable.y)
-        self:resize(mytable.width, mytable.height)
-        self.minimized = mytable.minimized
-
-        if mytable.locked == true then self:lockContainer()  else self:unlockContainer() end
-
-        if self.minimized == true then self.Inside:hide() self:resize(nil, self.buttonsize + 10) else self.Inside:show() end
-        self.origh = mytable.origh
-    end
-
-    if mytable.auto_hidden or mytable.hidden then
-        self:hide()
-        if not mytable.hidden then
-            self.hidden = false
-            self.auto_hidden = true
-        end
-    else
-        self:show()
-    end
-
-    self:detach()
-    if mytable.attached then
-        self:attachToBorder(mytable.attached) 
-    end
-
+    if not self.minimized then return end
+    self.Inside:show()
+    self:resize(nil, self.origh or "25%")
+    self.minimized = false
     self:adjustBorder()
-
-    self.connectedContainers = mytable.connectedContainers or self.connectedContainers
-    self.connectedToBorder = mytable.connectedToBorder or self.connectedToBorder
-    if self.connectedToBorder then
-        for k in pairs(self.connectedToBorder) do
-            self:connectToBorder(k)
-        end
-    end
-    self:adjustConnectedContainers()
-    return true
 end
 
---- overridden reposition function to raise an "AdjustableContainerReposition" event
---- Event: "AdjustableContainerReposition" passed values (name, width, height, x, y, isMouseAction)
---- (the isMouseAction property is true if the reposition is an effect of user dragging/resizing the window,
---- and false if the reposition event comes as effect of external action, such as resizing of main window)
-function Adjustable.Container:reposition()
-    Geyser.Container.reposition(self)
-    raiseEvent(
-      "AdjustableContainerReposition",
-      self.name,
-      self.get_width(),
-      self.get_height(),
-      self.get_x(),
-      self.get_y(),
-      adjustInfo.name == self.adjLabel.name and (adjustInfo.move or adjustInfo.right or adjustInfo.left or adjustInfo.top or adjustInfo.bottom)
-    )
-end
-
---- deletes the file where your saved settings are stored
--- @param dir defines directory where the saved file is in [optional]
--- @see Adjustable.Container:save
-function Adjustable.Container:deleteSaveFile(dir)
-    assert(dir == nil or type(dir) == "string" , "Adjustable.Container.deleteSaveFile: bad argument #1 type (directory as string expected, got "..type(dir).."!)")
-    dir = dir or self.defaultDir
-    local deleteDir = string.format("%s%s.lua", dir, self.name)
-    if io.exists(deleteDir) then
-        os.remove(deleteDir)
-    else
-        return "Adjustable.Container.deleteSaveFile: Couldn't find file to delete at " ..  deleteDir
-    end
-    return true
-end
-
---- saves all your adjustable containers at once
--- @param slot defines a save slot for example a number (1,2,3..) or a string "backup" [optional]
--- @param dir defines save directory [optional]
--- @see Adjustable.Container:save
-function Adjustable.Container:saveAll(slot, dir)
-    for  k,v in pairs(Adjustable.Container.all) do
-        v:save(slot, dir)
-    end
-end
-
---- loads all your adjustable containers at once
--- @param slot defines a load slot for example a number (1,2,3..) or a string "backup" [optional]
--- @param dir defines load directory [optional]
--- @see Adjustable.Container:load
-function Adjustable.Container:loadAll(slot, dir)
-    for  k,v in pairs(Adjustable.Container.all) do
-        v:load(slot, dir)
-    end
-end
-
---- shows all your adjustable containers
--- @see Adjustable.Container:doAll
-function Adjustable.Container:showAll()
-    for  k,v in pairs(Adjustable.Container.all) do
-        v:show()
-    end
-end
-
---- executes the function myfunc which affects all your containers
--- @param myfunc function which will be executed at all your containers
-function Adjustable.Container:doAll(myfunc)
-    for  k,v in pairs(Adjustable.Container.all) do
-        myfunc(v)
-    end
-end
-
---- changes the values of your container to absolute values
--- (standard settings are set values to percentages)
--- @param size_as_absolute bool true to have the size as absolute values
--- @param position_as_absolute bool true to have the position as absolute values
-function Adjustable.Container:setAbsolute(size_as_absolute, position_as_absolute)
-    if position_as_absolute then
-        self.x, self.y = self.get_x(), self.get_y()
-    end
-    if size_as_absolute then
-        self.width, self.height = self.get_width(), self.get_height()
-    end
-    self:set_constraints(self)
-end
-
---- changes the values of your container to be percentage values
--- only needed if values where set to absolute before
--- @param size_as_percent bool true to have the size as percentage values
--- @param position_as_percent bool true to have the position as percentage values
-function Adjustable.Container:setPercent (size_as_percent, position_as_percent)
-    local x, y, w, h = self:get_x(), self:get_y(), self:get_width(), self:get_height()
-    local winw, winh = getMainWindowSize()
-    if (self.container) and (self.container ~= Geyser) then
-        x,y = x-self.container.get_x(),y-self.container.get_y()
-        winw, winh = self.container.get_width(), self.container.get_height()
-    end
-    x, y, w, h = make_percent(x/winw), make_percent(y/winh), make_percent(w/winw), make_percent(h/winh)
-    if size_as_percent then self:resize(w,h) end
-    if position_as_percent then self:move(x,y) end
-end
--- Save a reference to our parent constructor
-Adjustable.Container.parent = Geyser.Container
--- Create table to put every Adjustable.Container in it
-Adjustable.Container.all = Adjustable.Container.all or {}
-Adjustable.Container.all_windows = Adjustable.Container.all_windows or {}
-Adjustable.Container.Attached = Adjustable.Container.Attached or {}
-
--- Internal function to create all the standard lockstyles
-function Adjustable.Container:globalLockStyles()
-    self.lockStyles = self.lockStyles or {}
-    self:newLockStyle("standard", function (s)
-        s.Inside:resize("100%",-1)
-        s.Inside:move(0, s.padding)
-        s.adjLabel:setStyleSheet(string.gsub(s.adjLabelstyle, "(border.-)%d(.-;)","%10%2"))
-        s.adjLabel:echo("")
-    end)
-
-    self:newLockStyle("border",  function (s)
-        s.Inside:resize("-"..s.padding,"-"..s.padding)
-        s.Inside:move(s.padding, s.padding)
-        s.adjLabel:setStyleSheet(s.adjLabelstyle)
-        s.adjLabel:echo("")
-    end)
-
-    self:newLockStyle("full", function (s)
-        s.Inside:resize("100%","100%")
-        s.Inside:move(0,0)
-        s.adjLabel:setStyleSheet(string.gsub(s.adjLabelstyle, "(border.-)%d(.-;)","%10%2"))
-        s.adjLabel:echo("")
-    end)
-
-    self:newLockStyle("light", function (s)
-        s:setTitle()
-        s.Inside:resize("-"..s.padding,"-"..s.padding)
-        s.Inside:move(s.padding, s.padding*2)
-        s.adjLabel:setStyleSheet(s.adjLabelstyle)
-    end)
-end
-
---- creates a new Lockstyle
--- @param name Name of the menu item/lockstyle
--- @param func function of the new lockstyle
-function Adjustable.Container:newLockStyle(name, func)
-    if self.lockStyles[name] then
-        return
-    end
-    self.lockStyles[#self.lockStyles + 1] = {name, func}
-    self.lockStyles[name] = self.lockStyles[#self.lockStyles]
-    if self.lockStylesLabel then
-        createMenus(self, "lockStylesLabel", name, "Adjustable.Container.lockContainer")
-    end
-end
-
---- creates a new custom menu item
--- @param name Name of the new menu item
--- @param func function of the new custom menu item
-function Adjustable.Container:newCustomItem(name, func)
-    self.customItems = self.customItems or {}
-    if self.customItems[name] then
-        return
-    end
-    self.customItems[#self.customItems + 1] = {name, func}
-    self.customItems[name] = self.customItems[#self.customItems]
-    createMenus(self, "customItemsLabel", name, "Adjustable.Container.customMenu")
-end
---- enablesAutoSave normally only used internally
--- only useful if autoSave was set to false before
-function Adjustable.Container:enableAutoSave()
-    self.autoSave = true
-    self.autoSaveHandler = self.autoSaveHandler or registerAnonymousEventHandler("sysExitEvent", function() self:save() end)
-end
-
---- disableAutoSave function to disable a before enabled autoSave
-function Adjustable.Container:disableAutoSave()
-    self.autoSave = false
-    killAnonymousEventHandler(self.autoSaveHandler)
-end
-
---- constructor for the Adjustable Container
----@param cons besides standard Geyser.Container parameters there are also:
----@param container
---@param[opt="getMudletHomeDir().."/AdjustableContainer/"" ] cons.defaultDir default dir where settings are loaded/saved to/from
---@param[opt="102" ] cons.ParentMenuWidth  menu width of the main right click menu
---@param[opt="82"] cons.ChildMenuWidth  menu width of the children in the right click menu (for attached, lockstyles and custom items)
---@param[opt="22"] cons.MenuHeight  height of a single menu item
---@param[opt="8"] cons.MenuFontSize  font size of the menu items
---@param[opt="15"] cons.buttonsize  size of the minimize and close buttons
---@param[opt="8"] cons.buttonFontSize  font size of the minimize and close buttons
---@param[opt="10"] cons.padding  how far is the inside element placed from the corner (depends also on the lockstyle setting)
---@param[opt="5"] cons.attachedMargin  margin for the MainWindow border if an adjustable container is attached
---@param cons.adjLabelstyle  style of the main Label where all elements are in
---@param cons.menustyle  menu items style
---@param cons.buttonstyle close and minimize buttons style
---@param[opt=false] cons.minimized  minimized at creation?
---@param[opt=false] cons.locked  locked at creation?
---@param[opt=false] cons.attached  attached to a border at creation? possible borders are ("top", "bottom", "left", "right")
---@param cons.lockLabel.txt  text of the "lock" menu item
---@param cons.minLabel.txt  text of the "min/restore" menu item
---@param cons.saveLabel.txt  text of the "save" menu item
---@param cons.loadLabel.txt  text of the "load" menu item
---@param cons.attLabel.txt  text of the "attached menu" item
---@param cons.lockStylesLabel.txt  text of the "lockstyle menu" item
---@param cons.customItemsLabel.txt  text of the "custom menu" item
---@param[opt="green"] cons.titleTxtColor  color of the title text
---@param cons.titleText  title text
---@param cons.titleFormat  a format list to use.  'c' - center, 'l' - left, 'r' - right,  'b' - bold, 'i' - italics, 'u' - underline, 's' - strikethrough,  '##' - font size.
---@param[opt="standard"] cons.lockStyle  choose lockstyle at creation.  possible integrated lockstyle are: "standard", "border", "light" and "full"
---@param[opt=false] cons.noLimit  there is a minimum size limit if this constraint is set to false.
---@param[opt=true] cons.raiseOnClick  raise your container if you click on it with your left mouse button
---@param[opt=true] cons.autoSave  saves your container settings on exit (sysExitEvent).  If set to false it won't autoSave
---@param[opt=true] cons.autoLoad  loads the container settings (if there are some to load) at creation of the container.  If set to false it won't load the settings at creation
-
-function Adjustable.Container:new(cons,container)
-    Adjustable.Container.Locale = Adjustable.Container.Locale or loadTranslations("AdjustableContainer")
-    cons = cons or {}
-    cons.type = cons.type or "adjustablecontainer"
-    local me = self.parent:new(cons, container)
+function Adjustable.Container:new(cons, container)
+    local me = Geyser.Container.new(self, cons, container)
     setmetatable(me, self)
-    self.__index = self
-    me.defaultDir = me.defaultDir or getMudletHomeDir().."/AdjustableContainer/"
-    me.ParentMenuWidth = me.ParentMenuWidth or "102"
-    me.ChildMenuWidth = me.ChildMenuWidth or "82"
-    me.MenuHeight = me.MenuHeight or "22"
-    me.MenuFontSize = me.MenuFontSize or "8"
-    me.buttonsize = me.buttonsize or "15"
-    me.buttonFontSize = me.buttonFontSize or "8"
-    me.padding = me.padding or 10
-    me.attachedMargin = me.attachedMargin or 5
-
-    me.adjLabelstyle = me.adjLabelstyle or [[
-    background-color: rgba(0,0,0,100%);
-    border: 2px groove white;]]
-    me.menuStyleMode = "light"
-    me.buttonstyle= me.buttonstyle or [[
-    QLabel{ border-color: rgba(255,255,255,100%); background-color: rgba(0,0,0,100%); }
-    QLabel::hover{ background-color: rgba(160,160,160,50%); }
-    ]]
-
     me:createContainers()
-    me.att = me.att or {}
     me:createLabels()
-    me:createRightClickMenu()
-
-    me:globalLockStyles()
-    me.minimized =  me.minimized or false
-    me.locked =  me.locked or false
-
-    me.adjLabelstyle = me.adjLabelstyle..[[ qproperty-alignment: 'AlignLeft | AlignTop';]]
-    me.lockLabel.txt = me.lockLabel.txt or [[<font size="5" face="Noto Emoji">🔒</font>]] ..  self.Locale.lock.message
-    me.minLabel.txt = me.minLabel.txt or [[<font size="5" face="Noto Emoji">🗕</font>]] ..self.Locale.min_restore.message
-    me.saveLabel.txt = me.saveLabel.txt or [[<font size="5" face="Noto Emoji">💾</font>]]..  self.Locale.save.message
-    me.loadLabel.txt = me.loadLabel.txt or [[<font size="5" face="Noto Emoji">📁</font>]]..  self.Locale.load.message
-    me.attLabel.txt  = me.attLabel.txt or [[<font size="5" face="Noto Emoji">⚓</font>]]..self.Locale.attach.message
-    me.lockStylesLabel.txt = me.lockStylesLabel.txt or [[<font size="5" face="Noto Emoji">🖌</font>]]..self.Locale.lockstyle.message
-    me.customItemsLabel.txt = me.customItemsLabel.txt or [[<font size="5" face="Noto Emoji">🖇</font>]]..self.Locale.custom.message
-
-    me.adjLabel:setStyleSheet(me.adjLabelstyle)
-    me.exitLabel:setStyleSheet(me.buttonstyle)
-    me.minimizeLabel:setStyleSheet(me.buttonstyle)
-    me:echoRightClickMenu()
-    
-    me.adjLabel:setClickCallback("Adjustable.Container.onClick",me, me.adjLabel)
-    me.adjLabel:setReleaseCallback("Adjustable.Container.onRelease",me, me.adjLabel)
-    me.adjLabel:setMoveCallback("Adjustable.Container.onMove",me, me.adjLabel)
-    me.minLabel:setClickCallback("Adjustable.Container.onClickMin", me)
-    me.saveLabel:setClickCallback("Adjustable.Container.onClickSave", me)
-    me.lockLabel:setClickCallback("Adjustable.Container.onClickL", me)
-    me.loadLabel:setClickCallback("Adjustable.Container.onClickLoad", me)
-    me.origh = me.height
-    me.exitLabel:setClickCallback("Adjustable.Container.hideObj", me)
-    me.minimizeLabel:setClickCallback("Adjustable.Container.onClickMin", me)
-    me.attLabel:setOnEnter("Adjustable.Container.onEnterAtt", me)
-    me.goInside = true
-    me.titleTxtColor = me.titleTxtColor or "grey"
-    me.titleText = me.titleText or me.name.." - Adjustable Container"
     me:setTitle()
-    me.lockStyle = me.lockStyle or "standard"
-    me.noLimit = me.noLimit or false
-    if not(me.raiseOnClick == false) then
-        me.raiseOnClick = true
-    end
-
-    if not Adjustable.Container.all[me.name] then
-        Adjustable.Container.all_windows[#Adjustable.Container.all_windows + 1] = me.name
-    else
-        --prevent showing the container on recreation if hidden is true
-        if Adjustable.Container.all[me.name].hidden then
-            me:hide()
-        end
-        if Adjustable.Container.all[me.name].auto_hidden then
-            me:hide(true)
-        end
-        -- detach if setting at creation changed
-        Adjustable.Container.all[me.name]:detach()
-    end
-
-    if me.minimized then
-        me:minimize()
-    end
-
-    if me.locked then
-        me:lockContainer()
-    end
-
-    if me.attached then
-        local attached = me.attached
-        me.attached = nil
-        me:attachToBorder(attached)
-    end
-
-    -- hide/show on creation
-    if cons.hidden == true then
-        me:hide()
-    elseif cons.hidden == false then
-        me:show()
-    end
-
-    -- Loads on creation (by Name) if autoLoad is not false
-    if not(me.autoLoad == false) then
-        me.autoLoad = true
-        me:load()
-    end
-
-    -- Saves on Exit if autoSave is not false
-    if not(me.autoSave == false) then
-        me.autoSave = true
-        me:enableAutoSave()
-    end
-
-    Adjustable.Container.all[me.name] = me
-    me:adjustBorder()
     return me
 end
 
--- Adjustable Container already uses add2 as it is essential for its functioning (especially for the autoLoad function)
--- added this wrapper for consistency
-Adjustable.Container.new2 = Adjustable.Container.new
+function Adjustable.Container:createLabels()
+    self.exitLabel = Geyser.Label:new({
+        x = -(self.buttonsize * 1.5), y = 2, 
+        width = self.buttonsize, height = self.buttonsize,
+        name = self.name.."ExitLabel"
+    }, self.adjLabel)
+    self.exitLabel:echo("<center>X")
+    self.exitLabel:setClickCallback(function() self:hide() end)
 
---- Overridden constructor to use the old add 
--- if someone really wants to use the old add for Adjustable Container
--- use this function (not recommended)
--- or just create elements inside the Adjustable Container with the cons useAdd2 = false
-function Adjustable.Container:oldnew(cons, container)
-    cons = cons or {}
-    cons.useAdd2 = false
-    local me = self:new(cons, container)
-    return me
+    self.minimizeLabel = Geyser.Label:new({
+        x = -(self.buttonsize * 2.8), y = 2, 
+        width = self.buttonsize, height = self.buttonsize,
+        name = self.name.."MinLabel"
+    }, self.adjLabel)
+    self.minimizeLabel:echo("<center>_")
+    self.minimizeLabel:setClickCallback(function() 
+        if self.minimized then self:restore() else self:minimize() end 
+    end)
+    
+    -- Connect mouse events to the main label
+    self.adjLabel:setClickCallback(function(l, e) self:onClick(self.adjLabel, e) end)
+    self.adjLabel:setReleaseCallback(function(l, e) self:onRelease(self.adjLabel, e) end)
+    self.adjLabel:setMoveCallback(function(l, e) self:onMove(self.adjLabel, e) end)
 end

When I started mudlet after the patch was applied prior to the build, I saw an improvement in the mudlet turorial too.  I was welcomed with something that was usable.  The image at the top of this blog post is how it looked for me on the mud I am playing.  It took a little adjustment within the UI itself to have it display the way it looks, but it functions.  The UI capability is available for any mud, their UI scripts will be downloaded automatically, possibly after indicating something like "mudlet_ui enable" on the server.

Click image to enlarge

I will be creating all the files needed for a mudlet port that follows release versions.  Mudlet is nearing a new release by the end of May 2026, so this could be a perfect time for an interested party to adopt mudlet and add it to the ports tree.  My next post will provide all of my efforts to get an official style port for mudlet that someone else could maintain as their own, with my blessing.

Frequently viewed this week