AutoHotkey: Wait for Process to Exit

In today’s small hack session the requirement sounds rather simple: Wait for a given process to exit and then exit the AutohotKey script itself – all while being non-blocking.

The most obvious choice would be a simple loop that checks whether the process still exists. This is also the implementation you will find when searching for this problem on the web. The drawback of this approach is the high cost in runtime, so that is no good; why would I want to burn CPU cycles constantly?

A much better way is to register a callback with Windows and have the operating system notify you once the process has exited.

#Requires AutoHotkey v2.0.2
#SingleInstance Force

DllCall("LoadLibrary", "Str", "Kernel32.dll")

ProcessExitCallback := CallbackCreate(OnProcessExit)

; Function to register a callback for process exit
RegisterForProcessExit(pid) {
    ; Open the process handle with SYNCHRONIZE access
    hProcess := DllCall("OpenProcess", "UInt", 0x00100000, "Int", 0, "UInt", pid, "Ptr")

    if (!hProcess) {
        MsgBox("Failed to open process handle. Error: " DllCall("GetLastError"), "Error opening process handle", "OK IconX")
        return
    }

    ; Create a variable to hold the wait handle
    waitHandle := 0

    ; Register the wait
    success := DllCall("Kernel32.dll\RegisterWaitForSingleObject"
        , "Ptr*", &waitHandle         ; Output wait handle (mutable variable)
        , "Ptr", hProcess            ; The process handle
        , "Ptr", ProcessExitCallback ; The callback function
        , "Ptr", pid                 ; The parameter passed to the callback (PID)
        , "UInt", 0xFFFFFFFF         ; INFINITE wait time
        , "UInt", 0x00000000)        ; WT_EXECUTEONLYONCE

    if (!success) {
        MsgBox("Failed to register wait. Error: " DllCall("GetLastError"), "Error registering callback", "OK IconX")
        DllCall("CloseHandle", "Ptr", hProcess)
        return
    }
}

; The callback function to execute when the process exits
OnProcessExit(param, TimerOrWaitFired) {
    ExitApp(0)
}

; Main script execution
pid := ProcessExist("Notepad.exe") ; Replace with your target process name
if pid
    RegisterForProcessExit(pid)
else
    MsgBox("Notepad.exe is not running!", "Notepad", "OK IconX")

The callback approach has the advantage of being able to run in the background for a long time without noticeable performance impact.

AutoHotkey: Center a Window on the Program’s display

This holiday was all about window management and AutoHotkey for me. Despite the name sounding a lot like AutoIt and being based off of it, AHK is a pretty different beast.

Today’s small hack session is all about centering the active window on the screen the window is on. For that to work, we first have to determine the window’s position and width, then calculate the window X and Y center point, then loop through the displays to get their work area properties and determine which of the displays the window is most likely on.

After that we can calculate the new X and Y positions based on the window dimensions and the determined display work area.

    activeWindow := WinExist("A")
    if !activeWindow
        return

    ; Retrieve the active window's current position and size
    WinGetPos(&winX, &winY, &winWidth, &winHeight, activeWindow)

    ; Calculate the center of the window
    windowCenterX := Round(winX + winWidth / 2, 0)
    windowCenterY := Round(winY + winHeight / 2, 0)

    ; Get number of monitors - but only ones used for the desktop!
    monitorCount := SysGet(80)

    ; Set initial value to check if we found the monitor the window is on...
    monitorIndex := -1

    ; Loop through each monitor to find which one the window is on...
    ; Do this by checking on which monitor the center of the window is, otherwise we get false positives.
    Loop monitorCount {
        MonitorGetWorkArea(A_Index, &workAreaX, &workAreaY, &workAreaWidth, &workAreaHeight)

        if (windowCenterX >= workAreaX and windowCenterX <= workAreaWidth
            and
            windowCenterY >= workAreaY and windowCenterY <= workAreaHeight)
        {
            monitorIndex := A_Index
            break
        }
    }

    ; If still initial value, exit...
    if monitorIndex = -1
        return

    ; Get the dimensions of the determined monitor's work area
    MonitorGetWorkArea(monitorIndex, &workAreaX, &workAreaY, &workAreaWidth, &workAreaHeight)

    ; Calculate new position to center the window
    newX := Round(workAreaX + ((workAreaWidth - workAreaX) - winWidth) / 2, 0)
    newY := Round(workAreaY + ((workAreaHeight - workAreaY) - winHeight) / 2, 0)

    ; Move the window to the new position
    WinMove(newX, newY, , , activeWindow)

