<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abi</title>
    <description>The latest articles on DEV Community by Abi (@milkmanabi).</description>
    <link>https://dev.to/milkmanabi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3828699%2F75ed381b-8951-4a39-9ff4-6559bbb51bd4.jpeg</url>
      <title>DEV Community: Abi</title>
      <link>https://dev.to/milkmanabi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/milkmanabi"/>
    <language>en</language>
    <item>
      <title>I wrote an X11 server in Swift so Linux apps on my Mac get their own Dock icons</title>
      <dc:creator>Abi</dc:creator>
      <pubDate>Tue, 27 Oct 2026 03:51:49 +0000</pubDate>
      <link>https://dev.to/milkmanabi/i-wrote-an-x11-server-in-swift-so-linux-apps-on-my-mac-get-their-own-dock-icons-9k0</link>
      <guid>https://dev.to/milkmanabi/i-wrote-an-x11-server-in-swift-so-linux-apps-on-my-mac-get-their-own-dock-icons-9k0</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/MilkmanAbi/MSL" rel="noopener noreferrer"&gt;MSL&lt;/a&gt; is my Mac Subsystem for Linux: type &lt;code&gt;msl &amp;lt;instance&amp;gt;&lt;/code&gt; in any terminal and you're in a Linux shell, like WSL. That part is the actual point of the project.&lt;/p&gt;

&lt;p&gt;This post is about the part that isn't the point. WSL has WSLg, so I decided MSL should have something similar. That turned into &lt;strong&gt;mslgd&lt;/strong&gt;, an X11 server written from scratch in Swift that puts Linux windows on the Mac as real &lt;code&gt;NSWindow&lt;/code&gt;s. It's arguably where most of my effort went, it's still labelled experimental, and it taught me more about X11 than I ever planned to know.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ae46jvl0yxd5cbii7mz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ae46jvl0yxd5cbii7mz.jpg" alt="Linux Chromium running as a Mac window, with its menus in the Mac menu bar and its own tile in the Dock" width="799" height="520"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The goal: Linux Chromium, with "Chromium" in the Mac menu bar and its own Dock tile. Not "XQuartz", and not "MSL".&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why not just use XQuartz?
&lt;/h2&gt;

&lt;p&gt;The first version did. Phase 1 was literally "X11 over vsock to XQuartz", and it worked: the windows appeared.&lt;/p&gt;

&lt;p&gt;But under XQuartz every Linux app is a window that belongs to XQuartz. You get one Dock tile for all of them, one icon, a menu bar that says XQuartz whatever app you're in, and Mission Control stacks them all together. I wanted each window to belong to its app. As far as I could tell, the only way to get there was to &lt;em&gt;be&lt;/em&gt; the X server.&lt;/p&gt;
&lt;h2&gt;
  
  
  The pipe
&lt;/h2&gt;

&lt;p&gt;Before any X11 is interpreted, the bytes have to get out of the VM. That part is deliberately dumb.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linux app (say, GIMP)
   │  DISPLAY=:0  →  /tmp/.X11-unix/X0
   ▼
x11tunnel          (C, in the guest)  says who the app is, then relays raw bytes
   │  vsock, port 5002
   ▼
mslhd              (Mac, the background service that owns the VM)
   │  reads the app's name, hands the live socket off with SCM_RIGHTS
   ▼
~/Library/Caches/MSL/AppShims/GIMP   (one host process per app)
      its own X server state, NSWindows, Dock tile, menu bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;x11tunnel.c&lt;/code&gt; never parses a single X request. It listens where a Linux app expects an X server, and for each client it dials a fresh vsock connection to the Mac and pumps bytes both ways. One X client means one vsock connection, never multiplexed, because X11 expects one full-duplex stream per client.&lt;/p&gt;

&lt;p&gt;Everything else happens on the Mac side, in Swift.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step one: speaking the protocol
&lt;/h2&gt;

&lt;p&gt;X11 is old and very precise. The connection setup reply is the most failure-sensitive part, because every byte has a fixed position in the spec, and one miscounted field means every client fails before sending a single real request. The server also has to reply in whatever byte order the &lt;em&gt;client&lt;/em&gt; chose in its first byte (&lt;code&gt;B&lt;/code&gt; or &lt;code&gt;l&lt;/code&gt;), so every multi-byte write goes through one small byte-order-aware writer.&lt;/p&gt;

&lt;p&gt;Getting things "nearly right" doesn't work with X11. Early on I had &lt;code&gt;PolyArc&lt;/code&gt; on opcode 67. 67 is actually &lt;code&gt;PolyRectangle&lt;/code&gt;, so arc requests fell through unhandled, and any real rectangle request would have been parsed as arc data, at 12 bytes per entry instead of 8. A pixel-comparison test caught it, which is a theme you'll see again.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Dock problem, and why every app gets its own process
&lt;/h2&gt;

