このサイトはWebfile便で作成しました利用はこちら

Building Neovim, Gemini CLI, and Onishi Layout (Vim-optimized) on GPD Micro PC 2

The story of spending too much time setting up just to code in spare moments

Introduction

This article is a memorandum of my journey to build "a Vim environment that fits in my pocket and is ready to code instantly."

Using the ultra-compact GPD Micro PC 2, I initially tried a native Linux installation but faced startup times of over 30 seconds. A dev environment that isn't ready the moment inspiration strikes is fatal for a mobile device.

The solution I arrived at was Windows 11 + WSL2. By combining it with WezTerm, I achieved instant startup. I introduced the Onishi layout using AutoHotkey, enabling me to operate Vim and develop from anywhere!

GPD Micro PC 2 Development Environment

Step 1: Foundation (WSL2 & WezTerm)

WSL2 (Windows Subsystem for Linux 2) allows running a Linux kernel on Windows with near-native performance. It installs with a single command wsl --install and integrates seamlessly with the Windows filesystem.

Reference: The Definitive WSL2 Setup Guide

For beginners, the article Installing and Using WSL2 Ubuntu on Windows 11 (Anyone can do it) is highly recommended. It provides a step-by-step guide with images, allowing even those unfamiliar with terminal operations to set up an Ubuntu environment easily. If you are unsure about preparing the WSL environment required for this guide, I suggest reading this article first.

Installing WezTerm

WezTerm is chosen for its speed and configurability. We use a bridge configuration to load the settings directly from WSL.

  1. Windows Side: Create C:\Users\{{Username}}\ .wezterm.lua with the bridge code.
  2. WSL Side: Place the actual config at ~/dotfiles/windows/.wezterm_windows.lua.
📂 Windows .wezterm.lua (Bridge)
Path: /mnt/c/Users/{{Username}}/.wezterm.lua
-- Change 'Ubuntu' to your distribution name
-- Note: Use forward slashes (/) even on Windows to avoid issues
return dofile("//wsl$/Ubuntu/home/{{Username}}/dotfiles/windows/.wezterm_windows.lua")
📂 WSL .wezterm_windows.lua (Actual Config)
Path: ~/dotfiles/windows/.wezterm_windows.lua
local wezterm = require("wezterm")
local config = wezterm.config_builder()

-- 1. Font Settings
config.font = wezterm.font_with_fallback({
        "JetBrains Mono", -- WezTerm built-in
        "Meiryo", -- Windows standard Japanese font
        "Segoe UI Emoji", -- Windows Emoji
})
config.font_size = 16 
config.enable_tab_bar = false

-- 2. Window Decorations (No Title Bar)
config.window_decorations = "RESIZE"
config.window_background_opacity = 0.8

-- 3. Blur Effect (Windows 10/11)
config.win32_system_backdrop = "Acrylic"