This works nicely on multi-monitor setups.

PowerShell: Checking a Font for Specific Glyphs

I had the requirement to check which of the fonts installed on my machine supported Japanese numerals. The easiest way to actually verify this was by doing it through a Powershell script.

The method below only works on Windows machines since it relies on the PresentationCore assembly.

param (
    [switch] $Verbose
)

Add-Type -AssemblyName PresentationCore

# List of Japanese numerals to check
$japaneseNumerals = @(
    [char] 0x4E00, # 一
    [char] 0x4E8C, # 二
    [char] 0x4E09, # 三
    [char] 0x56DB, # 四
    [char] 0x4E94, # 五
    [char] 0x516D, # 六
    [char] 0x4E03, # 七
    [char] 0x516B, # 八
    [char] 0x4E5D, # 九
    [char] 0x5341  # 十
)

# Function to check if a font contains a specific glyph
function FontSupportsCharacter {
    param (
        # The font name to check
        [string] $fontName,
        # The character to check
        [char] $character
    )

    try {
        # Create a GlyphTypeface object for the font
        $typefaces = (New-Object System.Windows.Media.FontFamily($fontName)).GetTypefaces()
        foreach ($typeface in $typefaces) {
            $glyphTypeface = $null
            $typeface.TryGetGlyphTypeface([ref]$glyphTypeface) | Out-Null
            if ($glyphTypeface -and $glyphTypeface.CharacterToGlyphMap.ContainsKey([int][char]$character)) {
                return $true
            }
        }
    } catch {
        # Handle errors, e.g., if the font cannot be loaded
        Write-Verbose -Message "Error loading font: ${fontName}"
    }

    return $false
}

if ($Verbose) {
    $VerbosePreference = "Continue"
}

# Get all installed fonts
$installedFonts = (New-Object System.Drawing.Text.InstalledFontCollection).Families