&lt;p&gt;At first, every Linux window lived inside &lt;code&gt;mslhd&lt;/code&gt;. That gave me exactly one Dock tile, labelled "mslhd", whose icon changed to whichever Linux app was in front. Apps visibly replaced each other as focus moved.&lt;/p&gt;

&lt;p&gt;The reason is simple and unfixable from the inside: &lt;strong&gt;macOS gives one Dock tile per &lt;code&gt;NSApplication&lt;/code&gt;&lt;/strong&gt;, and there's no public API for a second one. So per-app tiles mean per-app processes.&lt;/p&gt;

&lt;p&gt;Then there's the name. An unbundled executable is named in the Dock, in ⌘-Tab and in &lt;code&gt;NSRunningApplication.localizedName()&lt;/code&gt; by its &lt;strong&gt;filename&lt;/strong&gt;. I checked that with a probe binary before building anything on top of it. So there's one host binary, &lt;code&gt;mslgui&lt;/code&gt;. MSL copies it to &lt;code&gt;AppShims/Krita&lt;/code&gt;, starts that copy, and macOS calls it "Krita" everywhere. There's no &lt;code&gt;.app&lt;/code&gt; bundle and no Info.plist.&lt;/p&gt;

&lt;p&gt;Grouping is by app, not by connection. Krita opens five X11 connections and several windows, and they all need to land in one tile, so hosts are keyed by name and reused.&lt;/p&gt;
&lt;h3&gt;
  
  
  Who is this app, anyway?
&lt;/h3&gt;

&lt;p&gt;To route a connection to the right process, I need to know which app it is &lt;em&gt;before&lt;/em&gt; serving it. My first attempt was to read ahead in the X11 stream until the client sets &lt;code&gt;WM_CLASS&lt;/code&gt;, then route.&lt;/p&gt;

&lt;p&gt;That deadlocks, and it's obvious in hindsight. X11 setup is a request and a &lt;em&gt;response&lt;/em&gt;: the client sends its setup, then waits. Nothing else arrives until the server answers, and answering means already being the server for that connection. I watched &lt;code&gt;galculator&lt;/code&gt; sit there connected while not one byte showed up on the host.&lt;/p&gt;

&lt;p&gt;So the guest says who it is instead, and it knows for free. The peer of a unix socket is a real local process, so &lt;code&gt;SO_PEERCRED&lt;/code&gt; gives the pid, and &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cmdline&lt;/code&gt; gives the name. &lt;code&gt;x11tunnel&lt;/code&gt; sends one small frame before relaying:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'M' 'S' 'L' 'A'  &amp;lt;uint8 length&amp;gt;  &amp;lt;name bytes&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An X11 stream always starts with &lt;code&gt;B&lt;/code&gt; or &lt;code&gt;l&lt;/code&gt;, never &lt;code&gt;M&lt;/code&gt;, so one byte is enough to tell an announcement from a client that didn't send one. Any bytes read while finding out get replayed into the real connection.&lt;/p&gt;
&lt;h3&gt;
  
  
  Handing over a live socket
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;mslhd&lt;/code&gt; has to keep owning the vsock device, because it's the only thing Virtualization.framework gives a handle to. A file descriptor is per-process, so "send this connection to the Krita process" means asking the kernel to install the descriptor in another process. That's &lt;code&gt;SCM_RIGHTS&lt;/code&gt; over a unix socket, sent along with the bytes already consumed.&lt;/p&gt;

&lt;p&gt;The fun part: the &lt;code&gt;CMSG_*&lt;/code&gt; macros are C macros, so Swift can't see them, and I had to rebuild them by hand. Darwin aligns control messages to 4 bytes, not pointer size. Get that wrong and the kernel silently ignores the control message: the handoff "works", and the descriptor never arrives.&lt;/p&gt;
&lt;h3&gt;
  
  
  What splitting cost
&lt;/h3&gt;

&lt;p&gt;Splitting one X server into many processes broke things that X11 quietly assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resource IDs collided.&lt;/strong&gt; Each X client gets a range of IDs based on its client index. With one server those were unique. With one server &lt;em&gt;per app&lt;/em&gt;, every host started counting from the same base, so two apps' windows had identical IDs. The snapshot test harness names files &lt;code&gt;&amp;lt;windowID&amp;gt;.png&lt;/code&gt;, so four running apps overwrote each other's snapshots and seven tests failed comparing one program against another's reference. Fixed by giving each host its own slot in the ID space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy and paste between apps died.&lt;/strong&gt; X11 selections are peer-to-peer. The server stores nothing except who owns the selection, and the owner is asked for the data on demand. Two apps in two processes can't see each other at all. The fix was to make &lt;code&gt;NSPasteboard&lt;/code&gt; the broker, which also gives copy and paste with real Mac apps for free. It only mirrors &lt;code&gt;CLIPBOARD&lt;/code&gt;, not &lt;code&gt;PRIMARY&lt;/code&gt; (select-to-copy), because mirroring &lt;code&gt;PRIMARY&lt;/code&gt; would overwrite your Mac clipboard every time you dragged across some Linux text. And because AppKit has no clipboard change notification, it polls &lt;code&gt;changeCount&lt;/code&gt;, like every clipboard manager on macOS does.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  There's no window manager
&lt;/h3&gt;

