summaryrefslogtreecommitdiff
path: root/src/Waylean.lean
blob: 0f0a2e1318a3a3c2568ede3fb50f4d136fd3f738 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
import Wayland
import Waylean.Registry
import Std.Data.HashMap

open Wayland Protocols Waylean

def Array.findAndModifyM {α : Type} {m : Type → Type} [Monad m] (a : Array α) (pred : α → Bool) (update : α → m α) : m (Array α × Option α) := do
  let idx := a.findIdx pred
  if h : idx < a.size then
    let new ← update a[idx]
    return (a.setIfInBounds idx new, new)
  else return (a, .none)

def Array.findAndModify {α : Type} (a : Array α) (pred : α → Bool) (update : α → α) : Array α × Option α :=
  (a.findAndModifyM pred (update : α → Id α) : Id (Array α × Option α))

def Std.HashMap.mapM {α β γ : Type} [BEq α] [Hashable α] {m : Type → Type} [Monad m] (map : Std.HashMap α β) (update : α → β → m γ) : m (Std.HashMap α γ) := do
  let a := map.toArray
  let a' ← a.mapM fun (x, y) ↦ do return (x, ← update x y)
  return Std.HashMap.ofArray a'

def Std.HashMap.lens {α β : Type} [BEq α] [Hashable α] (key : α) : Lens (Std.HashMap α β) (Option β) where
  get x := x.get? key
  set v x := x.alter key fun _ ↦ v

def Std.HashMap.modifyM {α β : Type} [BEq α] [Hashable α] {m : Type → Type} [Monad m] (map : Std.HashMap α β) (key : α) (update : β → m β) : m (Std.HashMap α β) := do
  if h : map.contains key then
    let new ← update (map.get key h)
    return map.insert key new
  else return map

inductive Action where
  | none
  | spawnFoot
  | close
  | focusNext
  | move
  | resize
  | exit

inductive SeatOp where
  | none
  | move (window : RiverWindowV1) (startX startY : Int32)
  | resize (window : RiverWindowV1) (startX startY startWidth startHeight : Int32) (edges : RiverWindowV1.Edges)

structure XkbBinding where
  proxy : RiverXkbBindingV1
  action : Action

structure PointerBinding where
  proxy : RiverPointerBindingV1
  action : Action

structure Window where
  proxy : RiverWindowV1
  node : RiverNodeV1
  new : Bool := true
  closed : Bool := false
  x : Int32 := 0
  y : Int32 := 0
  width : Int32 := 0
  height : Int32 := 0
  pointerMoveRequested : Option RiverSeatV1 := .none
  pointerResizeRequested : Option RiverSeatV1 := .none
  pointerResizeRequestedEdges : RiverWindowV1.Edges := {}

structure Output where
  proxy : RiverOutputV1
  removed : Bool := false

structure Seat where
  proxy : RiverSeatV1
  new : Bool := true
  removed : Bool := false
  focused : Option RiverWindowV1 := .none
  hovered : Option RiverWindowV1 := .none
  interacted : Option RiverWindowV1 := .none
  xkbBindings : Std.HashMap RiverXkbBindingV1 XkbBinding := {}
  pointerBindings : Std.HashMap RiverPointerBindingV1 PointerBinding := {}
  pendingAction : Action := .none
  op : SeatOp := .none
  opDeltaX : Int32 := 0
  opDeltaY : Int32 := 0
  opRelease : Bool := false

structure State where
  registry : Registry.State := .new
  windows : Array Window := #[]
  outputs : Std.HashMap RiverOutputV1 Output := {}
  seats : Std.HashMap RiverSeatV1 Seat := {}

def zoomRegistry {α : Type} : DisplayM Registry.State α → DisplayM State α := DisplayM.zoom (mk_lens registry)

def handleSeat (ev : RiverSeatV1.Event) : DisplayM (Option Seat) Unit := do
  for s in ← get do
    match ev with
      | .removed               => set (Option.some { s with removed := true })
      | .pointerEnter win      => set (Option.some { s with hovered := win })
      | .pointerLeave          => set (Option.some { s with hovered := .none })
      | .windowInteraction win => set (Option.some { s with interacted := win })
      | .opDelta x y           => set (Option.some { s with opDeltaX := x, opDeltaY := y })
      | .opRelease             => set (Option.some { s with opRelease := true })
      | _                      => pure ()

