Skip to content

Instantly share code, notes, and snippets.

@agent-kilo
Created January 17, 2025 06:09
Show Gist options
  • Select an option

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

Select an option

Save agent-kilo/ada0e68d6854f0746473b3fcad896d5c to your computer and use it in GitHub Desktop.
A quick-and-dirty implementation of "scrolling" window managment, like PaperWM, for Jwno
# ==== scroll.janet ====
#
# A quick-and-dirty implementation of "scrolling" window
# managment, like PaperWM, for Jwno: https://github.com/agent-kilo/jwno.
#
# To test it out, put this file alongside your jwno-config.janet,
# and add these in your config:
#
# (import scroll)
# (def scroll (scroll/scroll jwno/context))
# (:enable scroll)
#
# Then if there're two or more frames lined up horizontally on the
# screen, it will automatically "scroll", so that the active frame
# will be shown at the the center of the screen.
#
# This is mostly a PoC, to demonstrate how it can be done
# using Jwno's built-in API. Beware of these caveats:
#
# 1. It supports only a single virtual desktop on a single monitor;
# 2. The first split on the top-level frame must be horizontal
# (the sub-frames should line up horizontally after the first
# split);
# 3. The last two frames can only be merged by the :flatten-parent
# command, or Jwno will freak out;
# 4. Other strange behavior that I'm not aware of.
#
(import jwno/util)
(import jwno/log)
(use jw32/_uiautomation)
(defn rect-center-x [rect]
(/ (+ (in rect :left) (in rect :right)) 2))
(defn scroll-to [cur-frame]
(def top-frame (:get-top-frame cur-frame))
(def cur-rect (in cur-frame :rect))
(def center-x (rect-center-x cur-rect))
(def mon-rect (get-in top-frame [:monitor :work-area]))
(def mon-center-x (rect-center-x mon-rect))
(def offset-x (- mon-center-x center-x))
(def top-rect (in top-frame :rect))
(def new-rect {:left (+ offset-x (in top-rect :left))
:top (in top-rect :top)
:right (+ offset-x (in top-rect :right))
:bottom (in top-rect :bottom)})
(log/debug "========= new-rect = %n" new-rect)
(:transform top-frame new-rect)
top-frame)
(defn insert-win-left [top-frame win new-width]
(def top-rect (in top-frame :rect))
(def [top-width top-height] (util/rect-size top-rect))
(def ratio (/ new-width (+ new-width top-width)))
(:insert-sub-frame top-frame 0 ratio)
(def new-top-rect {:left (- (in top-rect :left) new-width)
:top (in top-rect :top)
:right (in top-rect :right)
:bottom (in top-rect :bottom)})
(:transform top-frame new-top-rect)
(put (in win :tags) :frame (first (in top-frame :children))))
(defn insert-win-right [top-frame win new-width]
(def top-rect (in top-frame :rect))
(def [top-width top-height] (util/rect-size top-rect))
(def ratio (/ new-width (+ new-width top-width)))
(:insert-sub-frame top-frame (length (in top-frame :children)) ratio)
(def new-top-rect {:left (in top-rect :left)
:top (in top-rect :top)
:right (+ new-width (in top-rect :right))
:bottom (in top-rect :bottom)})
(:transform top-frame new-top-rect)
(put (in win :tags) :frame (last (in top-frame :children))))
(defn scroll-on-window-created [self win uia-win]
# Don't create new frames for these windows
(cond
(or (= "#32770" (:get_CachedClassName uia-win))
(not= 0 (:GetCurrentPropertyValue uia-win UIA_IsDialogPropertyId)))
# Dialog windows
(break))
(def root (get-in self [:context :window-manager :root]))
(def cur-frame (:get-current-frame root))
(def top-frame
(when cur-frame
(:get-top-frame cur-frame)))
(cond
(nil? cur-frame)
:nop
(empty? (:get-all-windows cur-frame))
:nop
(= :window (get-in top-frame [:children 0 :type]))
:nop
(= :vertical (:get-direction top-frame))
:nop
(let [cur-center-x (rect-center-x (in cur-frame :rect))
top-center-x (rect-center-x (in top-frame :rect))]
(if (< cur-center-x top-center-x)
(insert-win-left top-frame win (in self :inc-width))
(insert-win-right top-frame win (in self :inc-width))))))
(defn scroll-on-window-removed [self dead-win]
(def parent (in dead-win :parent))
(unless (:attached? parent)
(break))
(def top-frame (:get-top-frame dead-win))
(unless top-frame
(break))
(when (or (not (empty? (in parent :children)))
(in parent :monitor))
(break))
(def orig-top-child-count (length (in top-frame :children)))
(def window-man (get-in self [:context :window-manager]))
(def new-top-child-count (length (in top-frame :children)))
# Only adjust the top frame when a child is actually closed
(if (find |(= $ parent) (in top-frame :children))
# Skip activation hooks to avoid excessive retiles
(:close parent)
(do
(util/with-activation-hooks window-man
(:close parent))
(break)))
(def parent-rect (in parent :rect))
(def parent-width (util/rect-width parent-rect))
(def top-rect (in top-frame :rect))
(def new-top-rect
{:left (in top-rect :left)
:top (in top-rect :top)
:right (- (in top-rect :right) parent-width)
:bottom (in top-rect :bottom)})
(:transform top-frame new-top-rect)
(ev/spawn
(:retile (get-in self [:context :window-manager]) top-frame)))
(defn scroll-enable [self]
(:disable self)
(def hook-man (get-in self [:context :hook-manager]))
(def window-man (get-in self [:context :window-manager]))
(def root (in window-man :root))
(def hook-fns @{})
(put hook-fns :window-created
(:add-hook hook-man :window-created
(fn [win uia & _]
(:on-window-created self win uia))))
(put hook-fns :window-removed
(:add-hook hook-man :window-removed
(fn [win]
(:on-window-removed self win))))
(put hook-fns :frame-activated
(:add-hook hook-man :frame-activated
(fn [frame]
(:retile window-man (scroll-to frame)))))
(put self :hook-fns hook-fns))
(defn scroll-disable [self]
(def hook-man (get-in self [:context :hook-manager]))
(when-let [hook-fns (in self :hook-fns)]
(eachp [h f] hook-fns
(:remove-hook hook-man h f))))
(def scroll-proto
@{:on-window-created scroll-on-window-created
:on-window-removed scroll-on-window-removed
:enable scroll-enable
:disable scroll-disable})
(defn scroll [context]
(table/setproto
@{:context context
# Default settings
:inc-width 960
}
scroll-proto))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment