Initial Commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.direnv/
|
||||
/.jpm/
|
||||
/build/
|
||||
/result/
|
||||
/_build/
|
||||
1
bundle/info.jdn
Normal file
1
bundle/info.jdn
Normal file
@@ -0,0 +1 @@
|
||||
@{:name "censtablo" :description "River Window Manager" :jpm-dependencies @["https://codeberg.org/ifreund/janet-wayland" "https://codeberg.org/ifreund/janet-xkbcommon"]}
|
||||
4
bundle/init.janet
Normal file
4
bundle/init.janet
Normal file
@@ -0,0 +1,4 @@
|
||||
(if (dyn :install-time-syspath)
|
||||
(use @install-time-syspath/spork/declare-cc)
|
||||
(use spork/declare-cc))
|
||||
(dofile "project.janet" :env (jpm-shim-env))
|
||||
44
flake.lock
generated
Normal file
44
flake.lock
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767950769,
|
||||
"narHash": "sha256-oT4Tj7O9361bmMbPwuAcH2zgj2fUZao7F32Bkah+AmE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3cf525869eaad0c4105795523d158d6985d40885",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"river": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1770134013,
|
||||
"narHash": "sha256-O0GySsnOhbZBi86RdG1U/9ov8llLS/15ZZZVlSQqAv4=",
|
||||
"ref": "main",
|
||||
"rev": "e967499fb12b6074ebb93d98d7fc3e5eefb438ab",
|
||||
"revCount": 1578,
|
||||
"type": "git",
|
||||
"url": "https://codeberg.org/river/river.git"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"type": "git",
|
||||
"url": "https://codeberg.org/river/river.git"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"river": "river"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
125
flake.nix
Normal file
125
flake.nix
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
inputs.river = {
|
||||
url = "git+https://codeberg.org/river/river.git?ref=main";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
river,
|
||||
}: let
|
||||
systems = ["x86_64-linux" "aarch64-linux"];
|
||||
eachSystem = f:
|
||||
nixpkgs.lib.genAttrs systems (system:
|
||||
f {
|
||||
inherit system;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
});
|
||||
in {
|
||||
devShells = eachSystem ({
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}: {
|
||||
default = pkgs.mkShell (let
|
||||
libxkbcommon = pkgs.libxkbcommon.overrideAttrs {
|
||||
version = "1.12.0";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "xkbcommon";
|
||||
repo = "libxkbcommon";
|
||||
tag = "xkbcommon-1.12.0";
|
||||
hash = "sha256-QO3snl7NiyS2ao2MF3eT/lkNmVBjijr3JdTyrPn/2MQ=";
|
||||
};
|
||||
patches = [];
|
||||
doCheck = false;
|
||||
};
|
||||
in {
|
||||
packages = let
|
||||
janet-pm = pkgs.stdenv.mkDerivation rec {
|
||||
pname = "janet-pm";
|
||||
version = "1.1.1";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "janet-lang";
|
||||
repo = "spork";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-0L2G7flzNzi8w1nkGcQrtKjGl/D2uC8xSRgsBuLgRxM=";
|
||||
};
|
||||
patches = [
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://github.com/tw4452852/spork/commit/304ff781565d9bac21bef450770a312182730727.patch";
|
||||
hash = "sha256-9hTY3s3GphAg07we7gOF8vAPAYaWOjVkaspt8wP5KOU=";
|
||||
})
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://patch-diff.githubusercontent.com/raw/janet-lang/spork/pull/269.patch";
|
||||
hash = "sha256-HMvqmiZR+a2yE54PqlLji2Vi6zoUcQ8eD5HFcqAPxEo=";
|
||||
})
|
||||
];
|
||||
nativeBuildInputs = [pkgs.janet];
|
||||
|
||||
buildPhase = ''
|
||||
export JANET_PATH=$out
|
||||
mkdir $JANET_PATH
|
||||
janet --install .
|
||||
'';
|
||||
};
|
||||
river-pkg = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||
name = "river";
|
||||
version = "0.4";
|
||||
src = river;
|
||||
deps = pkgs.callPackage ./river.build.zig.zon.nix {};
|
||||
zigBuildFlags = ["--system" "${finalAttrs.deps}" "-Dxwayland"];
|
||||
nativeBuildInputs = with pkgs; [pkg-config wayland-scanner xwayland zig_0_15.hook];
|
||||
buildInputs = with pkgs; [
|
||||
libGL
|
||||
libevdev
|
||||
libinput
|
||||
pixman
|
||||
udev
|
||||
wayland
|
||||
wayland-protocols
|
||||
wlroots_0_19
|
||||
libx11
|
||||
libxkbcommon
|
||||
];
|
||||
|
||||
dontConfigure = true;
|
||||
});
|
||||
repl = pkgs.writeScriptBin "censtablo-repl" ''
|
||||
#!/usr/bin/env janet
|
||||
(import spork/netrepl)
|
||||
|
||||
(netrepl/client :unix (string (os/getenv "XDG_RUNTIME_DIR") "/censtablo-" (os/getenv "WAYLAND_DISPLAY")))
|
||||
'';
|
||||
in [pkgs.janet janet-pm river-pkg repl];
|
||||
|
||||
nativeBuildInputs = [pkgs.pkg-config];
|
||||
buildInputs = [
|
||||
(pkgs.wayland.overrideAttrs (final: prev: {
|
||||
patches =
|
||||
prev.patches
|
||||
++ [
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/485.patch";
|
||||
hash = "sha256-gd5BRE/ioFMM86Q8HLSzdvuwDDaqws0b3HiInC0nNgQ=";
|
||||
})
|
||||
];
|
||||
}))
|
||||
pkgs.wayland-scanner
|
||||
pkgs.wayland-protocols
|
||||
libxkbcommon
|
||||
];
|
||||
|
||||
RIVER_PROTOCOLS = "${river}/protocol/";
|
||||
|
||||
shellHook = ''
|
||||
export JANET_PATH="$PWD/.jpm"
|
||||
mkdir -p $JANET_PATH
|
||||
# Put this at the end so that installing spork in a local environment _doesn't_ override the janet-pm we're providing with patches so that pkg-config doesn't get `with-path`
|
||||
export PATH="$PATH:$JANET_PATH/bin/"
|
||||
'';
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
684
main.janet
Normal file
684
main.janet
Normal file
@@ -0,0 +1,684 @@
|
||||
(import wayland)
|
||||
(import spork/netrepl)
|
||||
(import xkbcommon)
|
||||
|
||||
(def river-protocols ((os/environ) "RIVER_PROTOCOLS"))
|
||||
|
||||
(def interfaces (wayland/scan
|
||||
:system-protocols ["stable/viewporter/viewporter.xml"]
|
||||
:custom-protocols (map |(string river-protocols $)
|
||||
["river-window-management-v1.xml"
|
||||
"river-layer-shell-v1.xml"
|
||||
"river-xkb-bindings-v1.xml"
|
||||
"river-input-management-v1.xml"
|
||||
"river-libinput-config-v1.xml"
|
||||
"river-xkb-config-v1.xml"])))
|
||||
|
||||
(def required-interfaces
|
||||
{"river_window_manager_v1" 3
|
||||
"river_layer_shell_v1" 1
|
||||
"river_xkb_bindings_v1" 1
|
||||
"river_xkb_config_v1" 1
|
||||
"river_input_manager_v1" 1})
|
||||
|
||||
(def registry @{})
|
||||
(def wl-outputs @{})
|
||||
|
||||
(def wm @{:outputs @[] :seats @[] :windows @[] :render-order @[] :inputs @[] :pulldowns @{}})
|
||||
|
||||
(def tags-main [:a :r :s :t :n :e :i :o])
|
||||
(def tags-offload [:comma :period])
|
||||
(def tags-other [:g :m :q :w :f :p :b :j :l :u :y :z :x :c :d :v :k :h])
|
||||
|
||||
(def config
|
||||
@{:xcursor-theme ["Vanilla-DMZ" 24]
|
||||
:border-width 1
|
||||
:border-normal 0x646464
|
||||
:border-focused 0xffffff
|
||||
:appid-rules @{}})
|
||||
|
||||
(defn binding/create [seat keysym mods action]
|
||||
(def binding @{:obj (:get-xkb-binding (registry "river_xkb_bindings_v1") (seat :obj) (xkbcommon/keysym keysym) mods)})
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:pressed] (put seat :pending-action [binding action])))
|
||||
(:set-handler (binding :obj) handle-event)
|
||||
(:enable (binding :obj))
|
||||
(array/push (seat :xkb-bindings) binding))
|
||||
|
||||
(defn input/configure [input]
|
||||
# Map trackpads to laptop displays
|
||||
(unless (nil? (string/find "Touchpad" (input :name)))
|
||||
(when-let [internal (find |(string/has-prefix? "eDP" (get $ :name "")) wl-outputs)]
|
||||
(put input :mapped internal)
|
||||
(:map-to-output (input :obj) (internal :obj)))))
|
||||
|
||||
(defn input/create [input]
|
||||
(def input @{:obj input})
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:type type] (put input :type type)
|
||||
[:name name] (put input :name name))
|
||||
(when (and (input :type) (input :name))
|
||||
(input/configure input)))
|
||||
(:set-handler (input :obj) handle-event)
|
||||
(:set-user-data (input :obj) input)
|
||||
(array/push (wm :inputs) input))
|
||||
|
||||
(defn wl-output/create [name obj]
|
||||
(def output @{:obj obj})
|
||||
(put wl-outputs name output)
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:name name] (do
|
||||
(put output :name name)
|
||||
(each input (wm :inputs) (input/configure input)))))
|
||||
(:set-handler obj handle-event)
|
||||
(:set-user-data obj output))
|
||||
|
||||
(defn output/create [obj]
|
||||
(def output @{:obj obj
|
||||
:layer-shell (:get-output (registry "river_layer_shell_v1") obj)
|
||||
:new true
|
||||
:tags-other []})
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:removed] (put output :removed true)
|
||||
[:position x y] (put output :position [x y])
|
||||
[:dimensions w h] (put output :dimensions [w h])
|
||||
[:wl-output name] (put output :wl-output (wl-outputs name))))
|
||||
(defn handle-layer-shell-event [event]
|
||||
(match event
|
||||
[:non-exclusive-area x y w h] (put output :non-exclusive-area [x y w h])))
|
||||
(:set-user-data obj output)
|
||||
(:set-handler obj handle-event)
|
||||
(:set-handler (output :layer-shell) handle-layer-shell-event)
|
||||
output)
|
||||
|
||||
(defn seat/create [obj]
|
||||
(def seat @{:obj obj
|
||||
:layer-shell (:get-seat (registry "river_layer_shell_v1") obj)
|
||||
:layer-focus :none
|
||||
:xkb-bindings @[]
|
||||
:pointer-bindings @[]
|
||||
:new true})
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:removed] (put seat :removed true)
|
||||
[:pointer-enter window] (put seat :pointer-target (:get-user-data window))
|
||||
[:pointer-leave] (put seat :pointer-target nil)
|
||||
[:window-interaction window] (put seat :window-interaction (:get-user-data window))
|
||||
[:shell-surface-interaction shell-surface] (do)))
|
||||
(defn handle-layer-shell-event [event]
|
||||
(match event
|
||||
[:focus-exclusive] (put seat :layer-focus :exclusive)
|
||||
[:focus-non-exclusive] (put seat :layer-focus :non-exclusive)
|
||||
[:focus-none] (put seat :layer-focus :none)))
|
||||
(:set-handler obj handle-event)
|
||||
(:set-handler (seat :layer-shell) handle-layer-shell-event)
|
||||
(:set-user-data obj seat)
|
||||
(:set-xcursor-theme obj ;(config :xcursor-theme))
|
||||
seat)
|
||||
|
||||
(defn window/create [obj]
|
||||
(def window
|
||||
@{:obj obj
|
||||
:node (:get-node obj)
|
||||
:new true})
|
||||
(defn handle-event [event]
|
||||
(match event
|
||||
[:closed] (put window :closed true)
|
||||
[:dimensions-hint min-w min-h max-w max-h] (put window :dimensions-bounds {:min-w min-w :max-w max-w :min-h min-h :max-h max-h})
|
||||
[:dimensions w h] (do (put window :w w) (put window :h h))
|
||||
[:app-id app-id] (put window :app-id app-id)
|
||||
[:parent parent] (put window :parent (if parent (:get-user-data parent)))
|
||||
[:decoration-hint hint] (put window :decoration-hint hint)
|
||||
[:pointer-move-requested seat] (put window :pointer-move-requested {:seat (:get-user-data seat)})
|
||||
[:pointer-resize-requested seat edges] (put window :pointer-resize-requested {:seat (:get-user-data seat) :edges edges})
|
||||
[:fullscreen-requested output] (put window :fullscreen-requested [:enter (if output (:get-user-data output))])
|
||||
[:exit-fullscreen-requested] (put window :fullscreen-requested [:exit])))
|
||||
(:set-handler obj handle-event)
|
||||
(:set-user-data obj window)
|
||||
window)
|
||||
|
||||
(defn clear [keys]
|
||||
(fn [obj]
|
||||
(each k keys
|
||||
(put obj k nil))))
|
||||
|
||||
(defn output/set-main-tag [output tag]
|
||||
(map |(when (= ($ :tag-main) tag) (put $ :tag-main nil)) (wm :outputs))
|
||||
(put output :tag-main tag)
|
||||
(put output :tag-offload nil))
|
||||
|
||||
(defn output/toggle-other-tag [output tag]
|
||||
(if (has-value? (output :tags-other) tag)
|
||||
(do
|
||||
(put output :tags-other (filter |(not (= $ tag)) (output :tags-other)))
|
||||
false)
|
||||
(do
|
||||
(map (fn [output] (update output :tags-other (fn [tags] (filter |(not (= $ tag)) tags)))) (wm :outputs))
|
||||
(update output :tags-other |[;$ tag])
|
||||
true)))
|
||||
|
||||
(defn output/toggle-offload-tag [output tag]
|
||||
(put output :tag-offload (if (= (output :tag-offload) tag) nil tag)))
|
||||
|
||||
(defn output/ensure-main-tag [output]
|
||||
(when (not (output :tag-main))
|
||||
(def available (->> tags-main
|
||||
(reverse)
|
||||
(filter (fn [tag] (not (some |(= tag ($ :tag-main)) (wm :outputs)))))))
|
||||
(def tag (or
|
||||
(find (fn [tag] (not (some |(= ($ :tag) tag) (wm :windows)))) available)
|
||||
(first available)))
|
||||
(put output :tag-main tag)))
|
||||
|
||||
(defn output/manage [output]
|
||||
(put output :tags [(output :tag-main)
|
||||
;(output :tags-other)
|
||||
;(if (output :tag-offload)
|
||||
[[(output :tag-main) (output :tag-offload)]]
|
||||
[])]))
|
||||
|
||||
(defn window/enter-fullscreen [window &opt inform]
|
||||
(put window :fullscreen true)
|
||||
(when inform
|
||||
(:inform-fullscreen (window :obj)))
|
||||
(:fullscreen (window :obj) ((window :output) :obj)))
|
||||
|
||||
(defn window/exit-fullscreen [window &opt inform]
|
||||
(put window :fullscreen false)
|
||||
(when inform
|
||||
(:inform-not-fullscreen (window :obj)))
|
||||
(:exit-fullscreen (window :obj)))
|
||||
|
||||
(defn window/set-float [window float]
|
||||
(if float
|
||||
(:set-tiled (window :obj) {})
|
||||
(:set-tiled (window :obj) {:left true :bottom true :top true :right true}))
|
||||
(put window :float float))
|
||||
|
||||
(defn window/move-output [window output]
|
||||
(when output
|
||||
(put window :tag (output :tag-main))
|
||||
(put window :output output)))
|
||||
|
||||
(defn seat/focus-output [seat output]
|
||||
(unless (= output (seat :focused-output))
|
||||
(put seat :focused-output output)
|
||||
(when output (:set-default (output :layer-shell)))))
|
||||
|
||||
(defn seat/focus [seat window]
|
||||
(defn focus-window [window]
|
||||
(unless (= (seat :focused) window)
|
||||
(:focus-window (seat :obj) (window :obj))
|
||||
(put seat :focused window)
|
||||
(set (wm :render-order) (filter |(not (= $ window)) (wm :render-order)))
|
||||
(array/push (wm :render-order) window)
|
||||
(:place-top (window :node))))
|
||||
(defn clear-focus []
|
||||
(when (seat :focused)
|
||||
(:clear-focus (seat :obj))
|
||||
(put seat :focused nil)))
|
||||
(defn focus-non-layer []
|
||||
(when (and window (window :output))
|
||||
(seat/focus-output seat (window :output)))
|
||||
(when-let [output (seat :focused-output)]
|
||||
(defn visible? [w] (and w (= output (w :output))))
|
||||
(def visible (filter |(= output ($ :output)) (wm :render-order)))
|
||||
(cond
|
||||
(def fullscreen (last (filter |($ :fullscreen) visible))) (focus-window fullscreen)
|
||||
(visible? window) (focus-window window)
|
||||
(visible? (seat :focused)) (do)
|
||||
(def top-visible (last visible)) (focus-window top-visible)
|
||||
(clear-focus))))
|
||||
(case (seat :layer-focus)
|
||||
:exclusive (put seat :focused nil)
|
||||
:non-exclusive (if window
|
||||
(do
|
||||
(put seat :layer-focus :none)
|
||||
(focus-non-layer))
|
||||
(put seat :focused nil))
|
||||
:none (focus-non-layer)))
|
||||
|
||||
(defn window/manage [window]
|
||||
(when (window :new)
|
||||
(:use-ssd (window :obj))
|
||||
(if-let [parent (window :parent)]
|
||||
(do
|
||||
(window/set-float window true)
|
||||
(put window :tag (parent :tag))
|
||||
(:propose-dimensions (window :obj) 0 0))
|
||||
(do
|
||||
(def seat (first (wm :seats)))
|
||||
(window/set-float window false)
|
||||
(window/move-output window (seat :focused-output))
|
||||
(each seat (wm :seats) (seat/focus seat window))))
|
||||
(when-let [rule ((config :appid-rules) (window :app-id))]
|
||||
(rule window)))
|
||||
|
||||
(match (window :fullscreen-requested)
|
||||
[:enter] (window/enter-fullscreen window)
|
||||
[:enter output] (do
|
||||
(window/move-output window output)
|
||||
(window/enter-fullscreen window))
|
||||
[:exit] (window/exit-fullscreen window))
|
||||
|
||||
(put window :output (find |(has-value? ($ :tags) (window :tag)) (wm :outputs)))
|
||||
|
||||
(if (window :output)
|
||||
(do
|
||||
(:show (window :obj))
|
||||
(when (window :fullscreen)
|
||||
(:fullscreen (window :obj) ((window :output) :obj))))
|
||||
(:hide (window :obj))))
|
||||
|
||||
|
||||
(defn seat/manage [seat]
|
||||
(when (seat :new)
|
||||
(each binding (config :xkb-bindings)
|
||||
(binding/create seat ;binding)))
|
||||
|
||||
(when-let [window (seat :focused)]
|
||||
(when (window :closed)
|
||||
(put seat :focused nil)))
|
||||
|
||||
(if (or (not (seat :focused-output)) ((seat :focused-output) :removed))
|
||||
(seat/focus-output seat (first (wm :outputs))))
|
||||
|
||||
(seat/focus seat nil)
|
||||
|
||||
(if-let [window (seat :window-interaction)]
|
||||
(seat/focus seat window))
|
||||
|
||||
(when-let [[binding action] (seat :pending-action)]
|
||||
(action seat binding))
|
||||
|
||||
(seat/focus seat nil))
|
||||
|
||||
(defn window/set-position [window x y]
|
||||
(let [border-width (config :border-width)
|
||||
x (+ x border-width)
|
||||
y (+ y border-width)]
|
||||
(put window :x x)
|
||||
(put window :y y)
|
||||
(:set-position (window :node) x y)))
|
||||
|
||||
(defn window/propose-dimensions [window w h]
|
||||
(:propose-dimensions (window :obj)
|
||||
(max 1 (- w (* 2 (config :border-width))))
|
||||
(max 1 (- h (* 2 (config :border-width))))))
|
||||
|
||||
(defn layout/rows [n x y w h]
|
||||
(if (> n 0)
|
||||
(do
|
||||
(def height (/ h n))
|
||||
(defn rows [n]
|
||||
(if (= n 0) [] (let [Y (- (+ y h) (* n height))]
|
||||
(tuple/join [[x Y w height]]
|
||||
(rows (- n 1))))))
|
||||
(rows n))
|
||||
[]))
|
||||
|
||||
(defn layout/split [count n x y w h]
|
||||
(if (= n 1)
|
||||
[[x y w h]]
|
||||
(let [left (/ w 2)
|
||||
right (- w left)]
|
||||
(tuple/join (layout/rows (min n count) x y (+ x left) h)
|
||||
(layout/rows (- n count) (+ x left) y right h)))))
|
||||
|
||||
(defn layout/main [n w h] (layout/split 1 n 0 0 w h))
|
||||
|
||||
(defn output/windows [output] (filter |(= output ($ :output)) (wm :windows)))
|
||||
|
||||
(defn output/layout [output]
|
||||
(def [X Y W H] (or (output :non-exclusive-area) [;(output :position) ;(output :dimensions)]))
|
||||
(def windows (filter |(not ($ :float)) (output/windows output)))
|
||||
(map (fn [window [x y w h]]
|
||||
(window/set-position window (+ X x) (+ Y y))
|
||||
(window/propose-dimensions window w h))
|
||||
windows
|
||||
(layout/main (length windows) W H))
|
||||
|
||||
(each window (filter |($ :float) (output/windows output))
|
||||
(let [t (+ Y (/ H 4))
|
||||
l (+ X (/ W 4))
|
||||
h (/ H 2)
|
||||
w (/ W 2)]
|
||||
(window/set-position window l t)
|
||||
(window/propose-dimensions window w h)
|
||||
(:place-top (window :node)))))
|
||||
|
||||
(defn wm/manage []
|
||||
(set (wm :render-order) (filter |(not ($ :closed)) (wm :render-order)))
|
||||
(defn close [o] (not (when (or (o :removed) (o :closed)) (:destroy (o :obj)) true)))
|
||||
(set (wm :outputs) (filter close (wm :outputs)))
|
||||
(set (wm :windows) (filter close (wm :windows)))
|
||||
(set (wm :seats) (filter close (wm :seats)))
|
||||
|
||||
(defn output/set-main-tag [output tag]
|
||||
(map |(if (= ($ :tag-main)) tag) (wm :outputs)))
|
||||
(map seat/manage (wm :seats))
|
||||
(map output/ensure-main-tag (wm :outputs))
|
||||
(map output/manage (wm :outputs))
|
||||
(map window/manage (wm :windows))
|
||||
(map |(seat/focus $ nil) (wm :seats)) # Reconcile focus again after potential tag changes
|
||||
|
||||
(map output/layout (wm :outputs))
|
||||
|
||||
(map (clear [:new]) (wm :outputs))
|
||||
(map (clear [:new :pointer-move-requested :pointer-resize-requested :fullscreen-requested]) (wm :windows))
|
||||
(map (clear [:new :window-interaction :pending-action]) (wm :seats))
|
||||
|
||||
(:manage-finish (registry "river_window_manager_v1")))
|
||||
|
||||
(defn rgb-to-u32-rgba [rgb]
|
||||
[(* (band 0xff (brushift rgb 16)) (/ 0xffff_ffff 0xff))
|
||||
(* (band 0xff (brushift rgb 8)) (/ 0xffff_ffff 0xff))
|
||||
(* (band 0xff rgb) (/ 0xffff_ffff 0xff))
|
||||
0xffff_ffff])
|
||||
|
||||
(defn set-borders [window status]
|
||||
(def rgb (case status
|
||||
:normal (config :border-normal)
|
||||
:focused (config :border-focused)))
|
||||
(:set-borders (window :obj)
|
||||
{:left true :bottom :true :top :true :right true}
|
||||
(config :border-width)
|
||||
;(rgb-to-u32-rgba rgb)))
|
||||
|
||||
(defn window/render [window]
|
||||
(when (and (not (window :x)) (window :w))
|
||||
(def output ((window :parent) :output))
|
||||
(window/set-position
|
||||
window
|
||||
(+ (output :x) (div (- (output :w) (window :w)) 2))
|
||||
(+ (output :y) (div (- (output :h) (window :h)) 2))))
|
||||
(if (find |(= ($ :focused) window) (wm :seats))
|
||||
(set-borders window :focused)
|
||||
(set-borders window :normal)))
|
||||
|
||||
(defn wm/render []
|
||||
(map window/render (wm :windows))
|
||||
(:render-finish (registry "river_window_manager_v1")))
|
||||
|
||||
|
||||
(defn wm/handle-event [event]
|
||||
(match event
|
||||
[:unavailable] (error "another window manager is already running")
|
||||
[:finished] (os/exit 0)
|
||||
[:manage-start] (wm/manage)
|
||||
[:render-start] (wm/render)
|
||||
[:output obj] (array/push (wm :outputs) (output/create obj))
|
||||
[:seat obj] (array/push (wm :seats) (seat/create obj))
|
||||
[:window obj] (array/push (wm :windows) (window/create obj))))
|
||||
|
||||
(defn mark-dirty []
|
||||
(:manage-dirty (registry "river_window_manager_v1")))
|
||||
|
||||
|
||||
(defn action/target-in [obj list dir]
|
||||
(let [i (or (index-of obj list) -1)]
|
||||
(case dir
|
||||
:next (get list (+ i 1) (first list))
|
||||
:prev (get list (- i 1) (last list)))))
|
||||
|
||||
(defn action/target [seat dir]
|
||||
(when-let [window (seat :focused)
|
||||
output (window :output)
|
||||
windows (output/windows output)]
|
||||
(action/target-in window windows dir)))
|
||||
|
||||
(defn spawn [command]
|
||||
(ev/spawn (os/proc-wait (os/spawn ["/bin/sh" "-c" command] :p))))
|
||||
|
||||
(defn action/spawn [command]
|
||||
(fn [seat binding] (spawn command)))
|
||||
|
||||
(defn action/focus [dir] (fn [seat binding] (seat/focus seat (action/target seat dir))))
|
||||
(defn action/focus-output [dir]
|
||||
(fn [seat binding]
|
||||
(when-let [current-output (seat :focused-output)
|
||||
outputs (wm :outputs)
|
||||
new-output (action/target-in current-output outputs dir)]
|
||||
(seat/focus-output seat new-output)
|
||||
(seat/focus seat (first (output/windows new-output))))))
|
||||
|
||||
(defn action/goto-tag [tag]
|
||||
(def f (cond
|
||||
(has-value? tags-main tag) output/set-main-tag
|
||||
(has-value? tags-other tag) output/toggle-other-tag
|
||||
(has-value? tags-offload tag) output/toggle-offload-tag))
|
||||
(fn [seat binding]
|
||||
(when-let [output (seat :focused-output)]
|
||||
(f output tag))))
|
||||
|
||||
(defn action/set-tag [tag]
|
||||
(fn [seat binding]
|
||||
(when-let [window (seat :focused)
|
||||
output (seat :focused-output)
|
||||
main-tag (output :tag-main)]
|
||||
(put window :tag (cond
|
||||
(= (window :tag) [main-tag tag]) main-tag
|
||||
(has-value? tags-offload tag) [main-tag tag]
|
||||
tag)))))
|
||||
|
||||
(defn action/toggle-fullscreen [inform]
|
||||
(fn [seat binding]
|
||||
(def window (seat :focused))
|
||||
(if (window :fullscreen)
|
||||
(window/exit-fullscreen window inform)
|
||||
(window/enter-fullscreen window inform))))
|
||||
|
||||
(defn action/close []
|
||||
(fn [seat binding]
|
||||
(if-let [window (seat :focused)]
|
||||
(:close (window :obj)))))
|
||||
|
||||
(defn action/zoom []
|
||||
(fn [seat binding]
|
||||
(when-let [focused (seat :focused)
|
||||
output (focused :output)
|
||||
visible (output/windows output)
|
||||
target (if (= focused (first visible)) (get visible 1) focused)
|
||||
i (assert (index-of target (wm :windows)))]
|
||||
(array/remove (wm :windows) i)
|
||||
(array/insert (wm :windows) 0 target)
|
||||
(seat/focus seat (first (wm :windows))))))
|
||||
|
||||
(defn action/screenshot []
|
||||
(fn [seat binding]
|
||||
(spawn (string "GRIM_DEFAULT_DIR=\"$HOME/tmp\" grim -o " (get-in seat [:focused-output :wl-output :name])))))
|
||||
|
||||
(defn action/rotate-outputs []
|
||||
(def ks [:tag-main :tag-offload :tags-other])
|
||||
(fn [seat binding]
|
||||
(def outputs (wm :outputs))
|
||||
(def tags (map |(table (seq [k :in ks] k ($ k))) outputs))
|
||||
(map (fn [out tags]
|
||||
(eachp [k tag] tags (put out k tag))) outputs [;(slice tags 1) (first tags)])))
|
||||
|
||||
(defn rule/pulldown []
|
||||
(fn [window]
|
||||
(def appid (window :app-id))
|
||||
(put window :tag [:pulldown appid])
|
||||
(window/set-float window true)
|
||||
(when-let [w ((wm :pulldowns) appid)]
|
||||
(:close (w :obj)))
|
||||
(put (wm :pulldowns) appid window)))
|
||||
|
||||
(defn action/pulldown [appid command]
|
||||
(fn [seat binding]
|
||||
(when (output/toggle-other-tag (seat :focused-output) [:pulldown appid])
|
||||
(if-let [window (get-in wm [:pulldowns appid])]
|
||||
(if-not (window :closed)
|
||||
(do
|
||||
(put window :output (seat :focused-output))
|
||||
(seat/focus seat window))
|
||||
(spawn command))
|
||||
(spawn command)))))
|
||||
|
||||
|
||||
(defn locked-screen []
|
||||
(print "Locked!")
|
||||
(def notifs-status (= 0 (os/execute ["dunstctl" "is-paused" "-e"] :p)))
|
||||
(os/execute ["dunstctl" "set-paused" "true"] :p)
|
||||
(os/execute ["pidwait" "swaylock"] :p) # Blocking is fine here -- we're locked
|
||||
(os/execute ["dunstctl" "set-paused" (if notifs-status "true" "false")] :p))
|
||||
|
||||
(defn lock-screen []
|
||||
(spawn "swaylock")
|
||||
(locked-screen))
|
||||
|
||||
(defn action/lock-screen []
|
||||
(fn [seat binding]
|
||||
(lock-screen)))
|
||||
|
||||
(put config :xkb-bindings (let [G {:mod4 true}
|
||||
G-S {:mod4 true :shift true}
|
||||
M {:mod1 true}
|
||||
M-S {:mod1 true :shift true}]
|
||||
[[:Tab M (action/focus :next)]
|
||||
[:Tab M-S (action/focus :prev)]
|
||||
[:Tab G (action/focus-output :next)]
|
||||
[:Tab G-S (action/focus-output :prev)]
|
||||
[:Tab {:mod4 true :ctrl true :mod1 true} (action/spawn "toggle-monitor")]
|
||||
[:Return G (action/spawn "alacritty")]
|
||||
[:f G (action/toggle-fullscreen true)]
|
||||
[:f G-S (action/toggle-fullscreen false)]
|
||||
[:k G (action/close)]
|
||||
[:backslash G (action/zoom)]
|
||||
[:backslash G-S (action/rotate-outputs)]
|
||||
[:space G (action/spawn "rofi -show launch")]
|
||||
[:x {:mod4 true} (action/spawn "rofi -show run")]
|
||||
[:b G (action/spawn "rofi -show bt")]
|
||||
[:p G (action/spawn "rofi-rbw")]
|
||||
[:n G (action/spawn "rofi -show notifications")]
|
||||
[:m G (action/spawn "rofi -show cliphist")]
|
||||
[:s G (action/screenshot)]
|
||||
[:j G (action/spawn "io.github.alainm23.planify.quick-add")]
|
||||
[:o G (action/spawn "wl-present-ui")]
|
||||
[:t G (action/pulldown "floating-terminal" "alacritty --class floating-terminal -e tmux new-session -ADX -s floating")]
|
||||
[:r G (action/pulldown "floating-repl" "alacritty --class floating-repl -e censtablo-repl")]
|
||||
[:XF86AudioLowerVolume {} (action/spawn "pamixer -d 5")]
|
||||
[:XF86AudioRaiseVolume {} (action/spawn "pamixer -i 5")]
|
||||
[:XF86AudioMute {} (action/spawn "pamixer -t")]
|
||||
[:XF86AudioPlay {} (action/spawn "playerctl play-pause")]
|
||||
[:XF86AudioPrev {} (action/spawn "playerctl previous")]
|
||||
[:XF86AudioNext {} (action/spawn "playerctl next")]
|
||||
[:Pause {} (action/lock-screen)]
|
||||
;(map |[$ {:mod5 true} (action/goto-tag $)] (tuple ;tags-main ;tags-other ;tags-offload))
|
||||
;(map |[$ {:mod5 true :shift true} (action/set-tag $)] (tuple ;tags-main ;tags-other ;tags-offload))]))
|
||||
|
||||
|
||||
(defn rule/tag [tag] (fn [window] (put window :tag tag)))
|
||||
|
||||
(put config :appid-rules @{"thunderbird" (rule/tag :m)
|
||||
"vesktop" (rule/tag :d)
|
||||
"io.github.alainm23.planify" (rule/tag :j)
|
||||
"floating-terminal" (rule/pulldown)
|
||||
"floating-repl" (rule/pulldown)})
|
||||
|
||||
|
||||
(defn startup []
|
||||
(spawn "pkill waybar && waybar")
|
||||
(spawn "kanshi")
|
||||
|
||||
(spawn "vesktop")
|
||||
(spawn "thunderbird")
|
||||
(spawn "solaar --window hide")
|
||||
(spawn "io.github.alainm23.planify"))
|
||||
|
||||
# TODO: layout flexibility
|
||||
|
||||
# TODO: swap display tags
|
||||
|
||||
# TODO: reload with repl?
|
||||
|
||||
(defn configure-keymap []
|
||||
(def ctx (xkbcommon/context/new))
|
||||
(def rmlvo (:create-rmlvo ctx))
|
||||
(:append-layout rmlvo "us-local")
|
||||
(def keymap (:create-keymap rmlvo :text-v2))
|
||||
(def str (:get-string keymap :text-v2))
|
||||
(def fd (wayland/memfd/from-string str))
|
||||
|
||||
(def keymap (:create-keymap (registry "river_xkb_config_v1") fd :text-v2))
|
||||
(defn apply-km [kb]
|
||||
(:set-keymap kb keymap)
|
||||
(:set-layout-by-name kb "us-local"))
|
||||
(var ready false)
|
||||
(def keyboards @[])
|
||||
(:set-handler keymap (fn [event]
|
||||
(match event
|
||||
[:success] (do
|
||||
(set ready true)
|
||||
(each keyboard keyboards (apply-km keyboard)))
|
||||
[:failure err] (errorf "invalid keymap %s" err))))
|
||||
|
||||
(:set-handler (registry "river_xkb_config_v1") (fn [event]
|
||||
(match event
|
||||
[:xkb-keyboard keyboard] (if ready (apply-km keyboard) (array/push keyboards keyboard))))))
|
||||
|
||||
|
||||
(def repl-env (curenv))
|
||||
(defn repl-server-create []
|
||||
(def path (string/format "%s/censtablo-%s" (os/getenv "XDG_RUNTIME_DIR") (os/getenv "WAYLAND_DISPLAY")))
|
||||
(protect (os/rm path))
|
||||
(netrepl/server :unix path repl-env))
|
||||
|
||||
# Stolen from https://git.sr.ht/~leon_plickat/river-config/tree/master/item/river/launch-river.sh
|
||||
(def env {"WAYLAND_DEBUG" nil
|
||||
"MOZ_ENABLE_WAYLAND" "1"
|
||||
"CLUTTER_BACKEND" "wayland"
|
||||
"QT_QPA_PLATFORM" "wayland"
|
||||
"ECORE_EVAS_ENGINE" "wayland-egl"
|
||||
"ELM_ENGINE" "wayland_egl"
|
||||
"_JAVA_AWT_WM_NONREPARENTING" "1"
|
||||
"NO_AT_BRIDGE" "1"
|
||||
|
||||
"XDG_SESSION_TYPE" "wayland"
|
||||
"XDG_SESSION_DESKTOP" "river"
|
||||
"XDG_CURRENT_DESKTOP" "river"
|
||||
|
||||
"NIXOS_OZONE_WL" "1"})
|
||||
|
||||
(defn main [&]
|
||||
(def display (wayland/connect interfaces))
|
||||
(eachp [var val] env (os/setenv var val))
|
||||
|
||||
(put registry :obj (:get-registry display))
|
||||
(:set-handler (registry :obj) (fn [event]
|
||||
(match event
|
||||
[:global name interface version]
|
||||
(do
|
||||
(when-let [required-version (required-interfaces interface)]
|
||||
(when (< version required-version)
|
||||
(errorf "wayland compositor supported %s version too old (need %d, got %d)" interface required-version version))
|
||||
(put registry interface (:bind (registry :obj) name interface required-version)))
|
||||
(when (= interface "wl_output")
|
||||
(wl-output/create name (:bind (registry :obj) name interface 4))))
|
||||
[:global-remove name]
|
||||
(when-let [obj (wl-outputs name)]
|
||||
(put wl-outputs name nil)
|
||||
(:release (obj :obj))))))
|
||||
(:roundtrip display)
|
||||
(eachk i required-interfaces
|
||||
(unless (get registry i)
|
||||
(errorf "wayland compositor does not support %s" i)))
|
||||
|
||||
(:set-handler (registry "river_window_manager_v1") wm/handle-event)
|
||||
(:set-handler (registry "river_input_manager_v1") (fn [event]
|
||||
(match event
|
||||
[:input-device input] (input/create input))))
|
||||
(configure-keymap)
|
||||
|
||||
(:roundtrip display)
|
||||
|
||||
(startup)
|
||||
|
||||
(def repl-server (repl-server-create))
|
||||
(defer (:close repl-server)
|
||||
(forever (:dispatch display))))
|
||||
9
project.janet
Normal file
9
project.janet
Normal file
@@ -0,0 +1,9 @@
|
||||
(declare-project
|
||||
:name "censtablo"
|
||||
:description ""
|
||||
:dependencies @["spork" "https://codeberg.org/ifreund/janet-wayland"]
|
||||
:version "0.0.0")
|
||||
|
||||
(declare-executable
|
||||
:name "censtablo"
|
||||
:entry "./main.janet")
|
||||
138
river.build.zig.zon.nix
Normal file
138
river.build.zig.zon.nix
Normal file
@@ -0,0 +1,138 @@
|
||||
# generated by zon2nix (https://github.com/Cloudef/zig2nix)
|
||||
|
||||
{
|
||||
lib,
|
||||
linkFarm,
|
||||
fetchurl,
|
||||
fetchgit,
|
||||
runCommandLocal,
|
||||
zig,
|
||||
name ? "zig-packages",
|
||||
}:
|
||||
|
||||
let
|
||||
unpackZigArtifact =
|
||||
{ name, artifact }:
|
||||
runCommandLocal name { nativeBuildInputs = [ zig ]; } ''
|
||||
hash="$(zig fetch --global-cache-dir "$TMPDIR" ${artifact})"
|
||||
mv "$TMPDIR/p/$hash" "$out"
|
||||
chmod 755 "$out"
|
||||
'';
|
||||
|
||||
fetchZig =
|
||||
{
|
||||
name,
|
||||
url,
|
||||
hash,
|
||||
}:
|
||||
let
|
||||
artifact = fetchurl { inherit url hash; };
|
||||
in
|
||||
unpackZigArtifact { inherit name artifact; };
|
||||
|
||||
fetchGitZig =
|
||||
{
|
||||
name,
|
||||
url,
|
||||
hash,
|
||||
rev ? throw "rev is required, remove and regenerate the zon2json-lock file",
|
||||
}:
|
||||
let
|
||||
parts = lib.splitString "#" url;
|
||||
url_base = lib.elemAt parts 0;
|
||||
url_without_query = lib.elemAt (lib.splitString "?" url_base) 0;
|
||||
in
|
||||
fetchgit {
|
||||
inherit name rev hash;
|
||||
url = url_without_query;
|
||||
deepClone = false;
|
||||
};
|
||||
|
||||
fetchZigArtifact =
|
||||
{
|
||||
name,
|
||||
url,
|
||||
hash,
|
||||
...
|
||||
}@args:
|
||||
let
|
||||
parts = lib.splitString "://" url;
|
||||
proto = lib.elemAt parts 0;
|
||||
path = lib.elemAt parts 1;
|
||||
fetcher = {
|
||||
"git+http" = fetchGitZig (
|
||||
args
|
||||
// {
|
||||
url = "http://${path}";
|
||||
}
|
||||
);
|
||||
"git+https" = fetchGitZig (
|
||||
args
|
||||
// {
|
||||
url = "https://${path}";
|
||||
}
|
||||
);
|
||||
http = fetchZig {
|
||||
inherit name hash;
|
||||
url = "http://${path}";
|
||||
};
|
||||
https = fetchZig {
|
||||
inherit name hash;
|
||||
url = "https://${path}";
|
||||
};
|
||||
};
|
||||
in
|
||||
fetcher.${proto};
|
||||
in
|
||||
linkFarm name [
|
||||
{
|
||||
name = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX";
|
||||
path = fetchZigArtifact {
|
||||
name = "pixman";
|
||||
url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.3.0.tar.gz";
|
||||
hash = "sha256-zX/jQV1NWGhalP3t0wjpmUo38BKCiUDPtgNGHefyxq0=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "wayland-0.5.0-dev-lQa1kvTUAQCsD8AobfOXJA_-TVG-WTYXju68OZ8L85RM";
|
||||
path = fetchZigArtifact {
|
||||
name = "wayland";
|
||||
url = "git+https://codeberg.org/ifreund/zig-wayland?ref=main#f2480d25764a50ed2fe29f49e4209c074a557f46";
|
||||
hash = "sha256-PosVlJ0FD80O46l0SYTWzfHFYfIE4Bdjqof/gttQ+KM=";
|
||||
rev = "f2480d25764a50ed2fe29f49e4209c074a557f46";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "wlroots-0.19.3-jmOlcuL_AwBHhLCwpFsXbTizE3q9BugFmGX-XIxqcPMc";
|
||||
path = fetchZigArtifact {
|
||||
name = "wlroots";
|
||||
url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.19.3.tar.gz";
|
||||
hash = "sha256-v/z6Kva5DQfLGHzrPXCu/7DtEbPT+XN+oATnIrwDHWs=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27";
|
||||
path = fetchZigArtifact {
|
||||
name = "wayland";
|
||||
url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz";
|
||||
hash = "sha256-kH/dGMft4asfJJxhi6Xc47inLCblAJC0rnAQAzpBm6k=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr";
|
||||
path = fetchZigArtifact {
|
||||
name = "xkbcommon";
|
||||
url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz";
|
||||
hash = "sha256-HhhUI+ayPtlylhTmZ1GrdSLbRIffTg3MeisGN1qs2iM=";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "xkbcommon-0.4.0-dev-VDqIe3bUAQB9reSurmSJxQjiQ6bnpekmOxIp_077GhVZ";
|
||||
path = fetchZigArtifact {
|
||||
name = "xkbcommon";
|
||||
url = "git+https://codeberg.org/ifreund/zig-xkbcommon?ref=main#a10651157cb1545ee3ae3818b4c7437c64d16462";
|
||||
hash = "sha256-fHf99JXSC1duAGoydZerzEGfmYUyZNEHdlz/SHy/xh8=";
|
||||
rev = "a10651157cb1545ee3ae3818b4c7437c64d16462";
|
||||
};
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user