<?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: Byeonghoon Yoo</title>
    <description>The latest articles on DEV Community by Byeonghoon Yoo (@bhyoo).</description>
    <link>https://dev.to/bhyoo</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1457323%2F101da12a-c4bb-4814-accf-65b89e3ddf8e.jpeg</url>
      <title>DEV Community: Byeonghoon Yoo</title>
      <link>https://dev.to/bhyoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhyoo"/>
    <language>en</language>
    <item>
      <title>How to Provision S3 Buckets in Kubernetes with COSI and VersityGW</title>
      <dc:creator>Byeonghoon Yoo</dc:creator>
      <pubDate>Fri, 20 Mar 2026 15:05:02 +0000</pubDate>
      <link>https://dev.to/bhyoo/how-to-provision-s3-buckets-in-kubernetes-with-cosi-and-versitygw-577i</link>
      <guid>https://dev.to/bhyoo/how-to-provision-s3-buckets-in-kubernetes-with-cosi-and-versitygw-577i</guid>
      <description>&lt;p&gt;Kubernetes has had CSI for block and file storage for years. But if your app needs an S3 bucket, you're on your own — script some API calls, create an IAM user, write a bucket policy, inject the credentials. COSI changes that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is COSI?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kubernetes-sigs/container-object-storage-interface-spec" rel="noopener noreferrer"&gt;COSI&lt;/a&gt; (Container Object Storage Interface) is a SIG-Storage project that adds two CRDs to Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BucketClaim&lt;/strong&gt; — like a PersistentVolumeClaim, but for S3 buckets. Apply one and the driver creates the bucket on your storage backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BucketAccess&lt;/strong&gt; — provisions a dedicated IAM user with a scoped bucket policy. The credentials are written to a Kubernetes Secret your app can mount.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Delete the CRs and the bucket, user, and policy get cleaned up. The spec is at &lt;code&gt;v1alpha1&lt;/code&gt; — functional, but expect changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is VersityGW?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/versity/versitygw" rel="noopener noreferrer"&gt;VersityGW&lt;/a&gt; is an open-source (Apache 2.0) S3-compatible gateway written in Go. Its POSIX backend takes a directory and exposes it as an S3 endpoint — no erasure coding or custom data format. If you have a filesystem (local disk, NFS, ZFS), VersityGW can serve it as S3.&lt;/p&gt;

&lt;p&gt;This makes it a good fit for homelabs and on-prem setups where you already have storage and just need an S3 API in front of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BucketClaim / BucketAccess
        │
        ▼
  COSI Controller
        │
        ▼
    COSI Sidecar
        │ gRPC (Unix Socket)
        ▼
  versitygw-cosi-driver
        │
   ┌────┴────┐
   ▼         ▼
S3 API   Admin API
   └────┬────┘
        ▼
    VersityGW
        │
        ▼
   Filesystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The driver runs as a Deployment with two containers: the driver itself and the standard COSI sidecar. The sidecar watches Kubernetes CRs and dispatches gRPC calls to the driver over a shared Unix socket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Kubernetes cluster (1.25+, when COSI was introduced)&lt;/li&gt;
&lt;li&gt;Helm 3.x&lt;/li&gt;
&lt;li&gt;A running VersityGW instance with IAM and Admin API enabled&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; configured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install the COSI Controller
&lt;/h2&gt;

&lt;p&gt;The COSI controller manages the CRD lifecycle. Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/kubernetes-sigs/container-object-storage-interface//?ref=v0.2.2'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;objectstorage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Install VersityGW (if not already running)
&lt;/h2&gt;

&lt;p&gt;If you're already running VersityGW, skip this. Otherwise, install it with its Helm chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;versitygw oci://ghcr.io/versity/versitygw/charts/versitygw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; auth.accessKey&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; auth.secretKey&lt;span class="o"&gt;=&lt;/span&gt;admin123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; admin.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; gateway.iam.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chart creates a credentials Secret automatically (&lt;code&gt;versitygw-versitygw-credentials&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install the COSI Driver
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;versitygw-cosi-driver &lt;span class="se"&gt;\&lt;/span&gt;
  oci://ghcr.io/isac322/charts/versitygw-cosi-driver &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; driver.name&lt;span class="o"&gt;=&lt;/span&gt;versitygw.cosi.dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; versitygw.credentials.secretName&lt;span class="o"&gt;=&lt;/span&gt;versitygw-versitygw-credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deploys the driver and creates a default &lt;code&gt;BucketClass&lt;/code&gt; and &lt;code&gt;BucketAccessClass&lt;/code&gt; named &lt;code&gt;versitygw&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create a Bucket
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bucket-claim.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;objectstorage.k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BucketClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-bucket&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bucketClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;versitygw&lt;/span&gt;
  &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; bucket-claim.yaml
kubectl get bucketclaim my-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Get Credentials
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bucket-access.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;objectstorage.k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BucketAccess&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-bucket-access&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bucketClaimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-bucket&lt;/span&gt;
  &lt;span class="na"&gt;bucketAccessClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;versitygw&lt;/span&gt;
  &lt;span class="na"&gt;credentialsSecretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-bucket-credentials&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; bucket-access.yaml