def handleXkbBinding (proxy : RiverXkbBindingV1) (ev : RiverXkbBindingV1.Event) : DisplayM (Option Seat) Unit := do
  for s in ← get do
    for binding in s.xkbBindings.get? proxy do
      match ev with
        | .pressed => set (Option.some { s with pendingAction := binding.action })
        | _        => pure ()

def handlePointerBinding (proxy : RiverPointerBindingV1) (ev : RiverPointerBindingV1.Event) : DisplayM (Option Seat) Unit := do
  for s in ← get do
    for binding in s.pointerBindings.get? proxy do
      match ev with
        | .pressed => set (Option.some { s with pendingAction := binding.action })
        | _        => pure ()

def handleOutput (ev : RiverOutputV1.Event) : DisplayM (Option Output) Unit := do
  for o in ← get do
    match ev with
      | .removed => set (Option.some { o with removed := true })
      | _        => pure ()

def handleWindow (proxy : RiverWindowV1) (ev : RiverWindowV1.Event) : DisplayM State Unit := do
  let windows := (← get).windows.findAndModify (fun win ↦ win.proxy == proxy) fun win ↦
    match ev with
    | .closed                     => { win with closed := true }
    | .dimensions w h             => { win with width := w, height := h }
    | .pointerMoveRequested s     => { win with pointerMoveRequested := s }
    | .pointerResizeRequested s e => { win with pointerResizeRequested := s, pointerResizeRequestedEdges := e }
    | _                           => win
  set { ← get with windows := windows.fst }

structure Context where
  wm : RiverWindowManagerV1
  xkbBindings : RiverXkbBindingsV1

def Window.setPosition {σ : Type} (w : Window) (x y : Int32) : DisplayM σ Window := do
  w.node.setPosition x y
  return { w with x, y}

def Seat.createXkbBinding (ctx : Context) (mods : RiverSeatV1.Modifiers) (keysym : UInt32) (action : Action) : DisplayM (Option Seat) Unit := do
  for s in ← get do
    let proxy ← ctx.xkbBindings.getXkbBinding s.proxy keysym mods
    proxy.enable
    proxy.listen (handleXkbBinding proxy)
    let xkbBindings := s.xkbBindings.insert proxy { proxy, action }
    set (Option.some { s with xkbBindings })

def Seat.createPointerBinding (mods : RiverSeatV1.Modifiers) (button : UInt32) (action : Action) : DisplayM (Option Seat) Unit := do
  for s in ← get do
    let proxy ← s.proxy.getPointerBinding button mods
    proxy.enable
    proxy.listen (handlePointerBinding proxy)
    let pointerBindings := s.pointerBindings.insert proxy { proxy, action }
    set (Option.some { s with pointerBindings })

def Seat.pointerMove {σ : Type} (seat : Seat) (win : Window) : DisplayM σ Seat := do
  seat.proxy.opStartPointer
  return { seat with
    interacted := win.proxy
    op := .move win.proxy win.x win.y
    opDeltaX := 0
    opDeltaY := 0
  }

def Seat.pointerResize {σ : Type} (seat : Seat) (win : Window) (edges : RiverWindowV1.Edges) : DisplayM σ Seat := do
  seat.proxy.opStartPointer
  win.proxy.informResizeStart
  return { seat with
    interacted := win.proxy
    op := .resize win.proxy win.x win.y win.width win.height edges
    opDeltaX := 0
    opDeltaY := 0
  }

def Seat.focusTop (s : Seat) : DisplayM State Seat := do
  let windows := (← get).windows
  if h : windows.size == 0 then
    s.proxy.clearFocus
    return { s with focused := .none }
  else
    let win := windows[windows.size - 1]'(by
      apply Nat.sub_one_lt
      rw [beq_iff_eq] at h
      assumption)
    s.proxy.focusWindow win.proxy
    win.node.placeTop
    return { s with focused := win.proxy }

