Skip to content

Instantly share code, notes, and snippets.

@agent-kilo
Created December 19, 2024 03:10
Show Gist options
  • Select an option

  • Save agent-kilo/553befdada13a0d896c0d601834697ce to your computer and use it in GitHub Desktop.

Select an option

Save agent-kilo/553befdada13a0d896c0d601834697ce to your computer and use it in GitHub Desktop.
A quick-and-dirty hack to manage WSLg windows with Jwno
#
# A quick-and-dirty hack to manage WSLg windows with Jwno
# (https://github.com/agent-kilo/jwno)
#
# To try it out, save this script to the same directory where
# your Jwno config resides, and do this in Jwno's REPL:
#
# (import wslg-transform)
# (def wt (wslg-transform/wslg-transform jwno/context))
# (:enable wt)
#
# To disable it:
#
# (:disable wt)
#
# Caveats:
#
# 1. Only works for X11 windows.
# 2. It does not escape window titles properly, certain characters
# in window titles may break it.
# 3. It gets really confused when there're more than one window with
# the same title.
# 4. A console window will flash-open when resizing/moving a WSLg
# window.
# 5. It's reeeeaaaally laggy.
#
# The author puts this file into the public domain. Happy hacking! 🤘
#
(use jw32/_winuser)
(use jw32/_util)
(use jwno/util)
(import jwno/log)
(def wslg-title-peg
(peg/compile
~{:main (sequence (opt (sequence :warn-text :s*)) :title-text (sequence :s* :distro-text -1))
:warn-text (sequence "[WARN:" (thru "]"))
:title-text (capture (to (sequence :s* :distro-text -1)))
:distro-text (sequence "(" (thru ")"))}))
(defn normalize-title-pattern [title]
(def clean-title
(if-let [matched (peg/match wslg-title-peg title)]
(first matched)
(errorf "unknown window title format: %n" title)))
(def dot-ascii (first "."))
(buffer/from-bytes
;(map (fn [c]
(if (find |(= $ c) "+*()[]?!")
dot-ascii
c))
clean-title)))
(defn wslg-window-transform [win orig-rect &opt tags wm normalize-fn]
(default tags @{})
(default wm (:get-window-manager win))
(log/debug "transforming WSLg window: %n" (in win :hwnd))
(def scale (calc-pixel-scale orig-rect))
(def [scale-x scale-y] scale)
(def merged-tags (merge (in win :tags) tags))
(def margins
(if-let [v (in merged-tags :margins)]
v
(let [v (in merged-tags :margin 0)]
{:top v
:left v
:bottom v
:right v})))
(def scaled-margins
(if (and (= 1 scale-x) (= 1 scale-y))
margins
{:top (* scale-y (in margins :top))
:left (* scale-x (in margins :left))
:bottom (* scale-y (in margins :bottom))
:right (* scale-x (in margins :right))}))
(log/debug "scaled-margins = %n" scaled-margins)
(def rect (shrink-rect orig-rect scaled-margins))
(log/debug "final rect = %n" rect)
(def win-title
(with-uia [uia-win (:get-uia-element win nil wm)]
(def name (:get_CurrentName uia-win))
(if normalize-fn
(normalize-fn name)
name)))
(log/debug "win-title = %n" win-title)
(let [x (in rect :left)
y (in rect :top)
[w h] (rect-size rect)]
(os/execute ["pwsh.exe"
"-WindowStyle"
"hidden"
"-Command"
"wsl.exe"
"--exec"
"xdotool"
"search"
"--name"
(string/format "'%s'" win-title)
"windowmove" (string x) (string y)
"windowsize" (string w) (string h)]
:p)))
(defn wslg-transform-on-window-created [self win uia-win exe-path _desktop-info]
(when (or (not= "RAIL_WINDOW" (:get_CachedClassName uia-win))
(not (string/has-suffix? "\\msrdc.exe" exe-path)))
(break))
(def normalize-fn
(if-let [normalize-title-pattern-fn (in self :normalize-title-pattern-fn)]
normalize-title-pattern-fn
normalize-title-pattern))
(put win
:transform
(fn [win rect &opt tags wm]
(wslg-window-transform win rect tags wm normalize-fn)))
(put (in win :tags) :margins {:left 10 :top 10 :right 23 :bottom 43}))
(defn wslg-transform-on-filter-forced-window [self _hwnd uia-win exe-path _desktop-info]
(and (= "RAIL_WINDOW" (:get_CachedClassName uia-win))
(not= "" (:get_CachedName uia-win))
(string/has-suffix? "\\msrdc.exe" exe-path)))
(defn wslg-transform-enable [self]
(:disable self)
(def win-created-hook-fn
(:add-hook (in self :hook-manager) :window-created
(fn [& args]
(:on-window-created self ;args))))
(put self :win-created-hook-fn win-created-hook-fn)
(def forced-win-hook-fn
(:add-hook (in self :hook-manager) :filter-forced-window
(fn [& args]
(:on-filter-forced-window self ;args))))
(put self :forced-win-hook-fn forced-win-hook-fn))
(defn wslg-transform-disable [self]
(def win-created-hook-fn (in self :win-created-hook-fn))
(def forced-win-hook-fn (in self :forced-win-hook-fn))
(when forced-win-hook-fn
(put self :forced-win-hook-fn nil)
(:remove-hook (in self :hook-manager) :filter-forced-window forced-win-hook-fn))
(when win-created-hook-fn
(put self :win-created-hook-fn nil)
(:remove-hook (in self :hook-manager) :window-created win-created-hook-fn)))
(def wslg-transform-proto
@{:on-window-created wslg-transform-on-window-created
:on-filter-forced-window wslg-transform-on-filter-forced-window
:enable wslg-transform-enable
:disable wslg-transform-disable})
(defn wslg-transform [context]
(def {:hook-manager hook-man}
context)
(table/setproto
@{:hook-manager hook-man
:normalize-title-pattern-fn nil}
wslg-transform-proto))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment