Created
January 17, 2025 06:09
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ==== 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