-- 4. Colors
config.colors = {
        foreground = "#CBE0F0",
        background = "#011423",
        cursor_bg = "#47FF9C",
        cursor_border = "#47FF9C",
        cursor_fg = "#011423",
        selection_bg = "#033259",
        selection_fg = "#CBE0F0",
        ansi = { "#214969", "#E52E2E", "#44FFB1", "#FFE073", "#0FC5ED", "#a277ff", "#24E
AF7", "#24EAF7" },
        brights = { "#214969", "#E52E2E", "#44FFB1", "#FFE073", "#A277FF", "#a277ff", "#
24EAF7", "#24EAF7" },
}

-- 5. Default Shell: Launch WSL (Ubuntu) in Home
config.default_prog = { "wsl.exe", "--cd", "~" }
-- PowerShell 7
-- config.default_prog = { "pwsh.exe" }

return config

Step 2: Linux Environment Init (init_linux.sh)

We created init_linux.sh to automate the WSL setup. This script installs critical components:

List of Major Components Installed
  • Dev Tools: build-essential, git, curl, unzip
  • Shell: zsh
  • Package Manager: Homebrew (Linuxbrew)
  • WSL Integration: wslu (wslview)
  • Apps: Obsidian (with Japanese support)
  • Locale: en_US.UTF-8 (for PostgreSQL fix)
📂 init_linux.sh (Full Source)
Path: ~/init_linux.sh
#!/bin/bash

# WSL/Linux Environment Setup Script
# Stop on error
set -e

echo "======================================="
echo "|---- Locale Generation (Fix for Postgres) ----|"
echo "======================================="
# Generate en_US.UTF-8 locale for PostgreSQL etc.
if [ -f /etc/debian_version ]; then
    sudo apt-get update
    sudo apt-get install -y locales
    if ! grep -q "^en_US.UTF-8 UTF-8" /etc/locale.gen; then
        echo "Generating en_US.UTF-8 locale..."
        sudo locale-gen en_US.UTF-8
        sudo update-locale LANG=en_US.UTF-8
        export LANG=en_US.UTF-8
    else
        echo "en_US.UTF-8 locale already exists."
    fi
fi
echo -e "\n --------------- END ----------------"


echo "======================================="
echo "|---- Base Dependencies (apt) ----|"
echo "======================================="
# Assuming Debian/Ubuntu
if [ -f /etc/debian_version ]; then
    # Install zsh here
    if ! dpkg -s build-essential zsh >/dev/null 2>&1; then
        read -p "Install build-essential, zsh, etc? (y/n) " Answer < /dev/t
ty
        case ${Answer} in
            y|Y) 
                sudo apt-get update
                sudo apt-get install -y build-essential curl file git unzip zsh
                ;;;
            n|N) 
                echo "Skipped apt install" ;;; 
        esac
    else
        echo "build-essential, zsh already installed"
    fi
else
    echo "Skipped apt commands (Not Debian/Ubuntu)."
fi
echo -e "\n --------------- END ----------------"

echo "======================================="
echo "|----------- wslu (WSL Utilities) -----------|"
echo "======================================="
if [ -f /etc/debian_version ]; then
    if ! command -v wslview >/dev/null 2>&1; then
        read -p "Install wslu (wslview)? (y/n) " Answer < /dev/tty
        case ${Answer} in
            y|Y) 
                # If add-apt-repository is missing
                if ! command -v add-apt-repository >/dev/null 2>&1; then
                     sudo apt-get install -y software-properties-common
                fi
                sudo add-apt-repository ppa:wslutilities/wslu -y
                sudo apt-get update
                sudo apt-get install -y wslu
                ;;;
            n|N) 
                echo "Skipped wslu install" ;;; 
        esac
    else
        echo "wslu already installed"
    fi
fi
echo -e "\n --------------- END ----------------"

echo "======================================="
echo "|----------- Obsidian (WSL) -----------|"
echo "======================================="
if [ -f /etc/debian_version ]; then
    read -p "Install Obsidian (and Japanese setup)? (y/n) " Answer < /dev/	ty
    case ${Answer} in
        y|Y) 
            echo "Installing Obsidian..."
            # Get latest deb URL from GitHub API
            deb_link=$(curl -s https://api.github.com/repos/obsidianmd/obsidian-releases
/releases/latest | grep "browser_download_url" | grep "amd64.deb" | cut -d '"' -f 4)
            if [ -z "$deb_link" ]; then
                echo "Could not get Obsidian download link."
            else
                wget "$deb_link" -O obsidian_latest.deb
                sudo dpkg -i ./obsidian_latest.deb || true
                sudo apt-get install -f -y
                rm ./obsidian_latest.deb
                echo "Obsidian installed."
            fi

            echo "Installing Japanese fonts, input support (IBus + Mozc), and dependenci
es..."
            sudo apt-get update
            sudo apt-get install -y libasound2t64 fonts-noto-cjk fonts-noto-color-emoji 