&lt;p&gt;On Linux, the window manager decides where a new window goes. Toolkits usually ask for &lt;code&gt;(0, 0)&lt;/code&gt;, meaning "no preference". mslgd has no window manager, so two &lt;code&gt;galculator&lt;/code&gt; windows landed at exactly the same spot, one hidden perfectly behind the other. They were both rendering correctly the whole time, which made it look like a rendering bug when it wasn't. The fix is what &lt;code&gt;twm&lt;/code&gt; and &lt;code&gt;mwm&lt;/code&gt; were already doing in the '90s: cascade new top-level windows.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug that made apps vanish
&lt;/h2&gt;

&lt;p&gt;This was MSL's longest-running bug. GIMP (and other GTK apps) would just disappear: no crash report, no signal, no core dump, no log line. The window was simply gone mid-frame.&lt;/p&gt;

&lt;p&gt;Every tool I had came up empty. mslgd's logs looked fine, gdb saw nothing, and &lt;code&gt;xcb_connection_has_error()&lt;/code&gt; returned 0 every time, because nothing was ever wrong with the socket.&lt;/p&gt;

&lt;p&gt;What finally cracked it was &lt;code&gt;xspy&lt;/code&gt;, an &lt;code&gt;LD_PRELOAD&lt;/code&gt; shim. It replaces GDK's I/O error handler, which turns out to call &lt;code&gt;_exit(1)&lt;/code&gt;, with one that dumps state and a backtrace, and it logs every syscall on the X socket. At the moment of death, the app had &lt;strong&gt;written 672 MB and read 27 MB&lt;/strong&gt;, with short writes all the way through. Across 31 recorded deaths, every single one was waiting on a reply, and &lt;code&gt;GetInputFocus&lt;/code&gt; was the most common unanswered request.&lt;/p&gt;

&lt;p&gt;What was happening: mslgd read a request and handled it on the same thread. Any slow handler (a big image, drawing, anything waiting on the main thread) stopped that thread from reading. A GTK app repainting hard filled its send buffer, started getting short writes, and the next time it needed a reply it couldn't flush its queued requests to ask for one. Xlib treats a flush that can't complete as a fatal I/O error, and GDK's handler calls &lt;code&gt;_exit(1)&lt;/code&gt;. Gone.&lt;/p&gt;

&lt;p&gt;The fix is boring, which is how I know it's right: &lt;strong&gt;a reader thread that does nothing but pull bytes off the socket&lt;/strong&gt;, feeding a queue that a separate dispatcher works through in order. The tunnel had a matching problem. It used blocking writes, so while it was busy pushing bytes into the Mac it wasn't reading the replies coming back, and the two sides could jam each other. It's non-blocking now, with a 256 KB buffer instead of 8 KB (a single full repaint of a 1140×740 window is about 3.4 MB).&lt;/p&gt;

&lt;p&gt;Side note: I tried letting a local vision model drive GIMP to reproduce the bug. llama3.2-vision wouldn't load at all, and qwen2.5vl answered "10 10" and "100 100" (round numbers unrelated to the screenshot) at 57 seconds per call. A boring deterministic script that churned layers and undo/redo found it much faster.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making input feel like a Mac
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scrolling.&lt;/strong&gt; Real Xorg with libinput reports both XInput 2.1 smooth-scroll values &lt;em&gt;and&lt;/em&gt; legacy wheel clicks, and toolkits each pick one: GTK3 uses both, Qt and Chromium read the smooth values, and old apps only see buttons. mslgd does the same, treating the mouse wheel and the trackpad as two devices. The old code truncated each wheel event with &lt;code&gt;Int(dy)&lt;/code&gt;, so slow wheel ticks under one line did nothing at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Option key.&lt;/strong&gt; On a Mac, Option is both the shortcut modifier and the key that types &lt;code&gt;é&lt;/code&gt; and &lt;code&gt;™&lt;/code&gt;. Linux splits those across Alt and AltGr, so you get to choose: left Alt and right AltGr (the default), both Alt, or both type special characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard layouts.&lt;/strong&gt; The keymap is built from your live Mac layout and switches when you switch it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac shortcuts (experimental).&lt;/strong&gt; ⌘C, ⌘V and friends get translated into what Linux apps expect. Some Mac commands have no single Linux key. ⌘⌫ ("delete to start of line") becomes Shift+Home, then BackSpace. Terminals are a special case, because Ctrl+C in a terminal means something very different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The close button asks first.&lt;/strong&gt; Clicking the red button sends &lt;code&gt;WM_DELETE_WINDOW&lt;/code&gt;, so unsaved work still gets its "Save changes?" dialog.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Pixels
&lt;/h2&gt;