kubectl get secret my-bucket-credentials &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'map_values(@base64d)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Secret contains &lt;code&gt;accessKeyID&lt;/code&gt;, &lt;code&gt;accessSecretKey&lt;/code&gt;, &lt;code&gt;endpoint&lt;/code&gt;, and &lt;code&gt;region&lt;/code&gt;. Mount it in your app and you're connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; bucket-access.yaml
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; bucket-claim.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The driver removes the IAM user, bucket policy, and bucket automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;COSI is still early (v1alpha1), but if you're managing S3 buckets on Kubernetes, it's worth trying. The declarative model fits naturally into GitOps workflows — your bucket definitions live in the same repo as your app manifests, and tools like ArgoCD or Flux handle the rest.&lt;/p&gt;

&lt;p&gt;The driver is open source (MIT):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/isac322/versitygw-cosi-driver" rel="noopener noreferrer"&gt;isac322/versitygw-cosi-driver&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helm chart&lt;/strong&gt;: &lt;a href="https://artifacthub.io/packages/helm/versitygw-cosi-driver/versitygw-cosi-driver" rel="noopener noreferrer"&gt;Artifact Hub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run into issues or have feature requests, open a GitHub issue. PRs welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you tried COSI? I'd be curious to hear about other setups — drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an MCP Server for Linux Desktop GUI Automation on Wayland</title>
      <dc:creator>Byeonghoon Yoo</dc:creator>
      <pubDate>Mon, 23 Feb 2026 16:14:41 +0000</pubDate>
      <link>https://dev.to/bhyoo/building-an-mcp-server-for-linux-desktop-gui-automation-on-wayland-30b2</link>
      <guid>https://dev.to/bhyoo/building-an-mcp-server-for-linux-desktop-gui-automation-on-wayland-30b2</guid>
      <description>&lt;p&gt;When I started working on AI agent tooling, I hit a wall: there's no clean way to automate GUI interactions on Wayland. X11 had &lt;code&gt;xdotool&lt;/code&gt; and &lt;code&gt;DISPLAY=:99&lt;/code&gt; — Wayland killed all of that, by design. No global input injection, no screen grabbing without portal authorization dialogs.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;kwin-mcp&lt;/strong&gt;, an MCP server that gives AI agents full GUI automation capabilities on Linux desktops, running in a completely isolated KWin Wayland session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Wayland's security model blocks the automation patterns we took for granted on X11. There's no equivalent of pointing tools at a virtual display. XDG RemoteDesktop portals require interactive user authorization — useless for headless automation. And most Wayland compositors don't expose any input injection API at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Triple Isolation
&lt;/h2&gt;

&lt;p&gt;kwin-mcp creates three layers of isolation for each session:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Private D-Bus bus&lt;/strong&gt; (&lt;code&gt;dbus-run-session&lt;/code&gt;) — no interference with host services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtual Wayland compositor&lt;/strong&gt; (&lt;code&gt;kwin_wayland --virtual&lt;/code&gt;) — no windows on your display&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped input injection&lt;/strong&gt; via KWin's EIS D-Bus interface — input stays inside the session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI agent never touches your host desktop. You get a fully functional KDE Plasma desktop that exists only in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Exposes
&lt;/h2&gt;

&lt;p&gt;The server provides 29 MCP tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;click, drag, scroll, move, button down/up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyboard&lt;/td&gt;
&lt;td&gt;type (ASCII), type unicode, key press, key down/up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touch&lt;/td&gt;
&lt;td&gt;tap, swipe, pinch, multi-finger swipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen&lt;/td&gt;
&lt;td&gt;screenshot, accessibility tree, find UI elements, wait for element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session&lt;/td&gt;
&lt;td&gt;start, stop, launch app, list/focus windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;clipboard get/set, D-Bus calls, Wayland protocol info&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Screenshot capture runs at ~30-70ms per frame via KWin's ScreenShot2 D-Bus interface. Any action tool accepts a &lt;code&gt;screenshot_after_ms&lt;/code&gt; parameter for burst frame capture without extra round-trips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why KWin Specifically?
&lt;/h2&gt;

&lt;p&gt;KWin is (as far as I know) the only Wayland compositor that exposes an EIS (Emulated Input Server) D-Bus interface. This provides the only clean path for input injection without triggering XDG RemoteDesktop authorization dialogs. Since we own the isolated session, we bypass the portal entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;KDE Plasma 6+ only (no GNOME, no Sway)&lt;/li&gt;
&lt;li&gt;US QWERTY keyboard layout for direct typing (Unicode input via &lt;code&gt;wtype&lt;/code&gt; works)&lt;/li&gt;
&lt;li&gt;AT-SPI2 accessibility coverage varies by application&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;kwin-mcp

&lt;span class="c"&gt;# Or with uv&lt;/span&gt;
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;kwin-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to your Claude Code MCP config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kwin-mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kwin-mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is MIT licensed and available on &lt;a href="https://github.com/isac322/kwin-mcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://pypi.org/project/kwin-mcp/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I'd love to hear if anyone has experience with Wayland automation or has ideas for extending this to other compositors. The EIS protocol is theoretically compositor-agnostic, but KWin is the only one implementing it right now.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>opensource</category>
      <category>python</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