fonts-ipafont-gothic fonts-ipafont-mincho ibus ibus-mozc

            # Configure env vars
            if ! grep -q "GTK_IM_MODULE=ibus" "$HOME/.bashrc"; then
                echo 'export GTK_IM_MODULE=ibus' >> "$HOME/.bashrc"
            fi
            if ! grep -q "QT_IM_MODULE=ibus" "$HOME/.bashrc"; then
                echo 'export QT_IM_MODULE=ibus' >> "$HOME/.bashrc"
            fi
            if ! grep -q "XMODIFIERS=@im=ibus" "$HOME/.bashrc"; then
                echo 'export XMODIFIERS=@im=ibus' >> "$HOME/.bashrc"
            fi
            # For zshrc
            if [ -f "$HOME/.zshrc" ]; then
                if ! grep -q "GTK_IM_MODULE=ibus" "$HOME/.zshrc"; then
                    echo 'export GTK_IM_MODULE=ibus' >> "$HOME/.zshrc"
                fi
                if ! grep -q "QT_IM_MODULE=ibus" "$HOME/.zshrc"; then
                    echo 'export QT_IM_MODULE=ibus' >> "$HOME/.zshrc"
                fi
                if ! grep -q "XMODIFIERS=@im=ibus" "$HOME/.zshrc"; then
                    echo 'export XMODIFIERS=@im=ibus' >> "$HOME/.zshrc"
                fi
            fi
            echo "Japanese environment setup complete."
            ;;;
        n|N) 
            echo "Skipped Obsidian install" ;;; 
    esac
fi
echo -e "\n --------------- END ----------------"

echo "======================================="
echo "|----------- Homebrew -----------|"
echo "======================================="
# Check Linuxbrew
if ! command -v brew >/dev/null 2>&1; then
  read -p "Install Homebrew (Linuxbrew)? (y/n) " Answer < /dev/tty
  case ${Answer} in
    y|Y) 
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD
/install.sh)" 
      
      # Linuxbrew Path
      test -d ~/.linuxbrew && eval "$(~/.linuxbrew/bin/brew shellenv)"
      test -d /home/linuxbrew/.linuxbrew && eval "$(/home/linuxbrew/.linuxbrew/bin/brew 
shellenv)"
      
      # Add to shell profile (zsh/bash)
      if [ -f "$HOME/.bashrc" ]; then
        if ! grep -q "brew shellenv" "$HOME/.bashrc"; then
            echo "eval \"$(
brew --prefix)/bin/brew shellenv)\"" >> "$HOME/.bashrc"
        fi
      fi
      if [ -f "$HOME/.zprofile" ]; then
        if ! grep -q "brew shellenv" "$HOME/.zprofile"; then
            echo "eval \"$(
brew --prefix)/bin/brew shellenv)\"" >> "$HOME/.zprofile"
        fi
      elif [ -f "$HOME/.zshrc" ]; then
         if ! grep -q "brew shellenv" "$HOME/.zshrc"; then
            echo "eval \"$(
brew --prefix)/bin/brew shellenv)\"" >> "$HOME/.zshrc"
        fi
      fi
      ;;;
    n|N) 
      echo "Skipped Homebrew install" ;;; 
  esac
else
  echo "Homebrew is already installed"
  test -d ~/.linuxbrew && eval "$(~/.linuxbrew/bin/brew shellenv)"
  test -d /home/linuxbrew/.linuxbrew && eval "$(/home/linuxbrew/.linuxbrew/bin/brew 
shel
llev)"
fi
echo -e "\n --------------- END ----------------"

echo -e "\n"
echo "======================================="
echo "|----------- symbolic link -----------|"
echo "======================================="
read -p "Apply symbolic links? (y/n) " Answer < /dev/tty
case ${Answer} in
  y|Y) 
        # Link dotfiles
        for f in .??*; do
            [ "$f" = ".git" ] && continue
            [ "$f" = ".DS_Store" ] && continue
            
            ln -snfv ~/dotfiles/"$f" ~
        done
    ;;;
  n|N) 
    echo "Skipped symbolic links" ;;; 
esac
echo -e "\n --------------- END ----------------"