&lt;p&gt;By default, one X pixel is one Mac point, which is sharp on Retina (each X pixel is exactly 2×2 physical pixels) and is what every app has been tested at. But Linux UIs are roomier than macOS: GNOME's body text is 11pt at 96 dpi, about 14.7 pixels, against the Mac's 13 points. So Linux apps looked right and read big. &lt;strong&gt;Native Scaling&lt;/strong&gt; draws them at 0.85 and reports a correspondingly bigger screen, so apps lay themselves out for the room they really have. &lt;strong&gt;Retina rendering&lt;/strong&gt; goes the other way: half a point per X pixel, so every X pixel is one physical pixel. It's four times the pixels crossing the X11 connection, so it costs speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffdqhb4h6ktrny3shai8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffdqhb4h6ktrny3shai8s.png" alt="The MSL Flags window, showing GPU acceleration for OpenGL, native scaling and Retina rendering" width="800" height="900"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;All of mslgd's riskier bits live behind flags, and each one says what it costs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Small things matter too. Dock icons come from the app's &lt;code&gt;_NET_WM_ICON&lt;/code&gt;, which stores straight alpha while Core Graphics wants premultiplied alpha. Get that backwards and nothing crashes, but every icon gets a faint bright halo around its edges. Also, picking the first icon in the list usually lands on the 16×16 one, which looks like garbage as a Dock tile.&lt;/p&gt;
&lt;h2&gt;
  
  
  OpenGL, and the real GPU
&lt;/h2&gt;

&lt;p&gt;mslgd originally advertised no GLX, so &lt;code&gt;glxinfo&lt;/code&gt;, &lt;code&gt;glxgears&lt;/code&gt;, Blender and most CAD refused to start. Krita uses EGL, so it was fine, which is why this went unnoticed for so long.&lt;/p&gt;

&lt;p&gt;The trick that made it manageable: &lt;strong&gt;mslgd renders no OpenGL itself.&lt;/strong&gt; It implements the &lt;em&gt;setup&lt;/em&gt; half of GLX (framebuffer configs, contexts, drawables). Mesa in the guest does the actual rendering, then presents the finished frame through ordinary &lt;code&gt;PutImage&lt;/code&gt;. With plain Mesa that's CPU rendering (llvmpipe, OpenGL 4.5).&lt;/p&gt;

&lt;p&gt;For the GPU, the guest's Mesa already ships a driver called &lt;code&gt;virpipe&lt;/code&gt; that sends Gallium commands over a socket instead of to a GPU device. On the Mac end, &lt;code&gt;virglrenderer&lt;/code&gt; (the same library QEMU and UTM use) turns them into Apple's OpenGL on Metal:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linux app → Mesa (GALLIUM_DRIVER=virpipe) → socket
   → [Mac] virgl_test_server → virglrenderer → OpenGL 4.1 → Metal → GPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The guest needs no changes at all. In the first proof of concept, &lt;code&gt;es2gears&lt;/code&gt; ran at 215 FPS on an M2, rendered by the real GPU. The catch is that Apple's OpenGL stops at 4.1. Blender wants 4.3, so it stays on the CPU path, and that's why the GPU path is an opt-in flag that you can also switch per app.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing an X server when you're one person
&lt;/h2&gt;

&lt;p&gt;Yes, there are a lot of tests. I'd never have got this semi-viable by clicking around alone. There are three layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference images.&lt;/strong&gt; Small C, Cairo, GTK and Qt programs draw known scenes. The harness snapshots each window and compares it, pixel by pixel, against a reference image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxs5rk84bp9y74whoz0p2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxs5rk84bp9y74whoz0p2.png" alt="A grid of mslgd's reference images: X11 window lifecycle, GC primitives, pixmaps, RENDER, events, subwindows, clip rectangles, RANDR and SHAPE, GetImage round trip, and Cairo shapes, text and gradients" width="800" height="491"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Not pretty, but every pixel is checked.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;xhell, the torture suite.&lt;/strong&gt; It's 166 tests in Python that speak X11 byte for byte, with no Xlib, so they can send what a real library never would: truncated requests, stale IDs, length lies, 5,000 sibling windows, a 100k-rectangle flood, and replays of real GTK, Qt and Tk sessions. The worst result isn't CRASH, it's DOWN: the test finished, but a &lt;em&gt;fresh&lt;/em&gt; connection afterwards couldn't complete a round trip, meaning the whole app host died with it.&lt;/p&gt;

