Warning: there may be occasional oddness due to css and blog edits. **Active remodel of blog posts - mostly starting with most recent** 70/86
Showing posts with label porting. Show all posts
Showing posts with label porting. Show all posts

Thursday, October 8, 2026

Sylve for clean jailed build

My previous description of using sylve for porting software to FreeBSD did not include two features that I have decided to enable, but which also cause me some little bit of grief because the most simple method around it seems to no longer exist.  I setup sylve which was easy, then I configured a jail, and I have used the jail for updating a number of ports since.  I may use it to create new unofficial -dev ports.  What I did not do, was begin from nothing for the start of any port.  What I have been doing lately is just that, begin with an empty jail with the bare minimum required for any port to build from a Makefile, and then reset the jail after.

Sylve has the functionality already integral to the system.  We need to create a ZFS snapshot after the minimal configured jail with minimal required ports are installed.  After the snapshot exists, we can rollback any time at will and also configure sylve to automatically rollback when the jail is stopped.  All of this is easy from within sylve.  The other parts of the jail configuration include some allowed options for the jail, and the fstab entries to allow access to my home directory where my git directories are, and one for ports so I can avoid duplication as well as for consistency between jail builds and builds outside of it.

We need to go to the jail options tab to make some adjustments.  Each item expands by clicking the property name, then going to the top to click the edit button.

OPTIONS
Property
Value
/etc/resolv.conf
—
Additional Options
—
Allowed Options
allow.set_hostname Mount devfs Mount nullfs Raw sockets Reserved ports Set hostname Socket address families Super User privileges SysV IPC
DevFS Ruleset
—
Execution Timeout
120 seconds
FSTab Entries
/home/tigersharke /zroot/sylve/jails/200/home/tigersharke nullfs rw 0 0 /usr/ports /zroot/sylve/jails/200/usr/ports nullfs rw 0 0
Lifecycle Hooks
        Pre-start
no-tick
        Start
ticked
/bin/sh /etc/rc
        Post-start
no-tick
        Pre-stop
no-tick
        Stop
ticked
/bin/sh /etc/rc.shutdown
        Post-stop
ticked
zfs rollback zroot/sylve/jails/200@sjs_fresh-start_1789000233518
Metadata
—
Start At Boot / Start Order  
No / 0
Wake on LAN
No

The snapshot needs to be made but after it exists, clicking on the snapshot line copies the name for it.  The whole path starts with the zfs pool name and the default path of sylve/jails and the id number for the jail followed by an @ and then the snapshot name you copied.

I have all of my -dev repo directories in my home directory organized using the same directory hierarchy as the ports tree (which is important).  In my /etc/make.conf are the necessary lines including one to setup the overlay, this needs to be created inside the jail as well as outside.  For example, my luanti-dev directory path is /home/tigersharke/GitRepos/PortsTree/games/luanti-dev.

/etc/make.confDEVELOPER="YES" DEFAULT_VERSIONS+=ssl=openssl WITH_CCACHE_BUILD="YES" OVERLAYS=/home/tigersharke/GitRepos/PortsTree/

Since we are starting from nothing but the most mandatory initial programs and configuration, any build from that first Makefile (such as for one of my unofficial -dev ports) will potentially cause a build and install of as many as 1400 other ports.  Once upon a time, there was an environment variable that pkg honored which would tell it to install all dependencies as packages rather than build from source via ports.  This functionality is no longer available when building directly from a Makefile in the ports tree.  The good news, as slight as it may be, is that there is still a method to get close to the same result.  I wrote a script for each step of the process.

The first script starts with make build-depends-list run-depends-list which we can pipe to sort to alphabetize and remove duplicates, then pipe to sed and awk to exclude the initial base ports and to remove /usr/ports/ from each line as well, and finally direct it to an output file.

#!/bin/sh
# First Dependencies
make build-depends-list run-depends-list | sort -u -d | sed -e 's:/usr/ports/::' -e 's:devel/bsddialog::' -e 's:devel/ccache::' -e 's:devel/desktop-file-utils::' -e 's:ports-mgmt/pkg::' -e 's:ports-mgmt/portconfig::' | sed '/^$/d' > first_deps_list

Another script takes a file name as input to send to pkg so the content of the file is installed.

#!/bin/sh
# Prep
yes|pkg install `cat $1`

The third script uses pkg origin output which is also similarly organized and cleaned up for an output file with a larger list.

#!/bin/sh
# Most dependencies
PORTNAME=`make -V PORTNAME`
SELF=`pkg origin | grep $PORTNAME`
pkg origin | sort -u -d | sed -e 's:devel/bsddialog::' -e 's:devel/ccache::' -e 's:devel/desktop-file-utils::' -e 's:ports-mgmt/pkg::' -e 's:ports-mgmt/portconfig::' | awk -vLine="$SELF" '!index($0,Line)' | sed '/^$/d' > most_deps_list