echo "======================================="
echo "|--------- create my ci dev env------------|"
echo "======================================="
read -p "Create my ci dev env? (y/n) " Answer < /dev/tty
case ${Answer} in
    y|Y) 
        # mac specific removed (yabai, skhd, karabiner, fonts, GUI apps)
        
        brew install neovim 
        brew install ripgrep
        brew install jesseduffield/lazygit/lazygit

        brew install go
        brew install tree
        brew install gh

        brew install nodebrew
        brew install curl
        brew install deno
        brew install docker
        brew install docker-compose
        brew install zsh-completions
        brew install direnv
        brew install pyenv
        brew install nvm

        brew install lynx
        brew install luarocks
        brew install wget
        brew install rust
        brew install composer
        brew install php
        brew install openjdk
        brew install julia
        # goku is mac only (karabiner)
        brew install mysql@8.0
        brew install postgresql@16
        
        # Fix for Postgres Locale issue if it failed previously
        brew postinstall postgresql@16 || true

        brew install node
        # font-meslo-lg-nerd-font is GUI/System font

        # Common tools & dependencies
        brew install bash
        brew install fish
        brew install fd
        brew install jq
        brew install make
        brew install pkgconf
        brew install python@3.12
        brew install python@3.13
        brew install sqlite
        brew install xz
        brew install zlib
        brew install boost
        brew install protobuf@29
        brew install openssl@3
        brew install llvm
        brew install autoconf
        brew install automake
        brew install libtool
        brew install gettext
        brew install ca-certificates
        brew install unixodbc
        brew install webp
        brew install tree-sitter
        brew install pipx
        brew install pillow
        # brew install pngpaste # Mac only
        brew install stylua
        brew install uv
        brew install awscli

        # brew install mcp-proxy # Check if needed

        # GUI Apps Removed (wezterm, karabiner, postman, chatgpt, raycast, obsidian, cod
ex, aquaskk)

        mkdir -p ~/.nvm

        npm install -g prettier eslint_d
        ;;;
    n|N) 
        echo "Skipped installation" ;;; 
esac
echo -e "\n --------------- END ----------------"


echo "======================================="
echo "|----------- ZSH Setup -----------|"
echo "======================================="
# Check default shell
if [ "$SHELL" != "$(which zsh)" ]; then
    read -p "Change default shell to zsh? (y/n) " Answer < /dev/tty
    case ${Answer} in
        y|Y) 
            chsh -s "$(which zsh)"
            echo "Default shell changed. Effective after logout."
            ;;;
        n|N) 
            echo "Skipped shell change." ;;; 
    esac
fi

Step 3: Neovim & Clipboard Integration

We use win32yank.exe to copy text from Neovim in WSL to the Windows clipboard.

3.1 Install win32yank

Download win32yank.zip from GitHub.

Detailed Steps (Unzip, Move, Path)
  1. Download the latest win32yank-x64.zip from the link above.
  2. Right-click the zip file in Explorer and select "Extract All".
  3. Open your WSL terminal and execute the following commands to configure it.
# 1. Create bin directory and move the file
mkdir -p ~/bin
# (Note: Adjust the source path according to your environment)
mv /mnt/c/Users/{{Username}}/Downloads/win32yank-x64/win32yank.exe ~/bin/

# 2. Grant execution permissions
cd ~/bin
chmod +x win32yank.exe

# 3. Update Path (.bashrc or .zshrc)
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

# 4. Verify
which win32yank.exe

3.2 Neovim Configuration (Lua)

Add the following to your Neovim config (init.lua) to enable synchronization with the Windows clipboard.

📂 wsl.lua (Neovim Config)
Path: ~/.config/nvim/lua/metaphor/wsl.lua
if vim.fn.has("wsl") == 1 then
    -- Use win32yank.exe
    vim.g.clipboard = {
        name = "win32yank",
        copy = {
            ["+"] = "win32yank.exe -i --crlf",
            ["*" ] = "win32yank.exe -i --crlf",
        },
        paste = {
            ["+"] = "win32yank.exe -o --lf",
            ["*" ] = "win32yank.exe -o --lf",
        },
        cache_enabled = 0,
    }

    -- Enable sync
    vim.opt.clipboard = "unnamedplus"