&lt;p&gt;It earned its keep straight away. Request handlers read fields with a reader that returns 0 past the end of the body. So a 4-byte &lt;code&gt;DestroyWindow&lt;/code&gt; with no body became "destroy window 0", which walked the root window's bookkeeping and recursed until the stack overflowed. One malformed request from any client killed the whole host. Now every core request is checked for minimum length and valid IDs first, and gets the same X errors a real server would give.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hunting kits.&lt;/strong&gt; For bugs that only show up after hours of real use, like the vanishing apps, there are scripts that boot an instance, drive a real app, notice a death, grab gdb output plus mslgd's recent wire traffic, and restart.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where it is now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;About &lt;strong&gt;22,700 lines of Swift across 46 files&lt;/strong&gt;, plus the 436-line C tunnel. Yes, &lt;code&gt;X11Connection.swift&lt;/code&gt; on its own is 12,337 lines. I know.&lt;/li&gt;
&lt;li&gt;No Xlib and no X.Org code underneath. X.Org and XQuartz were reading material, not dependencies.&lt;/li&gt;
&lt;li&gt;It handles the core protocol, RENDER (glyphs, gradients, trapezoids, transforms), SHAPE, RANDR, BIG-REQUESTS, XInput 2 and XKB, GLX, the clipboard bridge, and Linux app menus in the Mac menu bar (experimental, via dbusmenu).&lt;/li&gt;
&lt;li&gt;GTK 3 and 4, Qt, GIMP, Krita, LibreOffice, Chromium, galculator and xeyes all run as Mac windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X11 only.&lt;/strong&gt; Wayland-only apps won't find a display, so set &lt;code&gt;GDK_BACKEND=x11&lt;/code&gt; or &lt;code&gt;QT_QPA_PLATFORM=xcb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Still experimental.&lt;/strong&gt; Some apps repaint oddly on the GPU path, some draw their menus twice when the menu bar flag is on, and very old toolkits run but look plain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;mslgd is the one part of MSL under &lt;strong&gt;GPLv3&lt;/strong&gt; (the rest is MIT). It took a lot of effort and a lot of testing hours, so it should stay free: use it, change it, ship it, and share your changes.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MilkmanAbi" rel="noopener noreferrer"&gt;
        MilkmanAbi
      &lt;/a&gt; / &lt;a href="https://github.com/MilkmanAbi/MSL" rel="noopener noreferrer"&gt;
        MSL
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      MSL; WSL feel for macOS. Integration as a priority, seamlessness as a philosophy
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/MilkmanAbi/MSL/Assets/App_Logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMilkmanAbi%2FMSL%2FHEAD%2FAssets%2FApp_Logo.png" width="148" alt="The MSL melon"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MSL&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;b&gt;Mac Subsystem for Linux&lt;/b&gt;&lt;br&gt;
  Real Linux on your Mac. A shell in about a second, your files on both sides,&lt;br&gt;
  and Linux apps that open as Mac windows with Dock tiles of their own
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL/../../releases/latest" rel="noopener noreferrer"&gt;&lt;img alt="Latest release" src="https://camo.githubusercontent.com/3d4a629d286a7fc66276bbc8521542c8799b83226df4c221caa3a2d4a8d0f805/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f4d696c6b6d616e4162692f4d534c3f6c6162656c3d72656c6561736526636f6c6f723d653837333561"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/013a3b1de3c0fadcadcdef45988178f753de03330a0dfb00a4d60cb005a89fdb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d61634f532d31342532422d353535353535"&gt;&lt;img alt="macOS 14 or later" src="https://camo.githubusercontent.com/013a3b1de3c0fadcadcdef45988178f753de03330a0dfb00a4d60cb005a89fdb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d61634f532d31342532422d353535353535"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/f72e4c2eca0e859e71cbeb2f1245beb2dec74ec3e05aec5e21b3625c8c054086/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4170706c6525323073696c69636f6e2d61726d36342d353535353535"&gt;&lt;img alt="Apple silicon" src="https://camo.githubusercontent.com/f72e4c2eca0e859e71cbeb2f1245beb2dec74ec3e05aec5e21b3625c8c054086/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4170706c6525323073696c69636f6e2d61726d36342d353535353535"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0e62df79a9a8c49d206a94e190f25553432ddb716edfb559f3d836bf9cdd5fb3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f64697374726f732d31322d346339613661"&gt;&lt;img alt="12 distributions" src="https://camo.githubusercontent.com/0e62df79a9a8c49d206a94e190f25553432ddb716edfb559f3d836bf9cdd5fb3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f64697374726f732d31322d346339613661"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL#licence" rel="noopener noreferrer"&gt;&lt;img alt="MIT, and GPLv3 for mslgd" src="https://camo.githubusercontent.com/cbcb31ba21ce504902c437bc8e2532e762511d483350ccb33633a82013ee9d82/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e63652d4d495425323025324225323047504c7633253230286d736c6764292d336236666236"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL#getting-started" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#msl-files" rel="noopener noreferrer"&gt;MSL Files&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#mslgd" rel="noopener noreferrer"&gt;mslgd&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://milkmanabi.github.io/MSL-Wiki/" rel="nofollow noopener noreferrer"&gt;The guide&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#the-msl-family" rel="noopener noreferrer"&gt;The MSL family&lt;/a&gt;
&lt;/p&gt;