# Loop through each installed font
foreach ($font in $installedFonts) {
    $glyphFound = $true
    foreach ($numeral in $japaneseNumerals) {
        $exists = FontSupportsCharacter -fontName $font.Name -character $numeral
        if (-not $exists) {
            $glyphFound = $false
            Write-Verbose -Message "Font `"$($font.Name)`" does NOT contain glyph for `"${numeral}`" (U+$([System.String]::Format("{0:X4}", [int][char]$numeral)))."
            break
        }
    }

    if ($glyphFound) {
        Write-Output "Font `"$($font.Name)`" contains all Japanese numerals."
    }
}

Personally, I would expect the operating system to be able to filter fonts, but unfortunately it falls to the user to gain these insights.

tacky-borders enables beautiful, animated borders on Windows

Perhaps you have seen those beautiful, configurable and often animated borders on Hyprland and wished that something similar existed on Windows 10/11.

With tacky-borders‘ upcoming version 1.0 you can get a good number of those features on Windows.

The software can be configured through a yaml file and allows customization of the border thickness, radius, colors, animation and allows application-specific overrides for a number of settings to make sure the borders are properly aligned on applications like Chrome-based browsers.

tacky-borders allows for the “spin” animation, which can be combined with a gradient color setting to create a nice looking RGB crawl around the active window.

Previously I have been using Komorebi’s own implementation, which had a few drawbacks. The border was not as configurable, it was quite pixelated and there was a notable delay when switching between windows. tacky-borders solves all of those issues.

While these features may sound tacky (no pun intended!) to you, having a configurable visual indicator for the active window is incredibly handy in an age where applications render their own titlebars (or lack them altogether). It brings an element of visual clarity, which I highly appreciate.

Why Shiori is still the best Read It Later software

Archiving web contents is important. Not only to have an exact copy of the content from the time you were reading it, but also as a means of preserving the information in an ever volatile world.

In the past I was a huge fan of del.icio.us, a social bookmarking and tagging service. Syncing my bookmarks across different devices felt like the best choice – until I had visited some of the links and found myself in a world of dead links. Ever since then I have been trying to keep an archived copy of the sites around.

Personally, I still think that historious is an absolutely awesome service and I happily pay the subscription fee every year. historious handles more complex sites like reddit or YouTube perfectly and is a great way to quickly archive, tag and search – basically my old del.icio.us workflow but on steroids.

However, this still means you are reliant on someone else to host the service for you. And with the volatile way the web is, historious might just disappear tomorrow. The answer: Host your own application to do the job. But which one?

Over the course of 2023 and 2024 I have evaluated multiple different solutions with very different ideas how to approach the problem of how to archive a website and – more importantly – what to archive from a website.

I always come back to what I personally consider the best software around: Shiori.

While Shiori has some issues with reddit posts, it handles classic forum posts just fine. The content extractor also allows to archive PDF files and news articles from pretty much all the websites I ever visit. Unlike solutions like Obsidian’s Web Clipper that often picks the wrong content, I can be confident that I do not need to double-check the archive of everything I push into Shiori.

Shiori also has a basic Manifest v2 browser extension that allows tagging and saving contents with two clicks – absolutely awesome and all you really need! It does one thing and it does it well.

You have plenty of control to determine on a post-by-post basis whether an archive is available publicly or not and the user interface works great on mobile devices too.

Unlike Readeck or Wallabag, Shiori extracts the contents I want. This might not sound like a big deal to you, but for me it is incredibly important to have a solution I can depend on. Unlike MyBase the contents stays intact when being archived.

Over a long period of time I have come to the realization that for my use-case, Shiori simply seems to be the best fit. The out-of-box experience is just so smooth and stays out of your way. I cannot recommend the software highly enough.

Synology DSM7: “No Health Status”

After cleaning up my NAS I was greeted by a warning after restarting my DS2413+. My pool was in a degraded state, but all drives were fine? Checking the total pool use also did not give me any hints, as I still had plenty of free space.

Turns out none of my drives were reporting any health status anymore (“Health status not available”). Trying to open the S.M.A.R.T. values would just show an error about a failure to communicate with the drive. Those messages popped up for every drive. Did I kill my NAS during cleaning?

Of course not. Manually updating the S.M.A.R.T. database via Diskstation fixed the issue. Somehow the database got corrupted, possibly during an automated update?

Anyway, after updating the database the status turned back to all green. For good measure, I did run a scrub on my pool.

OBS – Websocket Stays Open After Exiting

I have been plagued by this issue for a while: After closing OBS, a zombie process would continue to block the TCP port previously used by OBS’s websocket plugin. Trying to query the process ID never yielded any results. So what gives?

In my case, it turns out that the likely culprit was the OBS Droidcam plugin, which just received an update to mitigate exactly this misbehaviour.

After installing the update I can confirm that the zombie process and the listening TCP port no longer occur.

Why Video Game Consoles are Bad

I recently wondered why my Playstation 3 would turn on and stay in limbo. Turns out that the console was trying to update its firmware to version 4.90 and failed repeatedly.

The reason for all the commotion is error 8002F1F9, which turns out to be a faulty WiFi/Bluetooth board in the console itself.

On a PC, you would utter a sigh and replace the faulty component. On a Playstation 3 Slim you are shit out of luck. The console is now junk because the corresponding components are soldered right onto the motherboard, thus not being user-serviceable.

This is a sorry trend with mass-market electronics. I can get another second-hand Playstation 3 or try Sony’s awful cloud-gaming service, or I will have to dump all my games and use an emulator.

Only one of these routes ensures I can play Blur in 10 years: Emulation.

After Sony removed purchased video content from user libraries and given how fickle and stupid licensing agreements are for cloud-gaming, there is no way I will ever trust Sony with Playstation Now.

Similarly, there is no point in buying another set of ageing hardware, hoping it will last me this time.

Thankfully, my entire Playstation 3 library consists of physical media. Platform holders like Nintendo or Sony have a track record of dropping support for older devices, locking people from accessing their purchased digital content. This is why physical media is the way to go, and going through the process of dumping all your media is a must.

This way, you can play games and watch movies on a PC through emulators. The only digital storefront I have any trust in is Valve’s Steam. In the 20 years I have used Steam, it never failed me once. Meanwhile, Sony has repeatedly screwed me over.

I would also like to address that exclusivity is a cancer. Alan Wake 2 can be the best game of a decade – if it is locked to the Epic Game Store and unavailable on Steam, I do not care for it. Bloodborne is awesome, but I could not care less since it is trapped on a dead platform with shit FPS, forsaken by its creators and publishers.

Developers and publishers should bring their games to the only platform that has shown to be consistently backwards compatible over the last 30 years: The personal computer. Especially since newer generation consoles are essentially neutered PCs anyway.

Because whether you like it or not, your little black box will fail. And when it does, it will lock you out from playing your digital purchases. Not because it is impossible to provide you with proper backwards compatibility on newer systems but because you will begrudgingly buy the same game again and again. Nintendo knows it. Sony knows it.

Increasing SVP-4 stability on 32bit Zoom Player

The SmoothVideo Project is pretty awesome. My only gripe as a passionate user of Zoom Player is that the combination of LAV decoders and the ffdshow raw filter to inject SVP into the pipeline can be pretty unstable. Seeking around in playing media often leads to the media player crashing.

The culprit seems to be ffdshow. While the filter was an absolutely incredible and indispensable part of every afficionados’ filter graph a few years ago, the project is pretty much abandoned. So what are our options to mitigate the crashes without switching to mpv or another 64bit media player?

Turns out that CrendKing has built the awesome AviSynth Filter. After unpacking and installing the filters, we can alter Zoom Player’s Smart Play decoder settings:

Open Zoom Player’s Smart Play options and select a video decoder to test with. In this case, let’s alter the H264 configuration. Click “Configure”.

Select the LAV Video Decoder profile, click “Add Filter” and select the “AviSynth Filter” from the list. Make sure that LAV Video Decoder is the first filter and AviSynth the second.

Afterwards, click “Save active filter(s) as a New Profile”, give it nice name, increase the priority to top and select the profile to activate it. Close the dialogue with OK.

Apply the settings, restart Zoom Player and play a h.264 video file to test. You should see that SVP is active. Seeking in the video should no longer crash the entire player.

Adding Kagi as a Vivaldi Image Search Engine

Kagi, the search engine I was pretty hyped about a year ago, recently added support for loading up on credits via Paypal and other European-friendly, non-credit-card ways.

I have a ton of things to say about the way they flip-flop about their pricing model but that is neither here nor there. Today’s topic is all about adding Kagi to Vivaldi’s list of image search engines.

If you define the following image search URL for Kagi, the reverse image search will work in Vivaldi:

https://kagi.com/images?q={google:imageURL}&reverse=reference

That is some pretty hype stuff because that means I can fully kagi-fy my browser usage.

If you have Kagi’s Chrome extension installed, you will most likely need to uninstall or disable that extension. For some reason, Vivaldi (at the time of writing) ignores the search engine definitions created through the extension.

Launching Remnant 2 with Steam Support through a Batch File

Gunfire Games’ highly anticipated Remnant II is close to launch. If you are not fond of having to start it through Steam’s URL handler or Steam directly, you will quickly notice that simply launching Remnant2.exe leads to the game being offline/in a different mode.

The Unreal Wiki provides the answer as to why this is: The game expects a steam_appid.txt with the corresponding ID of the game (in this case: 1282100) to be present in the main executable’s directory.

You can easily create a Remnant2.bat in steamapps/common/Remnant2 with the following content:

@echo off
set APPID=1282100
echo %APPID% > "%CD%\Remnant2\Binaries\Win64\steam_appid.txt"
start "" "%CD%\Remnant2.exe"

Starting this batch file will automatically create the required steam_appid.txt. The game will consequently use the Steam-specific profile and save directory and launch with full Steam support.

VMware Workstation “hostWin32.c:559” error

When running VMware Workstation 16 or 17 on a Windows host, you perhaps encounter the following error:

PANIC: VERIFY bora\vmx\main\hostWin32.c:559

This error happens when you have a Windows VM running and then try to launch a second Windows VM. As soon as the second machine boots into the desktop/login, it crashes, and the machine stops.

Luckily, this VMware forum post gave me a workaround that solved the issue. Hopefully, it will be helpful for you as well.

I am using an Nvidia graphics adapter and have set the “Max Frame Rate” and “Background Application Max Frame Rate” settings via the Nvidia Control Panel to values other than “Off”. This is what caused the issues for me.

The solution: Simply add a program-specific customization for vmware.exe, ensure both settings are set to “Off” in the customization, and you should be good.

Shape up your Fonts

When I am not busy contemplating suicide, I am a sucker for good-looking, case-appropriate fonts. Especially, when you are staring at code pretty much all day long.

Besides looking good, fonts can also carry additional glyphs and ligatures that can make source code easier to read or render the terminal more beautiful. If you want to see why not every monospace font is equal, you can check out various free and commercial options over at Programming Fonts.

A Font For Code

For programming, I really like MonoLisa. Across Sublime, VS Code and all my JetBrains IDEs, this is the font for editing code. Yes, it might seem pricey, but it also has great customisation options and gives me those nice, wide characters I crave.

Unfortunately, MonoLisa does not have the old DOS-style glyphs for drawing boxes and stuff, so using it on a terminal is not an option for me.

Someone did go through the trouble of creating a Nerd Font patcher specifically for Mono Lisa, which is highly appreciated.

I Command Thee

For the command line, I have found no better font than Gintronic. Yes, it is not the newest font anymore. Mark Frömberg has stated that the font is essentially finished and that he has no plans for ligatures, but that is okay. Gintronic has all the beautiful bits and bops you would expect for a terminal-ready font.

And it can be patched with Nerd Fonts‘ font patcher to add all those nifty icons for Powerline. Cool stuff!

Too Expensive?

“But tsukasa”, I hear you laugh, “Hack is a fine free font! Why would you pay for fonts – twice, even. You big dumb buffoon! JetBrains ships with a perfectly fine monospace font, to boot!”.

And that is a totally fair point. If you are happy with your fonts and see no reason to change them, paying 150-200 EUR per font might seem steep.

I stare at these beautiful characters for about 8 to 10 hours per day, so I need something that is easy on my eyes and pleases my aesthetic senses. Both fonts (or is it font families?) fulfil these requirements and have their own respective strengths. This might seem incredibly nit-picky, but your eyes get hung up if characters are “wrong”. Finding a good font is like finding a good keyboard or pair of shoes – they might all look the same from afar, but the differences become clear when using them.

Plus, I have no issue compensating the fine people making these fonts for their hard work.

A Eulogy for VMware Project Nautilus

Do you remember the hype in 2020 when VMware announced their own OCI-compliant, Docker-compatible container support in their desktop virtualization solutions? I even wrote about it in 2021 after it had some time to mature.

So where is Project Nautilus in 2023, a good three years later? Dead, in the grave.

In 2021, I called vctl “a promising disappointment”, but in 2023 I will have to change that title to “an ambitious stillbirth”. After three whole years and a new major (paid) VMware Workstation version, vctl is still exactly where it was back in 2020.

When people poke VMware staff about this on Twitter, the response pretty much shuts the door on any further development:

I wish I could say it were in a better state. […] We’re taking the time to address this in a more holistic way.

Now, just in case you do not know what “taking the time to address this in a more holistic way” means in plain old English: There are no plans to put another second of development time into it, but marketing says it is a bad look to deprecate it after just one major version, especially since VMware already removed Shared VMs from the desktop virtualization solutions.

This is such a shame. VMware had an admittedly early but incredibly compelling feature in the pipeline that perfectly complemented its core technology and gave developers another reason to love Workstation or Fusion.

Rest in peace, Project Nautilus. I bemoan the waste of potential and awesomeness.

1Password SSH-Agent + Pageant on Windows 10

1Password is a great password manager. For a little less than a year 1Password can act as your ssh-agent, meaning that you only need to store the public keys locally, the private key comes from 1Password itself. There are a few things to note and set up if you are on Windows.

I hope this post will help people interested but somewhat bewildered by some parts to get it to run and understand the relevant bits and bops.

Please note that this post assumes that you already have Windows’ own version of the OpenSSH client installed and possibly updated to a newer version. I also assume that you have already imported at least one SSH key pair (public/private key) into 1Password (under the SSH Key type).

Windows Hello

A requirement for 1Password’s ssh-agent is that you have Windows Hello enabled. The documentation on 1Password’s end makes it sound like you need a Windows Hello-compatible webcam or some kind of fingerprint sensor or whatnot.

This is not true, you can also use a simple PIN as your 2nd factor. This is easily set up via Windows 10’s Settings – Accounts – Sign-in options.

Disabling Windows’ SSH Agent

Open the Computer Management, browse to the services and ensure that, if it is present, the “OpenSSH Authentication Agent” is disabled.

This is required because the ssh-agent on Windows is published through a pipe. If the OpenSSH Authentication Agent is running, the pipe is already present – causing an issue with 1Password trying to fill that role.

Enabling 1Password SSH Agent

Within 1Password’s desktop client, go to the settings – Security and enable “Unlock using Windows Hello” as well as “Show Windows Hello prompt automatically”.

Now switch to the Developer tab and tick the box “Use the SSH agent”.

Configuring the SSH Client

For the sake of argument, let us assume that you have a dedicated ssh key for communicating with GitHub and that you already imported that ssh key into 1Password.

How do we get the Windows-version of the OpenSSH client to use the agent for this?

Edit your ~/.ssh/config file and generally enable the agent:

Host *
  IdentityAgent //./pipe/openssh-ssh-agent

We want to specify the exact key to use for GitHub, though. So let’s return to 1Password, select the public key of your ssh key and save it locally. Move the file to your ~/.ssh directory and rename it (i.e. id_ed25519_github.pub).

Edit the ~/.ssh/config file again and add/alter an entry for GitHub:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github.pub
  IdentityAgent //./pipe/openssh-ssh-agent
  IdentitiesOnly yes

Save the configuration. If you still have your pair of pubkey/privkey in the ~/.ssh directory, move it aside for a test. By instructing the use of the id_ed25519_github.pub, 1Password will look up the matching ssh key from it’s archive.

Otherwise, like other ssh-agents, it would try several keys. We do not want this.

Now you should be able to connect to GitHub via ssh and have 1Password provide the proper key:

ssh -T git@github.com

If you see the input field for your Windows Hello pin, things should be peachy:

Hi XXXXXXXX! You've successfully authenticated, but GitHub does not provide shell access.

Sweet stuff! You should also be able to check the contents of your 1Password ssh key archive:

ssh-add -l

Even better!

Git

Git for Windows ships with its own copy of ssh. We need to make sure the Windows version of the OpenSSH client is being used. So, as per documentation, we explicitly set the path to the ssh client:

git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"

If you have downloaded a newer version of the Windows-version of the OpenSSH client, point it to the appropriate executable.

Pageant

But what about applications that use Putty’s Pageant? Having two instances provide keys is not what we want.

Thankfully, WinSSH-Pageant is able to bridge between the two tools. Simply install WinSSH-Pageant, make sure to close Putty’s Pageant and… well, that’s it.

If you use WinSCP or other software using Putty’s components, WinSSH-Pageant should relay the agent requests to 1Password, meaning that the Windows Hello PIN will be prompted and keys will be delivered from 1Password’s archive.