end

Step 4: Windows Troubleshooting & QMK

4.1 Windows Update Nightmare

Trouble Log: I couldn't install apps (LINE and Raycast) from the Microsoft Store. Following the troubleshooting guide, I tried to update Windows, but then the update itself failed with error 0x80070005. I eventually solved this double-bind by performing an In-place Upgrade and waiting patiently.
The Video That Saved the Day

I was saved from this desperate situation by the following video tutorial. It explained the In-place Upgrade process very clearly. I am truly grateful.

【Windows 11】Fixing Windows Update Failure after 25H2 (YouTube)

4.2 Japanese Input (CorvusSKK)

Installed CorvusSKK for Vim-like Japanese input.

  • Configured Google Japanese Input API as a dictionary server for better accuracy.
  • Unified key toggles with Mac standards.

The article Recommended Settings and Usage for CorvusSKK was extremely helpful during setup. Special thanks to the author.

4.3 DIY Keyboard (QMK/Vial) Strategy

When using an external keyboard, QMK layers were optimized to unify Mac/Win operations.

  • Issue: Higher layers mask lower layers in QMK. Layer 6 masked key toggles on Layer 1.
  • Solution: Reorganized layers.
    • Layer 1: Mac Base
    • Layer 2: Windows Base (Ctrl mapped to Cmd position)
    • Layer 3/4: Function layers (Moved higher to be accessible from both bases)
    • Used TG2 key to toggle between Layer 1 and 2.

Step 5: Implementing Onishi Layout (for Vim) with AutoHotkey

To comfortably use the GPD Micro PC 2's built-in keyboard, I implemented Home Row Mods using AutoHotkey v2. This allows modifier keys (Ctrl/Shift) to be triggered by holding home row keys.

Since the GPD Micro PC 2 relies on thumb typing, standard keyboard layouts significantly slow down input speed. By introducing an Onishi-like layout optimized for alternating hand usage, I achieved fast and comfortable typing even with thumbs. This adaptation was essential.

GPD Micro PC 2 Keyboard Layer Layout

Specifically, holding 'y' or 'n' acts as Ctrl, and holding 'f' or 'h' acts as Shift.

For reference, here is the Karabiner configuration I previously used on Mac: gokurakujodo_karabiner_edn_v2.html

Script Deployment (keybind_v3.ahk)

Place this script in your Startup folder (shell:startup).

📂 keybind_v3.ahk (Full Source)
#Requires AutoHotkey v2.0
; shell:startup

; --- Variable Init ---
; Flag for Dual Role feature (Default: true = Enabled)
global UseDualRole := true
global UsedAsModifier := false

; --- QMK-like Configuration ---
global TappingTerm := 200 ; ms
global IgnoreModTapInterrupt := true

GetWaitTime() {
    return "T" . (TappingTerm / 1000)
}