&lt;p&gt;MSL runs real Linux distributions on your Mac — an actual kernel, an actual userland, on Apple's own Virtualization framework. Nothing's emulated, and there's no VM window to babysit. Type &lt;code&gt;msl&lt;/code&gt; and you're in. Your home folder's already there. Linux apps show up in the Dock, in Spotlight, in ⌘-Tab, like they always lived there.&lt;/p&gt;

&lt;p&gt;It's been over a year getting here, and most of that time was spent on problems I didn't know existed until I hit them. It's free, open source, and still held together by a fair amount of stubbornness. No like genuinely, around 4 months in planning…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MilkmanAbi/MSL" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;If you've ever written a display server, or tried to, I'd genuinely love to hear what bit you. And if you're the person who asks "why not just use XQuartz", now you know. (◞‸◟)&lt;/p&gt;

</description>
      <category>swift</category>
      <category>linux</category>
      <category>macos</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I'm moving to a Mac, so I made myself a WSL for it</title>
      <dc:creator>Abi</dc:creator>
      <pubDate>Tue, 27 Oct 2026 03:32:45 +0000</pubDate>
      <link>https://dev.to/milkmanabi/im-moving-to-a-mac-so-i-made-myself-a-wsl-for-it-5c50</link>
      <guid>https://dev.to/milkmanabi/im-moving-to-a-mac-so-i-made-myself-a-wsl-for-it-5c50</guid>
      <description>&lt;p&gt;MSL stands for Mac Subsystem for Linux. Yes, it's a play on the WSL name. That's it. I wish I were kidding.&lt;/p&gt;

&lt;p&gt;I made it for one single reason. My Surface Laptop 3 is starting to show its age, and I'm planning to move to a MacBook Air in a few months. The one thing I really didn't want to give up was WSL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ae46jvl0yxd5cbii7mz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ae46jvl0yxd5cbii7mz.jpg" alt="Linux Chromium running as a Mac window, with its menus in the Mac menu bar and its own tile in the Dock, MSL's window behind it" width="799" height="520"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ubuntu running on a Mac, with the Linux build of Chromium open as a normal Mac window.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  "Just use Docker"
&lt;/h2&gt;

&lt;p&gt;I know. Docker, OrbStack, VirtualBox: there are already plenty of ways to run Linux on a Mac. Docker is pretty much the standard, and it's a hugely powerful tool built by people with a clear goal.&lt;/p&gt;

&lt;p&gt;I just wanted to go my own way. I'm too used to WSL. What I wanted was the WSL feeling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Linux shell right in my normal terminal,&lt;/li&gt;
&lt;li&gt;fast to start,&lt;/li&gt;
&lt;li&gt;something I can call from the terminal or from a script, and it just happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole goal of MSL. Honestly, it's a glorified, slightly sucky virtual machine with a lot of UI and UX integration put on top.&lt;/p&gt;
&lt;h2&gt;
  
  
  The shell is the main thing
&lt;/h2&gt;

&lt;p&gt;Open any terminal on your Mac and type &lt;code&gt;msl &amp;lt;instance&amp;gt;&lt;/code&gt;. You get a Linux shell right there, with no clunky VM window. From fully powered off it takes about 6 seconds, and once the instance is running it's basically instant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcxpljd19tll0xhulr0pj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcxpljd19tll0xhulr0pj.png" alt="The Terminal tab showing uname reporting an aarch64 Linux kernel and Ubuntu 26.04.1 LTS" width="800" height="515"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A real kernel and a real arm64 userland. Nothing's emulated.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;msl                  &lt;span class="c"&gt;# lists every distro you can download&lt;/span&gt;
msl &lt;span class="nb"&gt;install &lt;/span&gt;debian   &lt;span class="c"&gt;# downloads it, then asks for a name, CPUs, memory and disk&lt;/span&gt;
msl debian           &lt;span class="c"&gt;# first start asks you to create your Linux account&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From then on, &lt;code&gt;msl debian&lt;/code&gt; drops you straight into your shell, signed in as your own user. Your Mac's home folder is already in there at &lt;code&gt;/mnt/mac&lt;/code&gt;. It works in scripts, from Terminal.app, from whatever terminal you like, and over SSH if you want it (MSL sets up &lt;code&gt;ssh msl-&amp;lt;name&amp;gt;&lt;/code&gt; for you, so scp and VS Code Remote-SSH just work).&lt;/p&gt;

&lt;p&gt;Under the hood, every instance is a lightweight VM on Apple's Virtualization framework, the same idea as WSL 2. It isn't really a "subsystem": nothing is woven into macOS and there's no kernel extension. I just liked the name.&lt;/p&gt;