This whole process mostly works well, except that when there are conflicting pkgs installed, such as llvm-lite and llvm, or git-tiny and git, especially when an automatic removal of one of the lesser used dependencies causes something like sdl3 to also be removed.  Not a problem but note when trying to build and install the updated or nascent unofficial -dev port, whether it still wishes to build dependencies.  At that point we can leave and allow it to chug away while we are not present, or just cancel the process to install it from pkg, and then clean and resume the install until the next ones if there are any.

The other method if those scripts are not used, is to set the build process going, re-starting it when it unexpectedly fails to get a dependency of a dependency somewhere (which truly should never occur) and then once it is entirely complete, use pkg to get a list of what was installed.  We can stop the build when we notice an extra long compile such as for llvm or any other thing, use pkg for that, clean up and restart the install.  After the build is done, we use pkg origin piped to sort and direct into an organized file, then remove the five mandatory initial ports: devel/bsddialog, devel/ccache, devel/desktop-file-utils, ports-mgmt/pkg, and ports-mgmt/portconfig.  Another two are useful to have for Makefile testing and cleanup: ports-mgmt/portfmt for portclippy, and ports-mgmt/porttools for portlint.  Portclippy has a way of helping visualize how to organize your Makefile, portlint tests various things needed for a proper FreeBSD port.

Any of the secondary lists will likely be larger than a preferred group of dependencies because it is best (for testing and development) to accept the one-size-fits-most default configurations of each and not the most lean build of any or all ports.  This is important because most users will use those defaults rather than a longer process of building customized ports locally.  When the Makefile for the resulting port is used, we can choose other options that may reduce or change dependencies from those defaults.  For testing, any options that affect what is installed should be ticked, any options that are unproven or may change upstream should also be tested, ticked.  My own system denies wayland, so I already have a few things I must build for myself due to a wayland default in the official FreeBSD pkg repo.

Regardless of whether we use the speedier or slower method initially, I believe it is best to do another pkg origin directed into a file for the final actual dependency list, minus those five ports of course.  Those mandatory ports are in the snapshot and so they will always remain after a rollback.  We are making this all-origins file so that each time in the future when we update the -dev port from an effectively empty jail, hopefully we will not need to spend any extra time to build the dependencies.  I could keep a jail for every port and leave those dependencies intact, but only up to the point that upstream changes them.  By keeping these lists and re-populating the jail before a build, its a happy medium without taking too much time after its setup.

It seems that no matter how much care is taken when compiling the list of dependencies for speedy easy install by pkg, there is a chance that weird conflicts among all of the dependencies may occur.  When this happens, automatically accepting by -y or piped yes, may also cause some dependencies to be removed just as quickly.  Instead of wasting effort on that, just get the best list of dependencies as possible, use pkg to install them, and then continue with the fresh build of the port.  I don't know how to manipulate pkg to get a better result or to ignore its sat solver.  I also don't know whatever it would take to truly re-implement that lost capability to install all dependencies with pkg instead of building any that exist in ports.

The main reason we want to test ports, or attempt to port new software, or update ports from within a clean jail, is that dependencies are not silently satisfied and therefore missed in the Makefile.  Another reason is that your main system also remains more clean and "uncorrupted" by all of the excess packages or ports that get installed to satisfy dependencies, or that are installed in hopes of satisfying dependencies.  Inside the jail we can set it up to be much more vanilla than our personal preference outside of it.  And one more good reason, is if we use what is built within the jail which means it is also installed outside of the jail, the update to a port or any testing done in the jail will not cause the executable such as a game, from being uninstalled or broken outside of the jail.  A jail can also be setup to be less vanilla but also distinct from the system outside of it, and again, means that system outside is unaffected.

Use sylve to very easily manage a dozen different jails, or just one.  Sylve makes the management accessible and visible, all of the options are present, it allows a fresh clean jail on demand, even if it is the same one you always use for a purpose.  Working from within a web browser for the interaction may change some things a little bit but those are easily understood.  I get around some of the differences by being able to work with git in the same directory from outside of the jail, but I think its the browser that is getting in the way rather than a quirk with sylve.  The more sylve is used, the greater chance for improvement, but it is still a great tool in its relatively early days.

Saturday, October 3, 2026

Mudlet properly fixed

It has been a while since my attempted patch for Mudlet to successfully build and fully function for FreeBSD.  I have been waiting patiently and have checked back with Grok to see if other solutions would become apparant.  Unfortunately my efforts with Grok did not yield any further improvements.  However, I recently saw a comment on my proposed patch which could solve the issue.