; --- Toggle Shortcut ---
; Ctrl + Alt + k to toggle special settings
^!k::
{
    global UseDualRole
    UseDualRole := !UseDualRole
    MsgBox("Dual Role / Remap: " . (UseDualRole ? "ON (Enabled)" : "OFF (Disabled)"), "Mode Swi
tch", "T1")
}

; --- Exclusion Apps ---
GroupAdd "TerminalEditors", "ahk_exe wezterm-gui.exe"
; GroupAdd "TerminalEditors", "ahk_exe WindowsTerminal.exe"

; ==========================================================
;  Keymap Definitions (Dual Role / Mod-Tap)
;  ★ Enabled only when UseDualRole is ON
; ==========================================================
#HotIf UseDualRole

; --- Helper Function for Combinations ---
; Checks if any of the custom modifiers are held
; Modifiers:
; Left Hand: r, e, t, f, y
; Right Hand: u, i, o, j, y

; ----------------------------------------------------------
; Left Hand Targets
; ----------------------------------------------------------

; Physical '1' -> q
$*1::
{
    global UsedAsModifier
    if GetKeyState("i", "P") { ; Symbols
        SendInput "?"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") { ; Numbers/Tab
        SendInput "{Tab}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") { ; Shift/Upper
        SendInput "Q"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") { ; Ctrl
        SendInput "^q"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") { ; Win (y)
        SendInput "#q"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}q"
}

; Physical 'Tab' -> q (Duplicate of 1)
$*Tab::
{
    global UsedAsModifier
    if GetKeyState("i", "P") {
        SendInput "?"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "{Tab}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "Q"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^q"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#q"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}q"
}

; Physical 'q' -> e
$*q::
{
    global UsedAsModifier
    if GetKeyState("i", "P") {
        SendInput "{!}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "0"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "E"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") { ; Not explicitly defined but consistent
        SendInput "+{End}" ; Emacs line selection/end? Description says "Emacs style lin
e end (all selected)"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#e"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}e"
}

; Physical 'w' -> i
$*w::
{
    global UsedAsModifier
    if GetKeyState("i", "P") {
        SendInput ","
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "4"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "I"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") { ; Not explicitly defined but consistent
        SendInput "^i"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#i"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}i"
}

; Physical 'e' -> a (Acts as Modifier 'e' when held)
$*e::
{
    global UsedAsModifier
    UsedAsModifier := false

    ; --- Combinations (When acting as Target) ---
    if GetKeyState("i", "P") {
        SendInput "."
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "5"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "A"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "{Home}" ; Emacs line start
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#a"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap Logic (When acting as Modifier) ---
    if KeyWait("e", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "e")
            SendInput "{Blind}a"
        else if (!UsedAsModifier)
            SendInput "{Blind}a"
    }
    else {
        KeyWait "e"
        if (!UsedAsModifier)
            SendInput "{Blind}a"
    }
}

; Physical 'r' -> o (Acts as Modifier 'r' when held)
$*r::
{
    global UsedAsModifier
    UsedAsModifier := false

    ; --- Combinations ---
    if GetKeyState("i", "P") {
        SendInput "^j" ; Ctrl+j
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "6"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "O"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") { 
        ; Not explicitly defined
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#o"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("r", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "r")
            SendInput "{Blind}o"
        else if (!UsedAsModifier)
            SendInput "{Blind}o"
    }
    else {
        KeyWait "r"
        if (!UsedAsModifier)
            SendInput "{Blind}o"
    }
}

; Physical 't' -> f (Acts as Modifier 't' when held)
$*t::
{
    global UsedAsModifier
    UsedAsModifier := false

    ; --- Combinations ---
    if GetKeyState("i", "P") {
        SendInput "="
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "``" ; Backtick
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "F"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^f"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#f"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("t", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "t")
            SendInput "{Blind}f"
        else if (!UsedAsModifier)
            SendInput "{Blind}f"
    }
    else {
        KeyWait "t"
        if (!UsedAsModifier)
            SendInput "{Blind}f"
    }
}

; Physical 'f' -> y (Acts as Modifier 'f' [Ctrl-like] when held)
$*f::
{
    global UsedAsModifier
    UsedAsModifier := false

    ; --- Combinations ---
    if GetKeyState("i", "P") {
        SendInput ";"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "@"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "Y"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^y"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#y"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap (Hold = Ctrl Modifier) ---
    if KeyWait("f", GetWaitTime()) {
        if (UsedAsModifier)
            return
        if IgnoreModTapInterrupt and (A_PriorKey != "f")
            SendInput "{Blind}y"
        else
            SendInput "{Blind}y"
    }
    else {
        KeyWait "f"
        if (!UsedAsModifier)
            SendInput "{Blind}y"
    }
}

; Physical 'd' -> enter
$*d::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "_"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "9"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "+^{Enter}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^{Enter}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#{Enter}"
        UsedAsModifier := true
        return
    }
    
    SendInput "{Enter}"
}

; Physical 's' -> c
$*s::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "{&}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "8"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "C"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^c"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#c"
        UsedAsModifier := true
        return
    }
    
    SendInput "{Blind}c"
}