&lt;p&gt;There are twelve distros ready to download, from Alpine to Nix. Every download is checked against its published checksum before it's used. You can also build your own image from any arm64 container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fey81b6u7zfi97kjjg6h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fey81b6u7zfi97kjjg6h1.png" alt="The New Instance sheet with twelve distributions: Alpine, Debian, Ubuntu, Kali, Arch, Fedora, Rocky, AlmaLinux, CentOS Stream, Oracle Linux, openSUSE and Nix" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can have up to eight named instances, with four running at once. Each one gets its own CPUs, memory and disk, and its disk only takes up the space Linux actually writes. Leave an instance alone and it suspends itself so it stops burning CPU. Close the lid and MSL freezes your instances before the Mac sleeps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your files, both ways
&lt;/h2&gt;

&lt;p&gt;WSL lets Windows and Linux see each other's files, and I wanted that too. Your Mac home folder shows up inside Linux. In the other direction, Linux shows up on the Mac in &lt;strong&gt;MSL Files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A running instance does technically appear in Finder, but as a network volume called &lt;code&gt;127.0.0.1&lt;/code&gt;, with nothing telling you which instance it is. So I wrote MSL Files instead. It looks like Finder. It isn't Finder. (◞‸◟)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkvc8xtyk6vvc1bkkuci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkvc8xtyk6vvc1bkkuci.png" alt="MSL Files browsing an Ubuntu root filesystem, showing real Unix permissions and symlinks next to the Mac sidebar and Finder tags" width="800" height="515"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A Linux filesystem with its real permissions and symlinks, right under your Mac folders.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your Mac folders sit in the sidebar right above every running instance, so getting a file from Downloads into Linux takes one drag. It also has git integration, because Finder workflow go brr &amp;gt;:3&lt;/p&gt;
&lt;h2&gt;
  
  
  mslgd, because WSL has WSLg
&lt;/h2&gt;

&lt;p&gt;This one exists only because I thought, "WSL has WSLg, so MSL should have something similar." It's arguably where most of my effort went, and it's a weird little side project.&lt;/p&gt;

&lt;p&gt;mslgd is an X server that draws Linux windows as real Mac windows (&lt;code&gt;NSWindow&lt;/code&gt;s). With XQuartz, every Linux app lives under one XQuartz icon. With mslgd, each Linux app gets its own window and its own icon in the Dock and in ⌘-Tab, like any other Mac app. Type &lt;code&gt;gimp&lt;/code&gt; and GIMP just opens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3rnk9mlgopgi09bm2pc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3rnk9mlgopgi09bm2pc.png" alt="The Applications tab, listing the Linux GUI apps found inside the instance" width="800" height="515"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MSL finds the GUI apps in an instance. You can add any of them to your Applications folder or pin it to the Dock.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GTK and Qt apps run: GIMP, Krita, LibreOffice, Chromium, and of course xeyes. OpenGL, EGL and libGL support is still a work in progress, though it kind of works. The whole GUI side is labelled experimental, and I mean it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The submodules nobody asked for
&lt;/h2&gt;