Thanks for chasing this down on FreeBSD. The crash is real, but the cause is in packaging,
not in Geyser, and it isn't specific to FreeBSD.

The real cause

  1. Adjustable.Container loads its strings with loadTranslations("AdjustableContainer"). That looks for translations/lua/mudlet-lua.json first in the source tree, then under luaGlobalPath .. "/translations/" (Other.lua).
  2. The make install rules for UNIX AND NOT APPLE in src/CMakeLists.txt install mudlet-lua/lua, the tests and lcf, but never translations/lua/*.json. Only the macOS bundle copies them. This is still true on current development.
  3. An installed Mudlet loads Lua from ${PREFIX}/share/mudlet/lua (TLuaInterpreter.cpp), so neither path exists and loadTranslations() returns nil. Adjustable.Container:new then fails on self.Locale.lock.message.
The FreeBSD port just hits this first. A Linux distro package or a /usr/local make install fails the same way. The fix is one install rule matching the existing fallback path:
install(DIRECTORY "../translations/lua/"
        DESTINATION "share/mudlet/lua/translations"
        FILES_MATCHING PATTERN "*.json"
        PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
That also fixes the package and module install messages in Other.lua, which load translations the same way. Problems with the current patch (checked by running the patched code in lua5.1)
  • Breaks package installs. It sets mudlet.Locale = {}. Other.lua fills mudlet.Locale lazily (mudlet.Locale = mudlet.Locale or loadTranslations("Mudlet")), so once any adjustable container exists, that load never runs. verbosePackageInstall / verboseModuleInstall then fail with "attempt to index field 'packageInstallSuccess' (a nil value)", even when translations are present.
  • The fallback covers 7 of the 17 AdjustableContainer keys. Hovering "Attach" (onEnterAtt, top/bottom/left/right) and calling addConnectMenu() (connectTo/ disconnect) still throw. The English fallback strings also differ from the real ones ("Minimize" vs "Min/Restore", "Lock Style" vs "Lockstyle:", "Custom Items" vs "Custom:").
  • The getOS() == "freebsd" gate misses the other affected platforms. OpenBSD, NetBSD and Linux installs still crash.
  • Leftover guards. min_restore / max_restore are never read as fields. The second constructor block writes the class Locale, and only that write keeps line 1137 from crashing.
Would you be up for replacing the Lua changes with the install rule above? It would help if you could confirm share/mudlet/lua/translations/mudlet-lua.json exists after make install on FreeBSD.

Soon after I saw this, I was a bit uncertain how to continue, so I asked Grok.

Prompt 3 of 20 (17 remaining).

This is the breakthrough we needed.

Upstream’s analysis is correct

The long comment on your PR (the one that looks Claude-generated) correctly identifies the real root cause:
  • Adjustable.Container (and other code) calls loadTranslations("AdjustableContainer").
  • That function looks for JSON files under translations/lua/ (first relative to the source tree, then under luaGlobalPath .. "/translations/").
  • On a normal make install for UNIX (including FreeBSD and Linux distro packages), those JSON files are never installed. Only the macOS bundle copies them.
  • Result: loadTranslations returns nil → self.Locale is nil → Geyser crashes.
The .qm / Qt resource embedding issues we spent so much time on are a separate (and still real) problem for the GUI language, but the Geyser crash is caused by the missing Lua JSON translation files.

The proper fix (recommended by upstream)

Add this install rule (in src/CMakeLists.txt or equivalent):
cmake
install(DIRECTORY "../translations/lua/"
        DESTINATION "share/mudlet/lua/translations"
        FILES_MATCHING PATTERN "*.json"
        PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
			
After install you should have:
  
/usr/local/share/mudlet/lua/translations/mudlet-lua.json
/usr/local/share/mudlet/lua/translations/translated/...
			

How to do this in the FreeBSD port (no CMake patch required)

The easiest and cleanest way for the port is a post-stage (or post-install) rule in the Makefile. Add something like this:
make
post-stage:
	@${ECHO_MSG} "==> Installing Lua translation JSON files"
	@${MKDIR} ${STAGEDIR}${PREFIX}/share/mudlet/lua/translations
	@${CP} -Rp ${WRKSRC}/translations/lua/* \
		${STAGEDIR}${PREFIX}/share/mudlet/lua/translations/ 2>/dev/null || true
			
(Adjust the source path if your work directory layout is different.) Then rebuild/reinstall and verify:
sh
ls -l /usr/local/share/mudlet/lua/translations/
ls -l /usr/local/share/mudlet/lua/translations/mudlet-lua.json
			
If those files exist, start Mudlet and the Geyser nil-Locale error should be gone without any Lua safety-net patches.

Next step

  1. Add the `post-stage` rule above (or the CMake install rule if you prefer a patch).
  2. Clean rebuild + install.
  3. Confirm the JSON files are present.
  4. Test Mudlet (especially the tutorial / any AdjustableContainer UI).
Would you like me to give you the exact Makefile snippet tailored to your current port layout, or a ready-to-apply `files/patch-src_CMakeLists.txt` instead? This is the clean, upstream-friendly solution.

I really only asked Grok for clarity or to give myself a moment to digest the information, the patching I needed to do was already provided, I simply had some uncertainty.  Since I already knew that this should be an upstream change and that it affects more than just FreeBSD, what I chose to do was simply edit the src/CMakeLists.txt file as mentioned in the comment to the commit.  I found the most appropriate location, directly inside of a conditional for Unix builds.  I copied and pasted it in, then created the patch.  I cleaned up and removed any other patches for the unofficial -dev port and set it to build.

After a while, and a few rebuilds to be sure the pkg-plist would be complete, it was finished.  I had made the adjustements and did the build in a clean jail that had only its necessary dependencies installed.  Once I created a fresh pkg-plist, I went outside of the jail to build and install on my system for testing.

The pkg-plist file I had generated already proved that the needed file to check for would be installed.  I built and installed a new mudlet on my system, then tested the client.  When I chose the mudlet tutorial, everything worked as it should, no complaint about locale and a broken UI.

Now mudlet upstream will soon correct the issue.  Technically I did do a pull request with the suggested fix but since I am a such a "great expert" with github and only somewhat better with git itself, it was closed and upstream will need to figure it out.  I am not going to add further noise by my git skills.  Git frustrates me with regard to backing out of commits, merging the whole mess I have on my end to upstream without my desire, and how I don't truly understand much of git or github.

We can now truly celebrate.  Before games/mudlet has even been added to the FreeBSD ports tree, it will be able to succeed with building, installing, and functioning for its GUI.  Right now my unofficial -dev port has a patch for the src/CMakeLists.txt file, but when upstream updates I can eliminate it.  As many have said, as I have repeated myself a number of times, it is best to contribute a FreeBSD patch to upstream so that it will be fixed properly by them and we can do away with our maintaining of patches.

Monday, August 31, 2026

Dogfood famine ecosystem

I keep going back to playing on luanti servers as my game for entertainment.  Not exactly because other options are missing but because of frustration, it works and other things I want won't build or are trapped by Windows dependency (and my struggles to get wine or its progeny to work for me), but my choice of server with luanti is also rather zen.  I do my best to keep my own -dev ports updated to the most recent commit.  I try to port things I think are cool, heavier emphasis on games since many other ports in the tree have plenty of representation and support by maintainers.  Along with a number of games that function in my -dev collection of unofficial ports, there is fvwm and feh and flameshot, and frustration with porting.  I use my -dev ports when they result in functional applications.

Porting software from Linux is our primary task, since software developed on and for FreeBSD is either rare or becomes a wider community project subsumed by Linux.  This may not necessarily be a bad thing, but it would also be nice sometimes for FreeBSD to be the originator and controlling interest in the result, for FreeBSD to be the upstream.

There should be no expectation of using portmaster or poudriere

When porting from Linux nothing is simple, not even when at first impression and initial investigation it could be.  There are not only multiple build management tools, such as CMake, Meson, Maven, Autotools, but even when those tools are used there may be non-standard ways of implementing the structure those facilitate.  I have long preferred to see CMake being used because I have become a bit more adept with it.  I can easily discover the options to expose in a Makefile for FreeBSD.  This is not always the case, sometimes CMake is not the method of setting options or not many options are set.  Sometimes dependencies come from another tool as with python eggs or rust crates or lua rocks.  And sometimes they use git submodules which are their own special pain to maintain.

I understand that the proper way for a port is a Makefile, distinfo file, pkg-plist and pkg-descr file, but most importantly the correctly designed Makefile.  On Linux or just outside of FreeBSD, the method for building and instructions given may be a shell script (frequently bash) or a series of commands which are not always directly transferrable into a Makefile.  Discovering the dependencies is usually easy but then FreeBSD ports has its own twist to navigate because we divide some things into a different logical set or use an alternate naming convention.  We may know the thing we're porting needs a certain qt or python module but it may take a bit of investigation to discover how to satisfy that dependency or build requirement.  We have a great porter's handbook which covers a very large swath of what we may need to know, but be careful of examples which may be out of date or could have typos.

A significant portion of all possible problems can be cured by testing.  The best testing is done by actually using it, and even better when the user is familiar with the tool or subject matter.  Who but someone dependent upon QT Creator, an IDE for development would know how it works, or which other files it needs but the port fails to install? It doesn't matter what port in the tree someone wants to build, it should build when given an accurate Makefile and preferably also an accurate distinfo file.  The options exposed should be enough to configure how it is built, the pkg-plist should be complete and up to date.  There should be no expectation of using portmaster or poudriere, when everything present at the origin for a port is meant to be sufficient to build it.  We do not require ccache be used while building ports but it can definitely speed up the process.  Distcc could be used as well, if infrastructure exists and distcc itself is without flaws or challenges.  Our pkg may be easy to use but even when using pkg, it is not immune to dependency issues, and the defaults it describes may be undesirable for those avoiding wayland or any other of a potentially growing list of unwanted or not necessary dependencies.  Poudriere is a very helpful and efficient tool, it can do the job very well.  However, it should not be the case that poudriere builds something significantly differently than directly from ports.  If it does, and it succeeds but building from ports fails, then maybe the exact same adjustments need to be made to the ports tree and its mechanisms.  Poudriere is an optional tool just as much as portmaster.  The result of building directly from ports must have the exact same result as when any additional tool is used.

The standard for being certain that a port has all of its dependencies, builds properly, and has its resulting ports tree files accurate should be to perform the process within a clean jail.  The make.conf used should include DEVELOPER="YES" and the tools portlint and portclippy should be used to help keep the Makefile consistent.  If portclippy or portlint expect an undesirable condition to be met, the tool can be adjusted.  Very often portlint and DEVELOPER="YES" cause problems to be found for correction, so those should be nearly mandatory to use.  With regard to the messages due to developer mode, the warnings about whether any certain library "may not be necssary" are confusing or sometimes inaccurate.  How it determines this seems to be imperfect when sometimes including a library may trigger that message but removing that same library triggers the need to add that dependency.  Sometimes the way to silence the "not necessary" message is to include the dependency another way, such as with a conditional instead of using the LIB_DEPENDS directly, but this doesn't feel right.  Once everything is finished building, the minimum testing that ought to be done is to see whether it will start up, will the executable work? Of course, better is to actually test its function, but at least if the application will start up and not complain about some flaw of dependencies or missing files, then its good to be updated in the ports tree.

We need to extend the /usr/ports/Mk structure for additional build systems, and improve things already present.  Why is it that there are only two primary git systems represented, and why do we do things differently for GitHub than GitLab? Why couldn't the necessary information be provided identically but used at each site in the way it prefers? We can keep the distinction between the two, but the account, project, tag, and install subdirectory (on the build side) could all be ordered identically with the same nomenclature.  How the git site uses or organizes that information should not necessarily mean we organize or provide it differently within the Makefile.  What about gitea or any other random as yet unnamed git repository? Why can't there be a generic git mechanism for the rest that would make it easier on those attempting to port with sources obtained at any existing git repo?

One thing that a potential port may have which becomes a significant bit of extra work, is a git submodule, or multiple of them.  The upstream developers have a way to obtain all of it in one shot but downstream as a FreeBSD port, the prescribed method is to add every one of those submodules as a unique git tuple and then check back each time upstream updates to see if any submodule, obtained by a seperate more extensive git pull, has changed too.  Why can't this be part of the initial git pull when obtaining the upstream sources? If we have any of those submodules as individual ports, and the upstream build can accept them as being provided like any other dependency, then we could do it that way.  However, its very excessive that we need to track down all of the submodules to create a tuple for each, and then repeat the process at each update.  A new Mk git option could be defined which might be Uses= git:sub and it would use the other method, the more extensive git pull that would include the submodules defined.  Those submodules would also automatically be in the expected position within the hierarchy and not be another custom post-extract or other operation in the Makefile.

Surely there may be more things that could be handled by the Mk files that would simplify the Makefile, or possibly improve its readability, clarity, or function.  Changes in the Mk files can certainly trigger informative messages about what is new or deprecated.  Improvements to the options could be made to add OPTNAME_CONTINGENT to be assigned other options that must be true for this option to also be set, although 'contingent' seems a bit long.  It may also be useful to add other kinds of checks, such as checking to see if a port is built with a specific option set, or some specific port is installed which may not be a dependency but could alter the install or configuration of the present port.  There will always be the flexibility of conditionals with .if and the pre-build or post-extract and other sections, but there are potentially simpler pre-defined constructions with less verbose language that could be added.

Makefile configuration option names should be unique when necessary and identical when they truly provide exactly the same function in all the ports that have such an option.  If a port requires wayland and has no way to build without it, then another port which depends upon it could potentially either have an option called wayland as well or mention in the option for that dependent port that it adds wayland.  There may also be times when upstream is not entirely clear about what a toggle in their build actually does, or are somewhat vague stating it adds documentation, which could be anything, and if we had but one Makefile config option across all ports of 'manpage' then it may not be accurate for a port that includes its README file.  Any config option should necessarily mean that the software is built differently, linking to different libraries, changes capabilities, or changes what is installed.  If the results are exactly the same, either the option was not set properly or something is not correct upstream.  There are ways to discover what upstream has for build options but each build management tool does it differently, oddly this is not mentioned in the porter's handbook for how these things can be discovered for each of the build systems, not even for the most common ones already mentioned.

A significant portion of all possible problems can be cured by testing.  The best testing is done by actually using it

Its nice that some things we can add to our Makefile defined in /usr/ports/Mk somewhere also include a number of defaults.  Defaults for CMake for example frequently cause "variable defined not used" messages, which is not actually a problem.  However, it might be helpful for additional CMake defaults or settings to be pre-defined, just to simplify invocations in the Makefile.  During the process of trying to understand a port, additional debugging information, particularly where dependencies are searched or other error messages with that regard, would be helpful.  Perhaps USES= cmake:debug or qt:debug or others, maybe it would be done another way.  I am no expert with Make or the design and function of the Mk files, but surely more can be done for some build systems, dependency mechanisms, and missing use cases that crop up within our growing ports tree.

The build system maven presently does not seem to have any helper in the Mk files which necessitates a potentially complex do-build and likely also a do-install subsection.  Surely this is only one instance where a do-build and possibly also do-install sections could be eliminated.  If the entire ports tree were checked for any of many potential additions to our Mk files, surely porting would be more likely to get easier for those who attempt to port or maintain.  Maybe we can automate upstream build option discovery, whether all those options get used (exposed) in the Makefile is up to the maintainer, but wouldn't it be cool if in the process of the build it could list all the possible options upstream provides?  Just like how there is a test with the DEVELOPER="YES" in the make.conf for linked libraries, maybe this could also trigger a list of those upstream options.  Would it depend upon whether the particular build management tool is in the USES line or simply auto-detect.  Maybe auto-detect because then it could also point at the build management tool if it wasn't already known.  Why not make our Mk infrastructure, mechanisms be ever more helpful and informative?

One tool that has potential to be improved or extended is portscout, since it can be used by those outside of port committers and maintainers.  Presently it can only check upstream for new release versions, but either portscout or a new tool with a similar capability to periodically, up to once daily (or configurable) to check upstream for new commits.  My own unofficial -dev ports which I maintain, were their number to grow significantly, would be helped with automation to check if a new commit, or the last of a series of commits had happened by midnight or the end of upstream's day.  Such a tool could be used not only to help maintain ports based upon last daily commit hash, but also with prompting testing that follows the build of that recent commit.

I want things to be better.  I want things to be easier.  I really want to be able to port anything under the Sun from Linux to FreeBSD, but there always seems to be an impasse where either the upstream coding somehow fails on FreeBSD, a dependency cannot be found while its location is provided, a dependency is entirely missing on our end, or something is weird with the build system or its resulting files.  I tried for ages to get dbeaver ported, I made progress but got stymied by time-date stamping of the resulting files to install.  I mostly got Mudlet functional on FreeBSD but to do so, may likely have broken translations.  Lutris had built last time I tried but seems only partially functional, and I am unsure how to fix it.  Beyond all reason eludes me for unknown reasons but I have made some progress on it.  There was something I was going to try to work on but it uses gitea and that git system is different enough that I am confused by it.

It is entirely possible that a lot of the stuff I am trying to get working for FreeBSD has coding issues upstream which cause me trouble.  As I said once before, we here in FreeBSD need to help upstream see us.  We need to help them help us, rather than we patch everything at downstream to work our way.  Upstream can add conditionals to their CMakeList.txt files (or other build system equivalent) to do things differently for us as necessary, and we can report back to them when its successful.  There is truly no reason why software written to work on Linux cannot be compiled to run under FreeBSD, with the few exceptions of systemd and kernel integration.  Patching all sorts of things on our end to get upstream's software to build how we need it, to install where we want it, and to fit everything else the way FreeBSD needs, is fine but this should be our last resort.  Our changes, patches, revisions should all be given to upstream so they can add them and so we can do as we always suggest to our downstream: give us your patches so we can maintain them instead of you.

We can raise the standard.  We can make porting easier.  Ideas first, testing and implementation next, then lets conquer the challenge of maintaining ports by additional automation and support structures, nothing is impossible but it may take time to build.

Friday, July 10, 2026

jail porting with sylve

Once before, in the now distant past I had used a spiffy GUIfied tool to handle jails on my FreeBSD system.  This was Warden on PC-BSD.  It was super helpful and easy to use.  Since then I have tried numerous different tools including bastille.  Of those, only bastille worked for me and was easy enough to use.  Unfortunately I never got accustomed to using it, not having used it enough.  I recently played with sylve as a tool for handling Virtual Machines in bhyve, and since it also has a jail function I have decided to test this out too.

My recent perceived finished Mudlet port seems to have developed a pile of flaws after I erased everything installed on my system when not long ago I had graphics issues.  It is because of this that I am going back to using jails for my periodic port updates if possible, and definitely when I do the initial porting.  This will ensure the option of a clean system with nothing else installed, so that I can start from zero without suffering the lack of things installed on my host system.

sylve jail setup

The setup seems very simple, there is a basic guide on the sylve site that helps to inform the user what each setting is about.  I followed it to get the initial jail setup accomplished but if I can mount my ~/GitRepos/PortsTree and possibly even use that as an overlay of the main ports tree, that would be ideal.

Click image to enlarge

When we create a jail, first from the Basic tab we choose a jail name and host name, they can be the same, and it makes sense to set 200 for the first jail ID.  From the Storage tab, we choose the host ZFS pool, and the Base we downloaded.  I could have chosen a 15.x release image "base.txz" but since there are snapshots for 15.1-stable, I chose to use that as it would be closest to what I have on my host system.  Using something nearly identical to what is on Ichigo will help me duplicate all aspects I already know, and help me find other issues that I have not yet discovered.  From the Network tab I choose Inherit, so that the jail uses the host's connectivity.  Accessing the Hardware tab lets us choose various limits, so I untick Resource limits.  I am unsure precisely what Clean Environment means, but I leave it ticked.  We also choose between Linux and FreeBSD jail type which I leave as FreeBSD because that was what I need for my purposes.  The Allowed Options dropdown includes a number of things we should tick for the jail purpose I need: Set Hostname, Raw Sockets, Mount devfs, Socket Address Families, Reserved Ports, Mount nullfs.

One thing I need to do to make use of the ports tree on the host is to null mount it within the jail.  I could set it as read-only because there are no changes I will need to make, but adding it will eliminate the need to re-create it within the jail.  The small problem is that /usr/ports/distfiles needs to be read-write, so we make the whole /usr/ports read-write.  We need to enter the jail to create the mountpoint, /usr/ports before the fstab can be successful.

Click image to enlarge

On sylve, we need to edit the fstab for the jail.  The jail I created has its name and an ID number in parenthesis, I set mine as 200 so that the ID will be distinct between VMs and Jails.  We click the jailname we gave it, underneath our hostname in the tree on the left.  Underneath the summary tab, at the bottom is options.  We click the options tab which opens a window showing property and value.  Right now there is no value for the FSTab Entries property.  We click on that line, and a new button, Edit FSTab Entries appears.  When we click on the edit button, we get an empty window with the title FSTab Entries.  Into the FSTab Entries window we type the line to add the host ports tree into the jail.

/usr/ports   /zroot/sylve/jails/200/usr/ports   nullfs   rw   0   0

Getting the nullfs mounted ports tree took me a bit of trial and error, since things are slightly different with sylve than they are for other jail managers.  I couldn't look at examples or forum posts and do the exact same thing, I had to try a couple ways to see which was successful by discovering which attempt permitted the jail to start.  Once it started I could use the console button below the summary button to verify by doing an ls of /usr/ports.

The other thing I want to do, to make things easier for myself, is to use a ports tree overlay of my ~/GitRepos/PortsTree onto the official FreeBSD ports tree.  The only time I have done this successfully was using the instructions to build xlibre the first time.  Its already been a while since I did that, but referencing the instructions at b-aaz/xlibre-ports on github, it tells me to clone that repo and enter it, then echo the pwd into the make.conf, and so to translate this to my purposes I do a little differently.

cd ~/GitRepos/PortsTree
echo "OVERLAYS=$(pwd)/" >> /etc/make.conf

This I can also do on the host in order to setup the overlay there as well.  What I am unsure about but seems likely, is that I may need to edit or remove the various directory Makefiles and the MOVED file.  Doing this in the jail will inform me about whether that is necessary.  However, to even attempt the ports overlay that I want, I will need to add two more lines to the fstab and create its mountpoint /home.

/home/tigersharke   /zroot/sylve/jails/200/home/tigersharke   nullfs   rw   0   0

One more detail that missed my recognition, we need to initialize pkg by initiating a pkg update.  Once this is done, a fetch in any of my -dev ports will succeed to register.  When I did a make config I discovered a new improvement that tripped me up some years ago.  Attempting to make config will cause bsddialog and portconfig to be installed, which otherwise would have caused the make config to fail, this was a pleasant surprise.

Working on porting

I had forgotten that I had a duplicate of yajl in the devel directory of my own PortsTree which necessarily conflicted with the official one because I didn't name it to be unique.  This didn't matter until I used my pseudo ports tree with the overlay, but now it caused unusual errors.  Easily fixed, I renamed the directory with the -dev suffix.  At the same time I renamed any extra Makefiles and the touched MOVED file with a hid- prefix.  I may not need them but I'm keeping them around for now.

When I tried to build Mudlet outside of the jail, before I set all this up, I ran into some weird errors.  It took me a while to recognize that the issue was one Mudlet dependency.  The lua-luarocks dependency is an oddity itself.  If you tick the option to use luajit instead of ordinary lua, then lua-luarocks has no flavors.  If you do not tick that option, lua-luarocks does have flavors.  This is somewhat of a concern but I am not sure how to permit either to satisfy the dependency for Mudlet.  I am going to add a note in the Makefile and use the unticked option build of lua-luarocks.

make all-depends-list | sed -e 's#^\/usr\/ports##' > all-depends
yes|pkg install `cat all-depends`

Another oddity originates from how I populated the jail.  Without anything installed, I chose to make an all-depends-list and use that to have pkg install all of those dependencies in the jail.  This greatly speeds things up but since this is an empty jail, there are more than 1000 things to install.  After all of that was installed, the build declares that I need a different port to satisfy the zip requirement, so I revised that dependency line and later, outside of the jail, it wanted to use the other zip dependency.  I decided to have both listed and let the system install whatever.  I am not sure yet if I need to have an option for whether to use zip or minizip.

The build eventually succeeded inside the jail and outside the jail, and I continued to update Mudlet for the more recent upstream commits, my own Mudlet-dev repo advancing from June 26th all the way to July 6th.

Later I adjusted the patch files with conditional wrappers and then on a whim, checked to see how many of the four patches were actually required.  What I discovered was that only one file must be patched and it needs both of the two patches within the GeyserAdjustableContainer.lua file.  I have since submitted a pull request for this patch to upstream Mudlet so that either my corrections will be applied for FreeBSD or better understanding of the issue will lead to changes which may obsolete FreeBSD specific patches for it.

After I setup git with my email and user name, I had to fix the dubious directory permission/ownership setting in git to quiet that complaint.  I had to add my github token file to my root in the jail, so I can copy and paste it, or maybe copy from outside and paste it into the console.  I also may want to eventually add any number of aliases or scripts that would make porting easier within the jail.  One thing that will be very nice is that the options for any port can be different within the jail than outside.  This means that when I rebuild luanti or mudlet for the latest commit, when I build it to use, I can leave the options set to only those that I personally need or want.  I won't need to tick and then untick for the two builds, but also there will need to be two builds because one is in the jail.  I frequently did build twice normally, so this will be a small improvement.

Sylve jail thoughts

Using sylve via librewolf is with a console screen in the browser.  This is a bit different than an xterm, the cursor is a blinking bar on the left edge of the current occupied space or character which takes a little getting used to.  Another difference is that I like to use the mouse to select and paste which is common for Unix/X but can be challenging in the sylve console.  Instead of select with one mouse button and paste with another, its select and control-v to paste.  This is functional but a little strange.  Selecting within the console can be difficult to limit to the line you desire, primarily if the line is near the bottom edge.

I had moments when sylve (or librewolf) forgot that I was typing in the console, and it took a little bit to sort of wake it up, or maybe it simply did not scroll to match what was displayed.  There is also some weirdness with how the option screen for a port configuration appears to me, a bit washed out and a kind of broken almost graph-paper appearance though all a pale blue.  I don't know why it looks like this, except that it is some kind of interpretation by either sylve or librewolf.  Its not something I could determine as able to be changed.

We can change the font size and color or background color from a gear icon in the upper right, so I chose green text on black, and size 14 font.  Closing that dialog after the change sometimes leaves it accessible, so pressing return or clicking the console could brings it back, but it is easily hidden again and doesn't appear unintentionally another time.

I figured out how to get the text in the console to have a gap between it and the bounds of the window with a little CSS but I was unable to figure out how to get the lines in the textarea to have a bit more spacing, and I have not yet checked whether I can use CSS to adjust the cursor.

.terminal-wrapper
{
    padding-left:8px;
    padding-top: 4px;
    padding-bottom: 4px;
    user-select: auto!important;
}

Sylve is definitely functional for use to setup a jail and to stop or start it, and I will be continuing to use it for that function, as well as for the virtual machine I setup previously.  I believe I can access a jail with a simple chroot into it since its localhost, so if I wanted, I could do similarly as with the VM and reach it with an ordinary xterm but with chroot instead of ssh.  Perhaps I am misremembering or it might have been a thing with Warden, so I was wrong, but it seems as though using sh is also the way to access the jail if I do not use the sylve console.

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