; Physical 'a' -> z
$*a::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "{%}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "7"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "Z"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^z"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#z"
        UsedAsModifier := true
        return
    }
    
    SendInput "{Blind}z"
}

; Physical '2' -> w
$*2::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "{*}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "1"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "W"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^w"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#w"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}w"
}

; Physical '3' -> u
$*3::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "{#}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "2"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "U"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^u"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#u" ; Accessibilty
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}u"
}

; Physical '4' -> p
$*4::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "$"
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "3"
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "P"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^p"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#p"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}p"
}

; Physical '5' -> v
$*5::
{
    global UsedAsModifier
    
    if GetKeyState("i", "P") {
        SendInput "#^ {Left}" ; Win+Ctrl+Left
        UsedAsModifier := true
        return
    }
    if GetKeyState("o", "P") {
        SendInput "{DoubleQuote}" ; "
        UsedAsModifier := true
        return
    }
    if GetKeyState("u", "P") {
        SendInput "V"
        UsedAsModifier := true
        return
    }
    if GetKeyState("j", "P") {
        SendInput "^v"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#v"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}v"
}

; ----------------------------------------------------------
; Right Hand Targets
; ----------------------------------------------------------

; Physical '-' -> r
$*-::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput ";"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "/"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "R"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^r"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#r"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}r"
}

; Physical '[' -> space
$*[
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput ":"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{Esc}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^{Space}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#{Space}"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}{Space}"
}

; Physical 'p' -> l
$*p::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "{}}" ; }}
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{Right}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "L"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^l"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#l"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}l"
}

; Physical 'o' -> k (Acts as Modifier 'o' when held)
$*o::
{
    global UsedAsModifier
    UsedAsModifier := false
    
    ; --- Combinations ---
    if GetKeyState("r", "P") {
        SendInput "{Text}{ " ; {
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{Up}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "K"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^k"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#k"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("o", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "o")
            SendInput "{Blind}k"
        else if (!UsedAsModifier)
            SendInput "{Blind}k"
    }
    else {
        KeyWait "o"
        if (!UsedAsModifier)
            SendInput "{Blind}k"
    }
}

; Physical 'i' -> j (Acts as Modifier 'i' when held)
$*i::
{
    global UsedAsModifier
    UsedAsModifier := false
    
    ; --- Combinations ---
    if GetKeyState("r", "P") {
        SendInput "^j"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{Down}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "J"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^j"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#j"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("i", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "i")
            SendInput "{Blind}j"
        else if (!UsedAsModifier)
            SendInput "{Blind}j"
    }
    else {
        KeyWait "i"
        if (!UsedAsModifier)
            SendInput "{Blind}j"
    }
}

; Physical 'u' -> h (Acts as Modifier 'u' when held)
$*u::
{
    global UsedAsModifier
    UsedAsModifier := false
    
    ; --- Combinations ---
    if GetKeyState("r", "P") {
        SendInput "{+}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{Left}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "H"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "{Backspace}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#h"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("u", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "u")
            SendInput "{Blind}h"
        else if (!UsedAsModifier)
            SendInput "{Blind}h"
    }
    else {
        KeyWait "u"
        if (!UsedAsModifier)
            SendInput "{Blind}h"
    }
}

; Physical '7' -> x
$*7::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "#^{Right}" ; Win+Ctrl+Right
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "'" ; Single Quote
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "X"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^x" ; Ctrl+x
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#x" ; Win+x
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}x"
}

; Physical '8' -> t
$*8::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "\"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        ; none
        return
    }
    if GetKeyState("t", "P") {
        SendInput "T"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^t"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#t"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}t"
}

; Physical '9' -> d
$*9::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "{("
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "^{ + }" ; Ctrl + +
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "D"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^d"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#d"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}d"
}

; Physical '0' -> s
$*0::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput ")}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "^{ - }" ; Ctrl + -
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "S"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^s"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#s"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}s"
}