&lt;p&gt;Most of these exist because I personally needed them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Camera and mic passthrough.&lt;/strong&gt; My ROS2 project uses a camera, which is honestly why I even thought of this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic USB passthrough.&lt;/strong&gt; I don't have Apple entitlements, but if macOS has no driver for some absolutely cursed piece of hardware, you can hand it to Linux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSLPS&lt;/strong&gt; (Peripheral Subsystem), for HID-like devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PublicMSL&lt;/strong&gt;, to easily configure an instance so it's reachable from the internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSLAP&lt;/strong&gt; (Admin Portal), to limit MSL features on managed Macs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSL Shipments.&lt;/strong&gt; A low-effort way to turn an instance into one small file you can share. Think of it as a shoddy, primitive, but easy version of what Docker does lol.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a Sandbox that can cut an instance off from the network, your Mac files, graphics or input, plus a live traffic monitor showing what the host and guest are actually saying to each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feh2b909cex85h6798zzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feh2b909cex85h6798zzu.png" alt="The Sandbox tab with gates for Network, Mac home share, Graphics (mslgd) and Keyboard &amp;amp; mouse, plus a Traffic Monitor" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The catches
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Apple silicon only&lt;/strong&gt;, on macOS 14 Sonoma or later. Apple's VM framework on Intel is hellishly limited, and there's no emulation, so Intel Macs can't run MSL at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The installer isn't notarized&lt;/strong&gt;, because I don't have a paid Apple developer account (I'm broke). The first time you open the .pkg, macOS blocks it. Go to &lt;strong&gt;System Settings ▸ Privacy &amp;amp; Security&lt;/strong&gt; and click &lt;strong&gt;Open Anyway&lt;/strong&gt;. I know how that sounds. All the source is there if you'd rather build it yourself than trust the package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's one person's project.&lt;/strong&gt; Shells, instances, snapshots, files and the sandbox are solid. The GUI apps and anything behind the Experimental Features window are experiments, not promises.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try it, or just poke around
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MilkmanAbi" rel="noopener noreferrer"&gt;
        MilkmanAbi
      &lt;/a&gt; / &lt;a href="https://github.com/MilkmanAbi/MSL" rel="noopener noreferrer"&gt;
        MSL
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      MSL; WSL feel for macOS. Integration as a priority, seamlessness as a philosophy
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/MilkmanAbi/MSL/Assets/App_Logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMilkmanAbi%2FMSL%2FHEAD%2FAssets%2FApp_Logo.png" width="148" alt="The MSL melon"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MSL&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;b&gt;Mac Subsystem for Linux&lt;/b&gt;&lt;br&gt;
  Real Linux on your Mac. A shell in about a second, your files on both sides,&lt;br&gt;
  and Linux apps that open as Mac windows with Dock tiles of their own
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL/../../releases/latest" rel="noopener noreferrer"&gt;&lt;img alt="Latest release" src="https://camo.githubusercontent.com/3d4a629d286a7fc66276bbc8521542c8799b83226df4c221caa3a2d4a8d0f805/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f4d696c6b6d616e4162692f4d534c3f6c6162656c3d72656c6561736526636f6c6f723d653837333561"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/013a3b1de3c0fadcadcdef45988178f753de03330a0dfb00a4d60cb005a89fdb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d61634f532d31342532422d353535353535"&gt;&lt;img alt="macOS 14 or later" src="https://camo.githubusercontent.com/013a3b1de3c0fadcadcdef45988178f753de03330a0dfb00a4d60cb005a89fdb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d61634f532d31342532422d353535353535"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/f72e4c2eca0e859e71cbeb2f1245beb2dec74ec3e05aec5e21b3625c8c054086/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4170706c6525323073696c69636f6e2d61726d36342d353535353535"&gt;&lt;img alt="Apple silicon" src="https://camo.githubusercontent.com/f72e4c2eca0e859e71cbeb2f1245beb2dec74ec3e05aec5e21b3625c8c054086/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4170706c6525323073696c69636f6e2d61726d36342d353535353535"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0e62df79a9a8c49d206a94e190f25553432ddb716edfb559f3d836bf9cdd5fb3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f64697374726f732d31322d346339613661"&gt;&lt;img alt="12 distributions" src="https://camo.githubusercontent.com/0e62df79a9a8c49d206a94e190f25553432ddb716edfb559f3d836bf9cdd5fb3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f64697374726f732d31322d346339613661"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL#licence" rel="noopener noreferrer"&gt;&lt;img alt="MIT, and GPLv3 for mslgd" src="https://camo.githubusercontent.com/cbcb31ba21ce504902c437bc8e2532e762511d483350ccb33633a82013ee9d82/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e63652d4d495425323025324225323047504c7633253230286d736c6764292d336236666236"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/MilkmanAbi/MSL#getting-started" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#msl-files" rel="noopener noreferrer"&gt;MSL Files&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#mslgd" rel="noopener noreferrer"&gt;mslgd&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://milkmanabi.github.io/MSL-Wiki/" rel="nofollow noopener noreferrer"&gt;The guide&lt;/a&gt; &amp;nbsp;·&amp;nbsp;
  &lt;a href="https://github.com/MilkmanAbi/MSL#the-msl-family" rel="noopener noreferrer"&gt;The MSL family&lt;/a&gt;
&lt;/p&gt;



&lt;p&gt;MSL runs real Linux distributions on your Mac — an actual kernel, an actual userland, on Apple's own Virtualization framework. Nothing's emulated, and there's no VM window to babysit. Type &lt;code&gt;msl&lt;/code&gt; and you're in. Your home folder's already there. Linux apps show up in the Dock, in Spotlight, in ⌘-Tab, like they always lived there.&lt;/p&gt;

&lt;p&gt;It's been over a year getting here, and most of that time was spent on problems I didn't know existed until I hit them. It's free, open source, and still held together by a fair amount of stubbornness. No like genuinely, around 4 months in planning…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MilkmanAbi/MSL" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The docs are a 121-article guide that also ships inside the app. You can read them on &lt;a href="https://milkmanabi.github.io/MSL-Wiki/" rel="noopener noreferrer"&gt;the MSL Wiki&lt;/a&gt; before installing anything (no domain, it's just GitHub Pages). The little knight on there was drawn by neekocat_2025.&lt;/p&gt;

&lt;p&gt;mslgd is GPLv3 and everything else is MIT. Bug reports, ideas and PRs are all welcome. And if you like it, a 🌟 makes me feel fuzzy and nice inside. Kind of like a cow. A cow with super APT powers.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>macos</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
    <item>
      <title>meow</title>
      <dc:creator>Abi</dc:creator>
      <pubDate>Tue, 17 Mar 2026 06:26:00 +0000</pubDate>
      <link>https://dev.to/milkmanabi/second-bridge-open-source-community-resource-directory-early-alpha-mit-licensed-looking-for-5bcp</link>
      <guid>https://dev.to/milkmanabi/second-bridge-open-source-community-resource-directory-early-alpha-mit-licensed-looking-for-5bcp</guid>
      <description>&lt;p&gt;meow&lt;/p&gt;

</description>
      <category>community</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