def Seat.doAction (s : Seat) (ctx : Context) : DisplayM State Seat := do
  let action := s.pendingAction
  let mut s : Seat := { s with pendingAction := .none }
  match action with
    | .none =>
        pure ()
    | .spawnFoot =>
        _ ← DisplayM.liftIO <| IO.Process.spawn {
          cmd := "foot"
          env := #[("WAYLAND_DEBUG", .none)]
        }
    | .close =>
        for window in s.focused do
          window.close
    | .focusNext =>
        let mut windows := (← get).windows
        if h : 0 < windows.size then
          let win := windows[0]
          windows := windows.eraseIdx 0
          windows := windows.push win
        set { ← get with windows }
        s ← focusTop s
    | .move =>
        for proxy in s.hovered do
          for win in (← get).windows.find? (fun win ↦ win.proxy == proxy) do
            s ← s.pointerMove win
    | .resize =>
        for proxy in s.hovered do
          for win in (← get).windows.find? (fun win ↦ win.proxy == proxy) do
            s ← s.pointerResize win { bottom := true, right := true }
    | .exit =>
        ctx.wm.exitSession
  return s

def Seat.opEnd (s : Seat) : DisplayM State Seat := do
  match s.op with
    | .resize proxy _ _ _ _ _ => proxy.informResizeEnd
    | _ => pure ()
  s.proxy.opEnd
  return { s with op := .none }

def Seat.opManage (s : Seat) : DisplayM State Seat := do
  match s.op with
    | .resize proxy startX startY startWidth startHeight edges =>
        let mut width := startWidth
        let mut height := startHeight
        if edges.left   then width  := width  - s.opDeltaX
        if edges.right  then width  := width  + s.opDeltaX
        if edges.top    then height := height - s.opDeltaY
        if edges.bottom then height := height + s.opDeltaY
        proxy.proposeDimensions width height
        pure { s with op := .resize proxy startX startY width height edges }
    | _ => pure s

def removeOutputs : DisplayM State Unit := do
  let (removedOutputs, aliveOutputs) := (← get).outputs.partition fun _ out ↦ out.removed
  for out in removedOutputs.values do
    out.proxy.destroy
  set { ← get with outputs := aliveOutputs }

def removeWindows : DisplayM State Unit := do
  let (closedWindows, aliveWindows) := (← get).windows.partition Window.closed
  let mut seats := (← get).seats
  for win in closedWindows do
    seats ← seats.mapM fun _ seat ↦ do
      let op  ← match seat.op with
        | .none => pure SeatOp.none
        | .move proxy startX startY =>
            if proxy == win.proxy then
              seat.proxy.opEnd
              pure SeatOp.none
            else pure (SeatOp.move proxy startX startY)
        | .resize proxy startX startY startWidth startHeight edges =>
            if proxy == win.proxy then
              seat.proxy.opEnd
              pure SeatOp.none
            else pure (SeatOp.resize proxy startX startY startWidth startHeight edges)
      pure { seat with
        op
        focused := if seat.focused == win.proxy then .none else seat.focused
        hovered := if seat.hovered == win.proxy then .none else seat.hovered
        interacted := if seat.interacted == win.proxy then .none else seat.interacted
      }
    win.proxy.destroy
  set { ← get with windows := aliveWindows, seats }

def removeSeats : DisplayM State Unit := do
  let (removedSeats, aliveSeats) := (← get).seats.partition fun _ seat ↦ seat.removed
  for seat in removedSeats.values do
    for binding in seat.xkbBindings.values do
      binding.proxy.destroy
    for binding in seat.pointerBindings.values do
      binding.proxy.destroy
    seat.proxy.destroy
  set { ← get with seats := aliveSeats }

def initNewWindows : DisplayM State Unit := do
  let windows ← (← get).windows.mapM fun win ↦ do
    let mut win := win
    if win.new then
      win ← win.setPosition win.x win.y
      win.proxy.proposeDimensions win.width win.height
      win := { win with new := false }
    pure win
  set { ← get with windows }

def initNewSeats (ctx : Context) : DisplayM State Unit := do
  -- See xkbcommon/xkbcommon-keysyms.h
  let space : UInt32 := 0x20
  let n : UInt32 := 0x6e
  let q : UInt32 := 0x71
  let esc : UInt32 := 0xff1b
  -- See linux/input-event-codes.h
  let btnLeft : UInt32 := 0x110
  let btnRight : UInt32 := 0x111
  let mods : RiverSeatV1.Modifiers := { mod1 := true }

  for s in (← get).seats.values.filter Seat.new do
    DisplayM.zoom (mk_lens seats) do
      DisplayM.zoom (Std.HashMap.lens s.proxy) do
        set (Option.some { s with new := false })
        Seat.createXkbBinding ctx mods space .spawnFoot
        Seat.createXkbBinding ctx mods q .close
        Seat.createXkbBinding ctx mods n .focusNext
        Seat.createXkbBinding ctx mods esc .exit
        Seat.createPointerBinding mods btnLeft .move
        Seat.createPointerBinding mods btnRight .resize