; Physical 'j' -> n (Acts as Modifier 'j' when held)
$*j::
{
    global UsedAsModifier
    UsedAsModifier := false
    
    ; --- Combinations ---
    if GetKeyState("r", "P") {
        SendInput "-"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput "{~}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "N"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^n"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#n"
        UsedAsModifier := true
        return
    }

    ; --- Mod-Tap ---
    if KeyWait("j", GetWaitTime()) {
        if IgnoreModTapInterrupt and (A_PriorKey != "j")
            SendInput "{Blind}n"
        else if (!UsedAsModifier)
            SendInput "{Blind}n"
    }
    else {
        KeyWait "j"
        if (!UsedAsModifier)
            SendInput "{Blind}n"
    }
}

; Physical 'k' -> m
$*k::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "{|}"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput " {Space}tp"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "M"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^m"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#m"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}m"
}

; Physical 'l' -> g
$*l::
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "["
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        SendInput " {Space}tn"
        UsedAsModifier := true
        return
    }
    if GetKeyState("t", "P") {
        SendInput "G"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^g"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#g"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}g"
}

; Physical ';' -> b
$*;
{
    global UsedAsModifier
    
    if GetKeyState("r", "P") {
        SendInput "]"
        UsedAsModifier := true
        return
    }
    if GetKeyState("e", "P") {
        ; none
        return
    }
    if GetKeyState("t", "P") {
        SendInput "B"
        UsedAsModifier := true
        return
    }
    if GetKeyState("f", "P") {
        SendInput "^b"
        UsedAsModifier := true
        return
    }
    if GetKeyState("y", "P") {
        SendInput "#b"
        UsedAsModifier := true
        return
    }
    SendInput "{Blind}b"
}

; --- Physical 'y' acts as Modifier 'y' (Win) ---
$*y::
{
    global UsedAsModifier
    UsedAsModifier := false
    
    ; y tap behavior: y (default)
    ; Hold behavior: Acts as 'y' modifier (Windows Key layer)
    
    if KeyWait("y", GetWaitTime()) {
        if (UsedAsModifier)
            return
        
        ; Tap -> y
        if IgnoreModTapInterrupt and (A_PriorKey != "y")
            SendInput "{Blind}y"
        else
            SendInput "{Blind}y"
    }
    else {
        KeyWait "y"
        if (!UsedAsModifier)
            SendInput "{Blind}y"
    }
}

#HotIf ; End UseDualRole

Step 6: Obsidian & Gemini CLI

6.1 Fix Obsidian Mouse Drift

When maximizing Electron apps (like Obsidian) on WSLg, the screen shifts downward, causing the mouse pointer and actual click position to misalign. This is an XWayland compatibility issue.

The article Using Obsidian in WSL Environment | Hiroaki - note was very helpful for the solution. Special thanks to the author.

Detailed Steps (VBScript Fix)

The issue is resolved by adding the --ozone-platform-hint=wayland launch option. Creating a shortcut as follows is convenient.

  1. Command Line Check: First, run the following in your terminal to verify it works.
    obsidian --ozone-platform-hint=wayland
  2. Create VBScript: To avoid typing the command every time, create Obsidian.vbs with the following content.
    ※ Change Ubuntu to your distribution name.
    CreateObject("Wscript.Shell").Run "wsl -d Ubuntu -e bash -l -c \"obsidian --ozone-platform-hint=wayland\"", 0, False
  3. Register to Start Menu: Place the file (or a shortcut) in the following folder to launch it from the Windows Start Menu.
    C:\Users\{{Username}}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs

6.2 WSL Utilities (wslview)

WSL (wslview) allows you to open Windows browsers from within the Linux terminal. It is installed via init_linux.sh.

6.3 Gemini CLI

Installed Gemini CLI for a terminal-based AI environment. I referred to the following article:

Installing Gemini CLI on WSL2

Conclusion

Through these steps, I have successfully built a development environment on the ultra-compact GPD Micro PC 2 that is almost identical to my beloved MacBook!

Setting up the environment was so interesting that I lost about half a month doing it...

I plan to recover the lost time during spare moments on the train!