def manageWindows : DisplayM State Unit := do
  let windows ← (← get).windows.mapM fun win ↦ do
    let mut seats := (← get).seats
    for proxy in win.pointerMoveRequested do
      seats ← seats.modifyM proxy (fun s ↦ s.pointerMove win)
    for proxy in win.pointerResizeRequested do
      seats ← seats.modifyM proxy (fun s ↦ s.pointerResize win win.pointerResizeRequestedEdges)
    set { ← get with seats }
    pure { win with
      pointerMoveRequested := .none
      pointerResizeRequested := .none
    }
  set { ← get with windows }

def manageSeats (ctx : Context) : DisplayM State Unit := do
  let seats ← (← get).seats.mapM fun _ seat ↦ do
    for proxy in seat.interacted do
      let mut windows := (← get).windows
      let winIdx := windows.findIdx (fun win ↦ win.proxy == proxy)
      if h : winIdx < windows.size then
        let win := windows[winIdx]
        windows := windows.eraseIdx winIdx
        windows := windows.push win
      set { ← get with windows }
    let mut seat := seat
    seat ← seat.focusTop
    seat ← seat.doAction ctx
    if seat.opRelease then
      seat ← seat.opEnd
    else
      seat ← seat.opManage
    pure { seat with
      interacted := .none
      opRelease := false
    }
  set { ← get with seats }

def handle (ctx : Context) : RiverWindowManagerV1.Event → DisplayM State Unit
  | .unavailable => do
      DisplayM.liftIO <| IO.println "Another window manager is already running"
      DisplayM.requestQuit
  | .finished => do
      DisplayM.requestQuit
  | .manageStart => do
      removeOutputs
      removeWindows
      removeSeats
      initNewWindows
      initNewSeats ctx
      manageWindows
      manageSeats ctx
      ctx.wm.manageFinish
  | .renderStart => do
      let seats ←  (← get).seats.mapM fun _ seat ↦ do
        match seat.op with
          | .none => pure seat
          | .move proxy startX startY =>
              let windows ← (← get).windows.findAndModifyM
                (fun win ↦ win.proxy == proxy)
                (fun win ↦ win.setPosition (startX + seat.opDeltaX) (startY + seat.opDeltaY))
              set { ← get with windows := windows.fst }
              pure seat
          | .resize proxy startX startY startWidth startHeight edges =>
              let windows ← (← get).windows.findAndModifyM
                (fun win ↦ win.proxy == proxy)
                (fun win ↦
                  let x := startX + if edges.left then (startWidth - win.width) else 0
                  let y := startY + if edges.top then (startHeight - win.height) else 0
                  win.setPosition x y)
              set { ← get with windows := windows.fst }
              match windows.snd with
                | .none => pure seat
                | .some win => pure { seat with op := .resize proxy win.x win.y startWidth startHeight edges }
      set { ← get with seats }
      ctx.wm.renderFinish
  | .sessionLocked => pure ()
  | .sessionUnlocked => pure ()
  | .window proxy => do
      let windows := (← get).windows.push {
        proxy
        node := ← proxy.getNode
      }
      set { ← get with windows }
      proxy.listen (handleWindow proxy)
  | .output proxy => do
      let outputs := (← get).outputs.insert proxy { proxy }
      set { ← get with outputs }
      DisplayM.zoom (mk_lens outputs) do
        DisplayM.zoom (Std.HashMap.lens proxy) do
          proxy.listen handleOutput
  | .seat proxy => do
      let seats := (← get).seats.insert proxy { proxy }
      set { ← get with seats }
      DisplayM.zoom (mk_lens seats) do
        DisplayM.zoom (Std.HashMap.lens proxy) do
          proxy.listen handleSeat

def launch : DisplayM State Unit := do
  _ ← DisplayM.registerSignal .SIGINT do
    DisplayM.liftIO <| IO.println "Received SIGINT"
    DisplayM.requestQuit
  zoomRegistry Registry.init
  DisplayM.sync do
    let wm : RiverWindowManagerV1 ← zoomRegistry Registry.bindSingleton
    let xkbBindings : RiverXkbBindingsV1 ← zoomRegistry Registry.bindSingleton
    wm.listen (handle { wm, xkbBindings })

def main : IO Unit :=
  DisplayM.run launch { }