mirror of
https://github.com/ebeem/guile-swayer.git
synced 2024-12-24 21:58:56 +01:00
init commit
This commit is contained in:
commit
5a866ddf1c
20 changed files with 6345 additions and 0 deletions
127
README.org
Normal file
127
README.org
Normal file
|
@ -0,0 +1,127 @@
|
|||
#+title: Readme
|
||||
#+STARTUP: inlineimages
|
||||
#+OPTIONS: toc:3 ^:nil
|
||||
|
||||
** SWAYIPC
|
||||
|
||||
I am an =Emacs= user and previously used =StumpWM=, an =X11= window manager written in =Common Lisp=. I believe window managers should be scriptable because the level of workflow customization required by users often exceeds what can be achieved with simple configuration parameters (see my workflow below for a clearer understanding of why this is the case). Unfortunately, =Sway/i3= lacks a straightforward programmable interface for customization. This project provides complete control over =Sway/i3= using =Guile=!
|
||||
|
||||
** Why Sway?
|
||||
|
||||
I had to migrate to =Wayland= at some point. Being a big fan of =StumpWM=, I tried to replicate a similar environment in one of the =Wayland= window managers. I made some progress with =hyprland= using a set of =Guile= bindings I developed called =hypripc=, but I found that =Hyprland= isn't as stable as =Sway=.
|
||||
|
||||
** Quick Overview
|
||||
|
||||
*** Query Sway
|
||||
|
||||
You can retrieve information about =Sway=, such as list of available =workspaces= or =outputs=. The response will be in Guile records, which you can easily manipulate! (refer to =swayipc/records.scm=)
|
||||
|
||||
#+begin_src scheme
|
||||
;; get focused workspace from a list of workspaces
|
||||
(define (focused-workspace-name workspaces)
|
||||
(cond
|
||||
((null? workspaces) #f)
|
||||
((equal? #t (sway-workspace-focused (car workspaces)))
|
||||
(sway-workspace-name (car workspaces)))
|
||||
(else (focused-workspace-name (cdr workspaces)))))
|
||||
|
||||
(focused-workspace-name (sway-get-workspaces))
|
||||
#+end_src
|
||||
|
||||
*** Assign Keybindings
|
||||
|
||||
You can assign keybindings that execute Guile code! Obviously, running shell commands is straightforward since you're operating within Guile. Additionally, you have full access to Sway/i3 specific commands (refer to =swayipc/dispatcher.scm=).
|
||||
|
||||
#+begin_src scheme
|
||||
;; define leader keymap
|
||||
(sway-define-keys
|
||||
#:prefix "s-Space" #:wk "Leader"
|
||||
`("o" (exec "rofi -show drun"))
|
||||
`("C-g" (sway-mode "default") #:wk "abort")
|
||||
|
||||
;; rofi keymap
|
||||
`(sway-define-keys
|
||||
#:prefix "r" #:wk "Rofi"
|
||||
("p" (exec "~/.config/rofi/bin/password-manager"))
|
||||
("m" (exec "rofi-mount"))
|
||||
("u" (exec "rofi-unmount"))
|
||||
("w" (exec ".config/rofi/bin/wifi"))
|
||||
("b" (exec "~/.config/rofi/bin/bluetooth"))
|
||||
("f" (exec "~/.config/rofi/bin/finder"))
|
||||
("k" (exec "~/.config/rofi/bin/keyboard-layout"))
|
||||
("P" (exec "~/.config/rofi/bin/powermenu"))
|
||||
("s" (exec "~/.config/rofi/bin/sound-input"))
|
||||
("S" (exec "~/.config/rofi/bin/sound-output")))
|
||||
|
||||
;; window management
|
||||
`(sway-define-keys
|
||||
#:prefix "w" #:wk "Window"
|
||||
("v" (sway-split-container SWAY-SPLIT-VERTICAL))
|
||||
("h" (sway-split-container SWAY-SPLIT-HORIZONTAL))
|
||||
("f" (sway-fullscreen SWAY-FULLSCREEN-TOGGLE))
|
||||
("d" (sway-fullscreen SWAY-FULLSCREEN-TOGGLE))))
|
||||
#+end_src
|
||||
|
||||
*** Subscribe for Events
|
||||
|
||||
Certain scenarios necessitate subscribing to events. One example from my =workflow= described below requires this capability. With =swayipc=, you have the ability to listen for events and execute actions in response.
|
||||
|
||||
#+begin_src scheme
|
||||
(define (workspace-changed workspace-event)
|
||||
(let* ((current-tree (sway-workspace-event-current workspace-event))
|
||||
(workspace (sway-tree-name current-tree))
|
||||
(focused-workspace (focused-workspace (sway-get-workspaces))))
|
||||
|
||||
(display workspace)))
|
||||
|
||||
(add-hook! sway-workspace-hook workspace-changed)
|
||||
#+end_src
|
||||
|
||||
** Documentation (WIP)
|
||||
|
||||
Most of the source code is documented. You can refer to =init.scm= for a configuration sample. Here are some important points to consider before hacking your Sway setup:
|
||||
|
||||
*** Quick Start
|
||||
Clone this repository to your =~/.config/sway=
|
||||
|
||||
*** Project Structure
|
||||
|
||||
| File | Description |
|
||||
|------------------------------+-----------------------------------------------------------------------------------------------------------|
|
||||
| init.scm | Main entry point for configuring Sway using Guile. |
|
||||
| behavior.scm | Loaded by =init.scm=; modifies parameters and behavior of Sway. |
|
||||
| keybindings.scm | Loaded by =init.scm=; adds custom keybindings to Sway. |
|
||||
| commander | Guile script to send commands to =swayipc= (facilitates keybinding functionality). |
|
||||
| config | Sway configuration file; typically used to invoke =init.scm=. |
|
||||
| sjson | A patched version of =guile-json=; planned to be a separate dependency in the future (not embedded). |
|
||||
|------------------------------+-----------------------------------------------------------------------------------------------------------|
|
||||
| modules/ | Directory containing modules for extending Sway using =swayipc=. |
|
||||
| modules/auto-reload.scm | TODO: Watcher to automatically reload Sway when Guile files change. |
|
||||
| modules/general.scm | Inspired by Emacs =general= package; provides an easy interface for assigning keybindings. |
|
||||
| modules/kbd.scm | WIP: Translates Emacs-like keybindings to be compatible with =Sway=. |
|
||||
| modules/which-key.scm | TODO: Inspired by Emacs =which-key= package; enhances keybinding discovery and management. |
|
||||
| modules/workspace-grid.scm | Configures workspaces in a grid and enables movement between them in specified directions (see workflow). |
|
||||
| modules/workspace-groups.scm | WIP: Spans/synchronizes workspaces across monitors (see workflow). |
|
||||
|------------------------------+-----------------------------------------------------------------------------------------------------------|
|
||||
| swayipc/ | Directory containing the core code for =swayipc=, facilitating communication with Sway. |
|
||||
| swayipc/connection | Establishes =IPC= connection for handling events and commands with Sway. |
|
||||
| swayipc/dispatcher | Provides =Guile functions= for all available =Sway= commands. |
|
||||
| swayipc/events | Provides =Gulie Hooks= for all available =Sway= events. |
|
||||
| swayipc/info | Provides =Guile functions= for querying Sway's current state and information. |
|
||||
| swayipc/records | Provides =Guile records= representing Sway's data structures. |
|
||||
|
||||
1- You can start your =swayipc= configurations from the =REPL=, =terminal=, or a configuration file.
|
||||
Remember: for debugging or displaying output, it's best to run Guile from the REPL or terminal. You can also pipe the output to a file if you desire.
|
||||
|
||||
#+begin_src conf
|
||||
# from sway config file
|
||||
exec_always "~/.config/sway/init.scm"
|
||||
#+end_src
|
||||
|
||||
2- I plan to publish a module for =swayipc=, it's currently not hosted anywhere. You'll need to add the module to your =load path=. Additionally, =swayipc= includes another patched Guile library called =guile-json=, which is embedded for now. In the future, this will be included as a separate dependency rather than embedded.
|
||||
|
||||
#+begin_src scheme
|
||||
(add-to-load-path
|
||||
(dirname (or (current-filename)
|
||||
(string-append (getenv "HOME") "/.config/sway/init.scm"))))
|
||||
#+end_src
|
9
behavior.scm
Executable file
9
behavior.scm
Executable file
|
@ -0,0 +1,9 @@
|
|||
(use-modules (swayipc dispatcher))
|
||||
|
||||
(sway-output "HDMI-A-2" "resolution 1920x1080 position 0,0")
|
||||
(sway-output "DP-1" "resolution 1920x1080 position 0,0")
|
||||
(sway-output "DP-2" "resolution 1920x1080 position 0,0")
|
||||
|
||||
(sway-output "*" "bg /home/ebeem/dotfiles/.wallpapers/fixed/flat-20.png fill")
|
||||
|
||||
(sway-focus-follow-mouse SWAY-FOCUS-FOLLOW-MOUSE-FLAG-NO)
|
39
commander
Executable file
39
commander
Executable file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/guile \
|
||||
-e main -s
|
||||
!#
|
||||
;; #!/usr/bin/guile --fresh-auto-compile
|
||||
;; ensure that the swayipc module is available under the same directory as the init file
|
||||
;; otherwise, the module should be referenced from packaging system or via custom load path
|
||||
|
||||
(add-to-load-path
|
||||
(dirname (or (current-filename)
|
||||
(string-append (getenv "HOME") "/.config/sway/init.scm"))))
|
||||
|
||||
(use-modules (swayipc connection)
|
||||
(ice-9 popen)
|
||||
(sjson builder)
|
||||
(ice-9 binary-ports)
|
||||
(rnrs bytevectors)
|
||||
(rnrs io ports)
|
||||
(oop goops)
|
||||
(srfi srfi-18)
|
||||
(srfi srfi-9)
|
||||
(srfi srfi-1))
|
||||
|
||||
(define COMMANDS-CLIENT-SOCKET (socket AF_UNIX SOCK_STREAM 0))
|
||||
(display (string-append "connecting to " SOCKET-COMMANDS-LISTENER-PATH "\n"))
|
||||
(connect COMMANDS-CLIENT-SOCKET
|
||||
(make-socket-address AF_UNIX SOCKET-COMMANDS-LISTENER-PATH))
|
||||
(display "connected\n")
|
||||
|
||||
(define (send-command command)
|
||||
(display (string-append "sending command: " command "\n"))
|
||||
(display (write-msg COMMANDS-CLIENT-SOCKET
|
||||
RUN-COMMMAND-MSG-ID
|
||||
(scm->json-string command)))
|
||||
(display "sent\n"))
|
||||
|
||||
(define (main args)
|
||||
(display (string-join (cdr args) " "))
|
||||
(newline)
|
||||
(send-command (string-join (cdr args) " ")))
|
2
config
Normal file
2
config
Normal file
|
@ -0,0 +1,2 @@
|
|||
include /etc/sway/config.d/*
|
||||
exec_always "~/.config/sway/init.scm"
|
51
init.scm
Executable file
51
init.scm
Executable file
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/guile
|
||||
!#
|
||||
|
||||
(add-to-load-path
|
||||
(dirname (or (current-filename)
|
||||
(string-append (getenv "HOME") "/.config/sway/init.scm"))))
|
||||
|
||||
(use-modules (oop goops)
|
||||
(srfi srfi-18)
|
||||
(modules workspace-groups)
|
||||
(modules workspace-grid)
|
||||
(swayipc connection)
|
||||
(swayipc records)
|
||||
(swayipc info)
|
||||
(swayipc events)
|
||||
(swayipc dispatcher))
|
||||
|
||||
(load "behavior.scm")
|
||||
;; init keybindings
|
||||
(load "keybindings.scm")
|
||||
(keybindings-init)
|
||||
|
||||
;; subscribe to all events
|
||||
(sway-subscribe-all)
|
||||
|
||||
(set! OUTPUTS '("HDMI-A-2" "DP-1" "DP-2"))
|
||||
(set! GROUPS
|
||||
'(("11-browser" "21-browser" "31-browser")
|
||||
("12-development" "22-development" "32-development")
|
||||
("13-databases" "23-databases" "33-databases")
|
||||
("14-communication" "24-communication" "34-communication")
|
||||
("15-development" "25-development" "35-development")
|
||||
("16-gaming" "26-gaming" "36-gaming")
|
||||
("17-mail" "27-mail" "37-mail")
|
||||
("18-development" "28-development" "38-development")
|
||||
("19-media" "29-media" "39-media")))
|
||||
|
||||
(workspace-groups-init)
|
||||
|
||||
(set! ROWS 3)
|
||||
(set! COLUMNS 3)
|
||||
(set! WORKSPACES (apply map list GROUPS))
|
||||
|
||||
(workspace-grid-init)
|
||||
|
||||
;; TODO: load which key module
|
||||
|
||||
(start-commands-listener-thread)
|
||||
(start-event-listener-thread)
|
||||
(thread-join! LISTENER-THREAD)
|
||||
(thread-join! COMMANDS-LISTENER-THREAD)
|
122
keybindings.scm
Executable file
122
keybindings.scm
Executable file
|
@ -0,0 +1,122 @@
|
|||
(use-modules (modules kbd)
|
||||
(modules general)
|
||||
(ice-9 popen)
|
||||
(srfi srfi-18)
|
||||
(ice-9 textual-ports))
|
||||
|
||||
(define (exec command)
|
||||
"execute given shell command"
|
||||
(display (string-append "running " command "\n"))
|
||||
(thread-start! (make-thread (lambda () (system command)))))
|
||||
|
||||
(define (custom-sway-keybinding-translator key)
|
||||
"Translates keybindings, passing kbd function will enable emacs
|
||||
like key chords. The default implementation doesn't modify passed keybindings"
|
||||
(kbd key))
|
||||
|
||||
(define (keybindings-init)
|
||||
(kbd-init)
|
||||
|
||||
;; (configure-sway-keybinding-translator custom-sway-keybinding-translator)
|
||||
(define general-module (resolve-module '(modules general)))
|
||||
(configure-sway-keybinding-translator custom-sway-keybinding-translator)
|
||||
|
||||
;; define root keybindings
|
||||
(sway-define-keys
|
||||
;; media-keys
|
||||
`("XF86AudioLowerVolume" (exec "pactl set-sink-volume @DEFAULT_SINK@ -5%"))
|
||||
`("XF86AudioRaiseVolume" (exec "pactl set-sink-volume @DEFAULT_SINK@ +5%"))
|
||||
`("s-[" (exec "pactl set-sink-volume @DEFAULT_SINK@ -5%"))
|
||||
`("s-]" (exec "pactl set-sink-volume @DEFAULT_SINK@ +5%"))
|
||||
`("XF86AudioMute" (exec "pactl set-sink-mute @DEFAULT_SINK@ toggle"))
|
||||
`("XF86AudioNext" (exec "mpc next"))
|
||||
`("XF86AudioPrev" (exec "mpc prev"))
|
||||
`("XF86AudioPlay" (exec "mpc toggle"))
|
||||
|
||||
;; brightness-keys
|
||||
`("XF86MonBrightnessUp" (exec "brightnessctl set +10%"))
|
||||
`("XF86MonBrightnessDown" (exec "brightnessctl set 10%-"))
|
||||
|
||||
;; window and group management
|
||||
`("s-f" (sway-fullscreen SWAY-FULLSCREEN-TOGGLE))
|
||||
|
||||
;; move focus
|
||||
`("s-h" (sway-focus-container SWAY-DIRECTION-LEFT))
|
||||
`("s-j" (sway-focus-container SWAY-DIRECTION-DOWN))
|
||||
`("s-k" (sway-focus-container SWAY-DIRECTION-UP))
|
||||
`("s-l" (sway-focus-container SWAY-DIRECTION-RIGHT))
|
||||
|
||||
;; move containers
|
||||
`("s-S-h" (sway-move-container SWAY-DIRECTION-LEFT))
|
||||
`("s-S-j" (sway-move-container SWAY-DIRECTION-DOWN))
|
||||
`("s-S-k" (sway-move-container SWAY-DIRECTION-UP))
|
||||
`("s-S-l" (sway-move-container SWAY-DIRECTION-RIGHT))
|
||||
|
||||
;; switch workspace
|
||||
`("s-C-h" (switch-workspace-left))
|
||||
`("s-C-j" (switch-workspace-down))
|
||||
`("s-C-k" (switch-workspace-up))
|
||||
`("s-C-l" (switch-workspace-right))
|
||||
|
||||
;; move container to workspace
|
||||
`("s-M-C-h" (move-container-to-workspace-left))
|
||||
`("s-M-C-j" (move-container-to-workspace-down))
|
||||
`("s-M-C-k" (move-container-to-workspace-up))
|
||||
`("s-M-C-l" (move-container-to-workspace-right))
|
||||
|
||||
;; ;; Tab like cycling
|
||||
;; (define-key *top-map* (kbd "s-.") "next-in-frame")
|
||||
;; (define-key *top-map* (kbd "s-,") "prev-in-frame")
|
||||
|
||||
`("s-w" (sway-kill))
|
||||
`("s-Return" (exec "alacritty"))
|
||||
`("M-s-Space" (exec "~/.bin/switch-keyboard-layout"))
|
||||
`("C-s-Space" (exec "rofi -show drun")))
|
||||
|
||||
;; define leader keymap
|
||||
(sway-define-keys
|
||||
#:prefix "s-Space" #:wk "Leader"
|
||||
`("o" (exec "rofi -show drun"))
|
||||
`("C-g" (sway-mode "default") #:wk "abort")
|
||||
|
||||
;; rofi keymap
|
||||
`(sway-define-keys
|
||||
#:prefix "r" #:wk "Rofi"
|
||||
("p" (exec "~/.config/rofi/bin/password-manager"))
|
||||
("m" (exec "rofi-mount"))
|
||||
("u" (exec "rofi-unmount"))
|
||||
("w" (exec ".config/rofi/bin/wifi"))
|
||||
("b" (exec "~/.config/rofi/bin/bluetooth"))
|
||||
("f" (exec "~/.config/rofi/bin/finder"))
|
||||
("k" (exec "~/.config/rofi/bin/keyboard-layout"))
|
||||
("P" (exec "~/.config/rofi/bin/powermenu"))
|
||||
("s" (exec "~/.config/rofi/bin/sound-input"))
|
||||
("S" (exec "~/.config/rofi/bin/sound-output")))
|
||||
|
||||
;; screenshot keymap
|
||||
`(sway-define-keys
|
||||
#:prefix "s" #:wk "Screenshot"
|
||||
("d" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot gui"))
|
||||
("s" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot screen"))
|
||||
("f" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot full"))
|
||||
("m" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot gui --last-region"))
|
||||
|
||||
(sway-define-keys
|
||||
#:prefix "d" #:wk "DelayScreenshot"
|
||||
("d" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot gui -d 2500"))
|
||||
("s" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot screen -d 2500"))
|
||||
("f" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot full -d 2500"))
|
||||
("l" (exec "export XDG_CURRENT_DESKTOP=sway && flameshot gui -d 2500 --last-region"))))
|
||||
|
||||
;; session keymap
|
||||
`(sway-define-keys
|
||||
#:prefix "q" #:wk "Session"
|
||||
("q" (sway-exit))
|
||||
("r" (sway-reload)))
|
||||
|
||||
`(sway-define-keys
|
||||
#:prefix "w" #:wk "Window"
|
||||
("v" (sway-split-container SWAY-SPLIT-VERTICAL))
|
||||
("h" (sway-split-container SWAY-SPLIT-HORIZONTAL))
|
||||
("f" (sway-fullscreen SWAY-FULLSCREEN-TOGGLE))
|
||||
("d" (sway-fullscreen SWAY-FULLSCREEN-TOGGLE)))))
|
4
modules/auto-reload.scm
Executable file
4
modules/auto-reload.scm
Executable file
|
@ -0,0 +1,4 @@
|
|||
(define-module (modules which-key)
|
||||
#:use-module (swayipc dispatcher)
|
||||
#:export ())
|
||||
|
124
modules/general.scm
Executable file
124
modules/general.scm
Executable file
|
@ -0,0 +1,124 @@
|
|||
(define-module (modules general)
|
||||
#:use-module (sjson parser)
|
||||
#:use-module (swayipc dispatcher)
|
||||
#:use-module (swayipc connection)
|
||||
#:use-module (srfi srfi-18)
|
||||
#:use-module (ice-9 hash-table)
|
||||
#:export (sway-keybinding-translator
|
||||
configure-sway-keybinding-translator
|
||||
sway-define-keys
|
||||
sway-define-key
|
||||
sway-keybindings
|
||||
sway-submaps))
|
||||
|
||||
;; Local copy of keybindings configured by general, it's recommended to
|
||||
;; only use general to assign keybindings, otherwise this hashtable won't
|
||||
;; by synced with actual keybindings available in sway
|
||||
(define sway-keybindings (make-hash-table))
|
||||
|
||||
;; Local copy of submaps configured by general, it's recommended to
|
||||
;; only use general to assign submaps, otherwise this hashtable won't
|
||||
;; by synced with actual submaps available in sway"
|
||||
(define sway-submaps (make-hash-table))
|
||||
(hash-set! sway-submaps "" "default")
|
||||
|
||||
(define (sway-keybinding-translator key)
|
||||
"Translates keybindings, passing kbd function will enable emacs
|
||||
like key chords. The default implementation doesn't modify passed keybindings"
|
||||
key)
|
||||
|
||||
(define (configure-sway-keybinding-translator proc)
|
||||
(set! sway-keybinding-translator proc))
|
||||
|
||||
(define (exp->string exp)
|
||||
(call-with-output-string (lambda (p)
|
||||
(write exp p))))
|
||||
|
||||
(define* (define-submap chord key wk submap parent-submap)
|
||||
(display (string-append "define submap " chord "\n"))
|
||||
(hash-set! sway-submaps chord submap)
|
||||
(define-keybindings chord key
|
||||
`(sway-mode ,submap)
|
||||
wk parent-submap)
|
||||
(define-keybindings (string-append chord " Esc")
|
||||
"Escape" `(sway-mode "default")
|
||||
"Escape" submap))
|
||||
|
||||
(define (last-key key)
|
||||
(car (reverse (string-split key #\+))))
|
||||
|
||||
(define (sway-command exp-str)
|
||||
(string-append "exec '" (dirname (dirname (current-filename))) "/commander "
|
||||
(exp->string exp-str) "'"))
|
||||
|
||||
(define* (define-keybindings chord key exp wk submap)
|
||||
(hash-set! sway-keybindings chord '(key exp wk submap))
|
||||
(let* ((type (if (string->number (last-key key)) "bindcode" "bindsym"))
|
||||
(command (string-append type " " key " " (sway-command (exp->string exp))))
|
||||
(esc (string-append type " " key " " (sway-command (exp->string `(and ,exp (sway-mode "default")))))))
|
||||
(if (equal? submap "default")
|
||||
(dispatch-command command)
|
||||
(begin
|
||||
(sway-mode-subcommand submap command)
|
||||
(unless (equal? "sway-mode" (symbol->string (car exp)))
|
||||
(display "escape after executing\n")
|
||||
(display esc)
|
||||
(sway-mode-subcommand submap esc))))))
|
||||
|
||||
(define (find-submap chord)
|
||||
(or
|
||||
(hash-get-handle sway-submaps
|
||||
(string-join
|
||||
(list-head chord (- (length chord) 1)) " "))
|
||||
(error 'find-submap "chord was not found in submaps, a submap has to be defined for it" chord)))
|
||||
|
||||
(define* (sway-define-key chord #:optional exp #:key wk submap)
|
||||
"assign a key to a given expression"
|
||||
(let* ((chord-ls
|
||||
(map sway-keybinding-translator
|
||||
(string-split chord #\Space)))
|
||||
(chord (string-join chord-ls " "))
|
||||
(key (car (last-pair chord-ls)))
|
||||
(key-code (string->number key))
|
||||
(description (or wk (or submap (symbol->string (car exp))))))
|
||||
(if submap
|
||||
(define-submap chord key wk submap
|
||||
(cdr (find-submap chord-ls)))
|
||||
(define-keybindings chord key exp wk
|
||||
(cdr (find-submap chord-ls))))))
|
||||
|
||||
(define* (sway-define-keys #:key parent-prefix prefix wk . args )
|
||||
(when parent-prefix
|
||||
(set! prefix (string-append parent-prefix " " prefix)))
|
||||
|
||||
(when prefix
|
||||
(display (string-append "define prefix submap: " prefix ", wk " wk "\n"))
|
||||
(sway-define-key prefix #:submap wk))
|
||||
|
||||
(map (lambda (arg)
|
||||
(when (list? arg)
|
||||
|
||||
(display "ARG: ")
|
||||
(display arg)
|
||||
(newline)
|
||||
(display (car arg))
|
||||
(newline)
|
||||
(if (and
|
||||
(symbol? (car arg))
|
||||
(equal? "sway-define-keys" (symbol->string (car arg))))
|
||||
(apply sway-define-keys (append `(#:parent-prefix ,prefix) (cdr arg)))
|
||||
(apply sway-define-key
|
||||
(cons (string-append
|
||||
(if prefix (string-append prefix " ") "")
|
||||
(car arg)) (cdr arg))))))
|
||||
args))
|
||||
|
||||
(define (custom-exception-handler exc command-id payload)
|
||||
(display "An error occurred: ")
|
||||
(display (exp->string exc))
|
||||
(newline)
|
||||
(display (string-append "command: " (number->string command-id) ", payload: " payload)))
|
||||
|
||||
(add-hook! command-received-hook
|
||||
(lambda (command-id payload)
|
||||
(eval-string (json-string->scm payload))))
|
2113
modules/kbd.scm
Executable file
2113
modules/kbd.scm
Executable file
File diff suppressed because it is too large
Load diff
4
modules/which-key.scm
Executable file
4
modules/which-key.scm
Executable file
|
@ -0,0 +1,4 @@
|
|||
(define-module (modules which-key)
|
||||
#:use-module (swayipc dispatcher)
|
||||
#:export ())
|
||||
|
167
modules/workspace-grid.scm
Executable file
167
modules/workspace-grid.scm
Executable file
|
@ -0,0 +1,167 @@
|
|||
;; use example:
|
||||
|
||||
;; (set! WORKSPACES
|
||||
;; (("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"))
|
||||
|
||||
;; (set! ROWS 3)
|
||||
;; (set! COLUMNS 3)
|
||||
|
||||
;; (workspace-grid-init)
|
||||
|
||||
(define-module (modules workspace-grid)
|
||||
#:use-module (swayipc records)
|
||||
#:use-module (swayipc info)
|
||||
#:use-module (swayipc dispatcher)
|
||||
#:use-module (swayipc events)
|
||||
|
||||
#:export (
|
||||
WORKSPACES
|
||||
COLUMNS
|
||||
ROWS
|
||||
configure-workspaces
|
||||
configure-rows
|
||||
configure-columns
|
||||
get-active-workspace-index
|
||||
switch-workspace-up
|
||||
switch-workspace-right
|
||||
switch-workspace-down
|
||||
switch-workspace-left
|
||||
move-container-to-workspace-up
|
||||
move-container-to-workspace-right
|
||||
move-container-to-workspace-down
|
||||
move-container-to-workspace-left
|
||||
valid-grid?
|
||||
workspace-grid-init))
|
||||
|
||||
;; The order in which the outputs are organized, it's important that
|
||||
;; the order of outputs match the order of workspaces in `WORKSPACE`
|
||||
;; example:
|
||||
;; (set! WORKSPACES '(("ws-o1-1" "ws-o1-2" "ws-o1-3") ("ws-o2-1" "ws-o2-2" "ws-o2-3"))
|
||||
(define WORKSPACES '())
|
||||
|
||||
;; number of rows in the grid
|
||||
(define ROWS 1)
|
||||
;; number of columns in the grid
|
||||
(define COLUMNS 1)
|
||||
|
||||
(define (configure-workspaces workspaces)
|
||||
(set! WORKSPACES workspaces))
|
||||
|
||||
(define (configure-rows rows)
|
||||
(set! ROWS rows))
|
||||
|
||||
(define (configure-columns columns)
|
||||
(set! COLUMNS columns))
|
||||
|
||||
(define* (get-active-workspace-name #:optional (workspaces (sway-get-workspaces)))
|
||||
"get name of active workspace"
|
||||
(cond
|
||||
((null? workspaces) #f)
|
||||
((equal? (sway-workspace-focused (car workspaces)) #t)
|
||||
(sway-workspace-name (car workspaces)))
|
||||
(else (get-active-workspace-name (cdr workspaces)))))
|
||||
|
||||
(define* (get-output-index workspace #:optional (workspaces WORKSPACES) (index 0))
|
||||
"get output index of target workspace"
|
||||
(cond
|
||||
((null? workspaces) #f)
|
||||
((member workspace (car workspaces)) index)
|
||||
(else (get-output-index workspace (cdr workspaces) (+ index 1)))))
|
||||
|
||||
(define* (get-workspace-index workspace #:optional
|
||||
(workspaces
|
||||
(list-ref WORKSPACES (get-output-index workspace))))
|
||||
"get index of target workspace"
|
||||
(let* ((memberls (member workspace workspaces)))
|
||||
(if memberls (- (length workspaces) (length memberls)))))
|
||||
|
||||
(define (get-active-workspace-index)
|
||||
"get index of active/focused workspace"
|
||||
(let* ((workspace (get-active-workspace-name)))
|
||||
(get-workspace-index workspace)))
|
||||
|
||||
;; available directions, up, right, down, left
|
||||
(define* (get-workspace-direction direction #:optional (index -1))
|
||||
"get the index the next workspace after applying the direction"
|
||||
(let* ((index (if (< index 0) (get-active-workspace-index) index))
|
||||
(current-row (floor (/ index COLUMNS)))
|
||||
(current-column (modulo index COLUMNS))
|
||||
(target-row
|
||||
(cond ((equal? direction "up") (- current-row 1))
|
||||
((equal? direction "down") (+ current-row 1))
|
||||
(else current-row)))
|
||||
(target-column
|
||||
(cond ((equal? direction "left") (- current-column 1))
|
||||
((equal? direction "right") (+ current-column 1))
|
||||
(else current-column))))
|
||||
(+ (* COLUMNS (modulo target-row ROWS))
|
||||
(modulo target-column COLUMNS))))
|
||||
|
||||
(define* (get-workspace-name #:optional
|
||||
(workspace (get-active-workspace-index))
|
||||
(output (get-output-index (get-active-workspace-name))))
|
||||
(list-ref (list-ref WORKSPACES output) workspace))
|
||||
|
||||
;; exposed command for easier access
|
||||
(define (switch-workspace-up)
|
||||
(sway-switch-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "up"))))
|
||||
|
||||
(define (switch-workspace-right)
|
||||
(sway-switch-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "right"))))
|
||||
|
||||
(define (switch-workspace-down)
|
||||
(sway-switch-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "down"))))
|
||||
|
||||
(define (switch-workspace-left)
|
||||
(sway-switch-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "left"))))
|
||||
|
||||
(define (move-container-to-workspace-up)
|
||||
(sway-move-container-to-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "up")))
|
||||
(switch-workspace-up))
|
||||
|
||||
(define (move-container-to-workspace-right)
|
||||
(sway-move-container-to-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "right")))
|
||||
(switch-workspace-right))
|
||||
|
||||
(define (move-container-to-workspace-down)
|
||||
(sway-move-container-to-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "down")))
|
||||
(switch-workspace-down))
|
||||
|
||||
(define (move-container-to-workspace-left)
|
||||
(sway-move-container-to-workspace
|
||||
(get-workspace-name
|
||||
(get-workspace-direction "left")))
|
||||
(switch-workspace-left))
|
||||
|
||||
(define (valid-grid? rows columns workspaces)
|
||||
"validate the grid structure"
|
||||
(and (> (length workspaces) 0)
|
||||
(equal? (* rows columns) (length (car workspaces)))))
|
||||
|
||||
(define (workspace-grid-init)
|
||||
(display "starting workspace-grid\n")
|
||||
(display WORKSPACES)
|
||||
(newline)
|
||||
(if (valid-grid? ROWS COLUMNS WORKSPACES)
|
||||
(display (string-append "successfully started workspace "
|
||||
(number->string ROWS) "x"
|
||||
(number->string COLUMNS) "\n"))
|
||||
(display (string-append "workspace grid failed to start the grid configs "
|
||||
(number->string ROWS) "x"
|
||||
(number->string COLUMNS) "\n"))))
|
110
modules/workspace-groups.scm
Executable file
110
modules/workspace-groups.scm
Executable file
|
@ -0,0 +1,110 @@
|
|||
;; use example:
|
||||
|
||||
;; (set! OUTPUTS '("HDMI-A-2" "DP-1" "DP-2"))
|
||||
;; (set! GROUPS
|
||||
;; '(("10" "20" "30")
|
||||
;; ("11" "21" "31")
|
||||
;; ("12" "22" "32")
|
||||
;; ("13" "23" "33")
|
||||
;; ("14" "24" "34")
|
||||
;; ("15" "25" "35")
|
||||
;; ("16" "26" "36")
|
||||
;; ("17" "27" "37")
|
||||
;; ("18" "28" "38")
|
||||
;; ("19" "29" "39")))
|
||||
|
||||
;; (workspace-groups-init)
|
||||
|
||||
(define-module (modules workspace-groups)
|
||||
#:use-module (swayipc records)
|
||||
#:use-module (swayipc info)
|
||||
#:use-module (swayipc dispatcher)
|
||||
#:use-module (swayipc events)
|
||||
|
||||
#:export (workspace-groups-init
|
||||
OUTPUTS
|
||||
GROUPS))
|
||||
|
||||
;; The order in which the outputs are organized, it's important that
|
||||
;; the order of outputs match the order of workspaces in `WORKSPACE-GROUPS`
|
||||
;; example:
|
||||
;; (set! OUTPUTS '("HDMI-A-2" "DP-1" "DP-2"))
|
||||
(define OUTPUTS '())
|
||||
|
||||
;; The workspace groups, each is a list of workspace names
|
||||
;; if a workspace of this list is activated, the rest of the workspaces
|
||||
;; will be activated as well.
|
||||
;; example:
|
||||
;; (set! GROUPS
|
||||
;; '(("10" "20" "30")
|
||||
;; ("19" "29" "39")))
|
||||
(define GROUPS '())
|
||||
|
||||
(define (configure-outputs outputs)
|
||||
(set! OUTPUTS outputs))
|
||||
|
||||
(define (configure-groups groups)
|
||||
(set! GROUPS groups))
|
||||
|
||||
(define last-switched-group '())
|
||||
|
||||
(define (is-workspace-focused workspace output outputs)
|
||||
(cond
|
||||
((null? outputs) #f)
|
||||
((equal? output (sway-output-name (car outputs)))
|
||||
(equal? workspace (sway-output-current-workspace (car outputs))))
|
||||
(else (is-workspace-focused workspace output (cdr outputs)))))
|
||||
|
||||
(define (switch-to-workspace-group group initiator)
|
||||
(unless (equal? last-switched-group group)
|
||||
(let* ((initiator-output "")
|
||||
(outputs (sway-get-outputs)))
|
||||
(set! last-switched-group group)
|
||||
(newline)
|
||||
(for-each
|
||||
(lambda (workspace output)
|
||||
(if (equal? workspace initiator)
|
||||
(set! initiator-output output)
|
||||
(unless (is-workspace-focused workspace output outputs)
|
||||
(sway-switch-workspace workspace))))
|
||||
group OUTPUTS)
|
||||
|
||||
;; switch to initiator at last so the focus behaves as expected
|
||||
(sway-switch-workspace initiator))))
|
||||
|
||||
(define (focused-workspace workspaces)
|
||||
(cond
|
||||
((null? workspaces) #f)
|
||||
((equal? #t (sway-workspace-focused (car workspaces)))
|
||||
(sway-workspace-name (car workspaces)))
|
||||
(else (focused-workspace (cdr workspaces)))))
|
||||
|
||||
(define (workspace-changed workspace-event)
|
||||
(let* ((current-tree (sway-workspace-event-current workspace-event))
|
||||
(workspace (sway-tree-name current-tree))
|
||||
(focused-workspace (focused-workspace (sway-get-workspaces))))
|
||||
|
||||
;; sometimes there is a delay in events, it's neccessary to ensure
|
||||
;; that event workspace is same as the currently focused workspace
|
||||
(when (equal? workspace focused-workspace)
|
||||
(unless (member workspace last-switched-group)
|
||||
(set! last-switched-group '()))
|
||||
(for-each
|
||||
(lambda (group)
|
||||
(when (member workspace group)
|
||||
(switch-to-workspace-group group workspace)))
|
||||
GROUPS))))
|
||||
|
||||
(define (pin-workspaces-to-output groups outputs)
|
||||
(for-each
|
||||
(lambda (group)
|
||||
(for-each
|
||||
(lambda (workspace output)
|
||||
(sway-switch-workspace-on-output workspace output))
|
||||
group outputs))
|
||||
groups))
|
||||
|
||||
(define (workspace-groups-init)
|
||||
;; pin workspaces to output
|
||||
(pin-workspaces-to-output GROUPS OUTPUTS)
|
||||
(add-hook! sway-workspace-hook workspace-changed))
|
280
sjson/builder.scm
Normal file
280
sjson/builder.scm
Normal file
|
@ -0,0 +1,280 @@
|
|||
;;; (json builder) --- Guile JSON implementation.
|
||||
|
||||
;; Copyright (C) 2013-2020 Aleix Conchillo Flaque <aconchillo@gmail.com>
|
||||
;; Copyright (C) 2015,2016 Jan Nieuwenhuizen <janneke@gnu.org>
|
||||
;;
|
||||
;; This file is part of guile-json.
|
||||
;;
|
||||
;; guile-json is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; guile-json is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with guile-json. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; JSON module for Guile
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-module (sjson builder)
|
||||
#:use-module (ice-9 format)
|
||||
#:use-module (ice-9 textual-ports)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-43)
|
||||
#:export (scm->json
|
||||
scm->json-string
|
||||
scm->json-seq
|
||||
scm->json-seq-string))
|
||||
|
||||
|
||||
;;
|
||||
;; Miscellaneuos helpers
|
||||
;;
|
||||
|
||||
(define (indent-string pretty level)
|
||||
(if pretty (format #f "~v_" (* 2 level)) ""))
|
||||
|
||||
;;
|
||||
;; String builder helpers
|
||||
;;
|
||||
|
||||
(define (unicode->json-string unicode)
|
||||
(format #f "\\u~4,'0x" unicode))
|
||||
|
||||
(define (unicode->json-surrogate-pair unicode)
|
||||
(let* ((u (- unicode #x10000))
|
||||
(w1 (+ #xD800 (ash u -10)))
|
||||
(w2 (+ #xDC00 (logand u #x3ff))))
|
||||
(string-append (unicode->json-string w1)
|
||||
(unicode->json-string w2))))
|
||||
|
||||
(define (build-json-unicode c)
|
||||
(let* ((value (char->integer c)))
|
||||
(cond
|
||||
((< value 32)
|
||||
(unicode->json-string value))
|
||||
((<= value 255)
|
||||
(string c))
|
||||
((<= value #xFFFF)
|
||||
(unicode->json-string value))
|
||||
((<= value #x10FFFF)
|
||||
(unicode->json-surrogate-pair value))
|
||||
(else (throw 'json-invalid (string c))))))
|
||||
|
||||
(define (->string x)
|
||||
(cond ((char? x) (make-string 1 x))
|
||||
((number? x) (number->string x))
|
||||
((symbol? x) (symbol->string x))
|
||||
(else x)))
|
||||
|
||||
(define (build-string c port solidus unicode)
|
||||
(case c
|
||||
((#\" #\\) (format port "\\~c" c))
|
||||
((#\bs) (put-string port "\\b"))
|
||||
((#\ff) (put-string port "\\f"))
|
||||
((#\lf) (put-string port "\\n"))
|
||||
((#\cr) (put-string port "\\r"))
|
||||
((#\ht) (put-string port "\\t"))
|
||||
((#\/) (if solidus
|
||||
(put-string port "\\/")
|
||||
(put-char port c)))
|
||||
(else (if unicode
|
||||
(put-string port (build-json-unicode c))
|
||||
(put-char port c)))))
|
||||
|
||||
(define (json-build-string scm port solidus unicode)
|
||||
(put-string port "\"")
|
||||
(for-each (lambda (c) (build-string c port solidus unicode))
|
||||
(string->list (->string scm)))
|
||||
(put-string port "\""))
|
||||
|
||||
;;
|
||||
;; Object builder functions
|
||||
;;
|
||||
|
||||
(define (build-object-pair p port solidus unicode null pretty level)
|
||||
(put-string port (indent-string pretty level))
|
||||
(json-build-string (car p) port solidus unicode)
|
||||
(put-string port ":")
|
||||
(build-space port pretty)
|
||||
(json-build (cdr p) port solidus unicode null pretty level))
|
||||
|
||||
(define (build-newline port pretty)
|
||||
(cond (pretty (newline port))))
|
||||
|
||||
(define (build-space port pretty)
|
||||
(cond (pretty (put-string port " "))))
|
||||
|
||||
(define (json-build-object scm port solidus unicode null pretty level)
|
||||
(put-string port "{")
|
||||
(let ((pairs scm))
|
||||
(unless (null? pairs)
|
||||
(build-newline port pretty)
|
||||
(build-object-pair (car pairs) port solidus unicode null pretty (+ level 1))
|
||||
(for-each (lambda (p)
|
||||
(put-string port ",")
|
||||
(build-newline port pretty)
|
||||
(build-object-pair p port solidus unicode null pretty (+ level 1)))
|
||||
(cdr pairs))
|
||||
(build-newline port pretty)
|
||||
(put-string port (indent-string pretty level))))
|
||||
(put-string port "}"))
|
||||
|
||||
;;
|
||||
;; Array builder functions
|
||||
;;
|
||||
|
||||
(define (json-build-array scm port solidus unicode null pretty level)
|
||||
(put-string port "[")
|
||||
(unless (or (null? scm) (zero? (vector-length scm)))
|
||||
(build-newline port pretty)
|
||||
(vector-for-each (lambda (i v)
|
||||
(cond
|
||||
((> i 0)
|
||||
(put-string port ",")
|
||||
(build-newline port pretty)))
|
||||
(put-string port (indent-string pretty (+ level 1)))
|
||||
(json-build v port solidus unicode null pretty (+ level 1)))
|
||||
scm)
|
||||
(build-newline port pretty)
|
||||
(put-string port (indent-string pretty level)))
|
||||
(put-string port "]"))
|
||||
|
||||
;;
|
||||
;; Booleans, null and number builder functions
|
||||
;;
|
||||
|
||||
(define (json-build-boolean scm port)
|
||||
(put-string port (if scm "true" "false")))
|
||||
|
||||
(define (json-build-null port)
|
||||
(put-string port "null"))
|
||||
|
||||
(define (json-build-number scm port)
|
||||
(if (and (rational? scm) (not (integer? scm)))
|
||||
(put-string port (number->string (exact->inexact scm)))
|
||||
(put-string port (number->string scm))))
|
||||
|
||||
;;
|
||||
;; Main builder functions
|
||||
;;
|
||||
|
||||
(define (json-number? number)
|
||||
(and (number? number) (eqv? (imag-part number) 0) (finite? number)))
|
||||
|
||||
(define (json-key? scm)
|
||||
(or (symbol? scm) (string? scm)))
|
||||
|
||||
(define (json-valid? scm null)
|
||||
(cond
|
||||
((eq? scm null) #t)
|
||||
((boolean? scm) #t)
|
||||
((json-number? scm) #t)
|
||||
((symbol? scm) #t)
|
||||
((string? scm) #t)
|
||||
((vector? scm) (vector-every (lambda (elem) (json-valid? elem null)) scm))
|
||||
((pair? scm)
|
||||
(every (lambda (entry)
|
||||
(and (pair? entry)
|
||||
(json-key? (car entry))
|
||||
(json-valid? (cdr entry) null)))
|
||||
scm))
|
||||
((null? scm) #t)
|
||||
(else (throw 'json-invalid scm))))
|
||||
|
||||
(define (json-build scm port solidus unicode null pretty level)
|
||||
(cond
|
||||
((eq? scm null) (json-build-null port))
|
||||
((boolean? scm) (json-build-boolean scm port))
|
||||
((json-number? scm) (json-build-number scm port))
|
||||
((symbol? scm) (json-build-string (symbol->string scm) port solidus unicode))
|
||||
((string? scm) (json-build-string scm port solidus unicode))
|
||||
((vector? scm) (json-build-array scm port solidus unicode null pretty level))
|
||||
((or (pair? scm) (null? scm))
|
||||
(json-build-object scm port solidus unicode null pretty level))
|
||||
(else (throw 'json-invalid scm))))
|
||||
|
||||
;;
|
||||
;; Public procedures
|
||||
;;
|
||||
|
||||
(define* (scm->json scm
|
||||
#:optional (port (current-output-port))
|
||||
#:key
|
||||
(solidus #f) (unicode #f) (null 'null)
|
||||
(validate #t) (pretty #f))
|
||||
"Creates a JSON document from native. The argument @var{scm} contains the
|
||||
native value of the JSON document. Takes one optional argument, @var{port},
|
||||
which defaults to the current output port where the JSON document will be
|
||||
written. It also takes a few keyword arguments: @{solidus}: if true, the
|
||||
slash (/ solidus) character will be escaped (defaults to false), @{unicode}:
|
||||
if true, unicode characters will be escaped when needed (defaults to false),
|
||||
@{null}: value for JSON's null (defaults to the 'null symbol), @{validate} :
|
||||
if true, the native value will be validated before starting to print the JSON
|
||||
document (defaults to true) and @{pretty}: if true, the JSON document will be
|
||||
pretty printed (defaults to false).
|
||||
|
||||
Note that when using alists to build JSON objects, symbols or numbers might be
|
||||
used as keys and they both will be converted to strings.
|
||||
"
|
||||
(cond
|
||||
((and validate (json-valid? scm null))
|
||||
(json-build scm port solidus unicode null pretty 0))
|
||||
(else
|
||||
(json-build scm port solidus unicode null pretty 0))))
|
||||
|
||||
(define* (scm->json-string scm #:key
|
||||
(solidus #f) (unicode #f) (null 'null)
|
||||
(validate #t) (pretty #f))
|
||||
"Creates a JSON document from native into a string. The argument @var{scm}
|
||||
contains the native value of the JSON document. It also takes a few keyword
|
||||
arguments: @{solidus}: if true, the slash (/ solidus) character will be
|
||||
escaped (defaults to false), @{unicode}: if true, unicode characters will be
|
||||
escaped when needed (defaults to false), @{null}: value for JSON's
|
||||
null (defaults to the 'null symbol), @{validate} : if true, the native value
|
||||
will be validated before starting to print the JSON document (defaults to
|
||||
true) and @{pretty}: if true, the JSON document will be pretty
|
||||
printed (defaults to false).
|
||||
|
||||
Note that when using alists to build JSON objects, symbols or numbers might be
|
||||
used as keys and they both will be converted to strings.
|
||||
"
|
||||
(call-with-output-string
|
||||
(lambda (p)
|
||||
(scm->json scm p
|
||||
#:solidus solidus #:unicode unicode #:null null
|
||||
#:pretty pretty #:validate validate))))
|
||||
|
||||
(define* (scm->json-seq objects #:optional (port (current-output-port))
|
||||
#:key (null 'null) (solidus #f) (validate #t))
|
||||
"Create a JSON text sequence from native @var{objects} and write it.
|
||||
The optional argument @var{port} specifies the output port, which defaults to
|
||||
the current output port. This procedure also takes a subset of
|
||||
@code{json->scm} keyword arguments - @{null}, @{solidus} and @{validate}.
|
||||
@{unicode} and @{pretty} are unsupported because RFC 7464 requires JSON text
|
||||
sequences to be written in UTF-8, one per line."
|
||||
(define (put-entry object)
|
||||
(put-char port #\rs)
|
||||
(scm->json object port
|
||||
#:unicode #t #:null null #:solidus solidus #:validate validate)
|
||||
(put-char port #\lf))
|
||||
(for-each put-entry objects))
|
||||
|
||||
(define* (scm->json-seq-string objects
|
||||
#:key (null 'null) (solidus #f) (validate #t))
|
||||
"Create a JSON text sequence from native @var{objects} and return it.
|
||||
This procedure takes the same keyword arguments as @code{scm->json-seq}."
|
||||
(call-with-output-string
|
||||
(lambda (port)
|
||||
(scm->json-seq objects port
|
||||
#:null null #:solidus solidus #:validate validate))))
|
||||
|
||||
;;; (json builder) ends here
|
490
sjson/parser.scm
Normal file
490
sjson/parser.scm
Normal file
|
@ -0,0 +1,490 @@
|
|||
;;; (json parser) --- Guile JSON implementation.
|
||||
|
||||
;; Copyright (C) 2013-2020 Aleix Conchillo Flaque <aconchillo@gmail.com>
|
||||
;;
|
||||
;; This file is part of guile-json.
|
||||
;;
|
||||
;; guile-json is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; guile-json is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with guile-json. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; JSON module for Guile
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-module (sjson parser)
|
||||
#:use-module (ice-9 rdelim)
|
||||
#:use-module (ice-9 streams)
|
||||
#:use-module (rnrs io ports)
|
||||
#:export (json->scm
|
||||
json-string->scm
|
||||
json-seq->scm
|
||||
json-seq-string->scm))
|
||||
|
||||
;;
|
||||
;; Miscellaneuos helpers
|
||||
;;
|
||||
|
||||
(define (json-exception port)
|
||||
(throw 'json-invalid port))
|
||||
|
||||
(define (digit? c)
|
||||
(case c
|
||||
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) #t)
|
||||
(else #f)))
|
||||
|
||||
(define (whitespace? c)
|
||||
(case c
|
||||
((#\sp #\ht #\lf #\cr) #t)
|
||||
(else #f)))
|
||||
|
||||
(define (control-char? ch)
|
||||
(<= (char->integer ch) #x1F))
|
||||
|
||||
(define (skip-whitespaces port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
((whitespace? ch)
|
||||
(read-char port)
|
||||
(skip-whitespaces port))
|
||||
(else *unspecified*))))
|
||||
|
||||
(define (expect-string port expected return)
|
||||
(let loop ((n 0))
|
||||
(cond
|
||||
;; All characters match.
|
||||
((= n (string-length expected)) return)
|
||||
;; Go to next characters.
|
||||
((eqv? (read-char port) (string-ref expected n))
|
||||
(loop (+ n 1)))
|
||||
;; Anything else is an error.
|
||||
(else (json-exception port)))))
|
||||
|
||||
(define (expect-delimiter port delimiter)
|
||||
(let ((ch (read-char port)))
|
||||
(cond
|
||||
((not (eqv? ch delimiter)) (json-exception port))
|
||||
;; Unexpected EOF.
|
||||
((eof-object? ch) (json-exception port)))))
|
||||
|
||||
(define (skip-record-separators port)
|
||||
(when (eqv? #\rs (peek-char port))
|
||||
(read-char port)
|
||||
(skip-record-separators port)))
|
||||
|
||||
;;
|
||||
;; Number parsing helpers
|
||||
;;
|
||||
|
||||
(define (expect-digit port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
((not (digit? ch)) (json-exception port))
|
||||
;; Unexpected EOF.
|
||||
((eof-object? ch) (json-exception port)))))
|
||||
|
||||
;; Read + or -, and return 1 or -1 respectively. If something different is
|
||||
;; found, return 1.
|
||||
(define (read-sign port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
((eqv? ch #\+)
|
||||
(read-char port)
|
||||
1)
|
||||
((eqv? ch #\-)
|
||||
(read-char port)
|
||||
-1)
|
||||
(else 1))))
|
||||
|
||||
(define (read-digit-value port)
|
||||
(let ((ch (read-char port)))
|
||||
(cond
|
||||
((eqv? ch #\0) 0)
|
||||
((eqv? ch #\1) 1)
|
||||
((eqv? ch #\2) 2)
|
||||
((eqv? ch #\3) 3)
|
||||
((eqv? ch #\4) 4)
|
||||
((eqv? ch #\5) 5)
|
||||
((eqv? ch #\6) 6)
|
||||
((eqv? ch #\7) 7)
|
||||
((eqv? ch #\8) 8)
|
||||
((eqv? ch #\9) 9)
|
||||
(else (json-exception port)))))
|
||||
|
||||
;; Read digits [0..9].
|
||||
(define (read-digits port)
|
||||
(expect-digit port)
|
||||
(let loop ((ch (peek-char port)) (number 0))
|
||||
(cond
|
||||
((digit? ch)
|
||||
(let ((value (read-digit-value port)))
|
||||
(loop (peek-char port) (+ (* number 10) value))))
|
||||
(else number))))
|
||||
|
||||
(define (read-digits-fraction port)
|
||||
(expect-digit port)
|
||||
(let loop ((ch (peek-char port)) (number 0) (length 0))
|
||||
(cond
|
||||
((digit? ch)
|
||||
(let ((value (read-digit-value port)))
|
||||
(loop (peek-char port) (+ (* number 10) value) (+ length 1))))
|
||||
(else
|
||||
(/ number (expt 10 length))))))
|
||||
|
||||
(define (read-exponent port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
((or (eqv? ch #\e) (eqv? ch #\E))
|
||||
(read-char port)
|
||||
(let ((sign (read-sign port))
|
||||
(digits (read-digits port)))
|
||||
(if (<= digits 1000) ;; Some maximum exponent.
|
||||
(expt 10 (* sign digits))
|
||||
(json-exception port))))
|
||||
(else 1))))
|
||||
|
||||
(define (read-fraction port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
((eqv? ch #\.)
|
||||
(read-char port)
|
||||
(read-digits-fraction port))
|
||||
(else 0))))
|
||||
|
||||
(define (read-positive-number port)
|
||||
(let* ((number
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; Numbers that start with 0 must be a fraction.
|
||||
((eqv? ch #\0)
|
||||
(read-char port)
|
||||
0)
|
||||
;; Otherwise read more digits.
|
||||
(else (read-digits port)))))
|
||||
(fraction (read-fraction port))
|
||||
(exponent (read-exponent port))
|
||||
(result (* (+ number fraction) exponent)))
|
||||
(if (and (zero? fraction) (>= exponent 1))
|
||||
result
|
||||
(exact->inexact result))))
|
||||
|
||||
(define (json-read-number port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; Negative numbers.
|
||||
((eqv? ch #\-)
|
||||
(read-char port)
|
||||
(expect-digit port)
|
||||
(* -1 (read-positive-number port)))
|
||||
;; Positive numbers.
|
||||
((digit? ch)
|
||||
(read-positive-number port))
|
||||
;; Anything else is an error.
|
||||
(else (json-exception port)))))
|
||||
|
||||
;;
|
||||
;; Object parsing helpers
|
||||
;;
|
||||
|
||||
(define (read-pair port null ordered)
|
||||
;; Read key.
|
||||
(let ((key (json-read-string port)))
|
||||
(skip-whitespaces port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; Skip colon and read value.
|
||||
((eqv? ch #\:)
|
||||
(read-char port)
|
||||
(cons key (json-read port null ordered)))
|
||||
;; Anything other than colon is an error.
|
||||
(else (json-exception port))))))
|
||||
|
||||
(define (uniquify-keys pairs res)
|
||||
(cond ((null? pairs) res)
|
||||
((assoc (caar pairs) res)
|
||||
(uniquify-keys (cdr pairs) res))
|
||||
(else (uniquify-keys (cdr pairs) (cons (car pairs) res)))))
|
||||
|
||||
(define (json-read-object port null ordered)
|
||||
(expect-delimiter port #\{)
|
||||
(let loop ((pairs '()) (added #t))
|
||||
(skip-whitespaces port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; End of object.
|
||||
((eqv? ch #\})
|
||||
(read-char port)
|
||||
(cond
|
||||
(added (if ordered
|
||||
(uniquify-keys pairs '())
|
||||
(reverse! (uniquify-keys pairs '()))))
|
||||
(else (json-exception port))))
|
||||
;; Read one pair and continue.
|
||||
((eqv? ch #\")
|
||||
(let ((pair (read-pair port null ordered)))
|
||||
(loop (cons pair pairs) #t)))
|
||||
;; Skip comma and read more pairs.
|
||||
((eqv? ch #\,)
|
||||
(read-char port)
|
||||
(cond
|
||||
(added (loop pairs #f))
|
||||
(else (json-exception port))))
|
||||
;; Invalid object.
|
||||
(else (json-exception port))))))
|
||||
|
||||
;;
|
||||
;; Array parsing helpers
|
||||
;;
|
||||
|
||||
(define (json-read-array port null ordered)
|
||||
(expect-delimiter port #\[)
|
||||
(skip-whitespaces port)
|
||||
(cond
|
||||
;; Special case when array is empty.
|
||||
((eqv? (peek-char port) #\])
|
||||
(read-char port)
|
||||
#())
|
||||
(else
|
||||
;; Read first element in array.
|
||||
(let loop ((values (list (json-read port null ordered))))
|
||||
(skip-whitespaces port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; Unexpected EOF.
|
||||
((eof-object? ch) (json-exception port))
|
||||
;; Handle comma (if there's a comma there should be another element).
|
||||
((eqv? ch #\,)
|
||||
(read-char port)
|
||||
(loop (cons (json-read port null ordered) values)))
|
||||
;; End of array.
|
||||
((eqv? ch #\])
|
||||
(read-char port)
|
||||
(list->vector (reverse! values)))
|
||||
;; Anything else other than comma and end of array is wrong.
|
||||
(else (json-exception port))))))))
|
||||
|
||||
;;
|
||||
;; String parsing helpers
|
||||
;;
|
||||
|
||||
(define (read-hex-digit->integer port)
|
||||
(let ((ch (read-char port)))
|
||||
(cond
|
||||
((eqv? ch #\0) 0)
|
||||
((eqv? ch #\1) 1)
|
||||
((eqv? ch #\2) 2)
|
||||
((eqv? ch #\3) 3)
|
||||
((eqv? ch #\4) 4)
|
||||
((eqv? ch #\5) 5)
|
||||
((eqv? ch #\6) 6)
|
||||
((eqv? ch #\7) 7)
|
||||
((eqv? ch #\8) 8)
|
||||
((eqv? ch #\9) 9)
|
||||
((or (eqv? ch #\A) (eqv? ch #\a)) 10)
|
||||
((or (eqv? ch #\B) (eqv? ch #\b)) 11)
|
||||
((or (eqv? ch #\C) (eqv? ch #\c)) 12)
|
||||
((or (eqv? ch #\D) (eqv? ch #\d)) 13)
|
||||
((or (eqv? ch #\E) (eqv? ch #\e)) 14)
|
||||
((or (eqv? ch #\F) (eqv? ch #\f)) 15)
|
||||
(else (json-exception port)))))
|
||||
|
||||
(define (read-unicode-value port)
|
||||
(+ (* 4096 (read-hex-digit->integer port))
|
||||
(* 256 (read-hex-digit->integer port))
|
||||
(* 16 (read-hex-digit->integer port))
|
||||
(read-hex-digit->integer port)))
|
||||
|
||||
;; Unicode codepoint with surrogates is:
|
||||
;; 10000 + (high - D800) + (low - DC00)
|
||||
;; which is equivalent to:
|
||||
;; (high << 10) + low - 35FDC00
|
||||
;; see
|
||||
;; https://github.com/aconchillo/guile-json/issues/58#issuecomment-662744070
|
||||
(define (json-surrogate-pair->unicode high low)
|
||||
(+ (* high #x400) low #x-35FDC00))
|
||||
|
||||
(define (read-unicode-char port)
|
||||
(let ((codepoint (read-unicode-value port)))
|
||||
(cond
|
||||
;; Surrogate pairs. `codepoint` already contains the higher surrogate
|
||||
;; (between D800 and DC00) . At this point we are expecting another
|
||||
;; \uXXXX that holds the lower surrogate (between DC00 and DFFF).
|
||||
((and (>= codepoint #xD800) (< codepoint #xDC00))
|
||||
(expect-string port "\\u" #f)
|
||||
(let ((low-surrogate (read-unicode-value port)))
|
||||
(if (and (>= low-surrogate #xDC00) (< low-surrogate #xE000))
|
||||
(integer->char (json-surrogate-pair->unicode codepoint low-surrogate))
|
||||
(json-exception port))))
|
||||
;; Reserved for surrogates (we just need to check starting from the low
|
||||
;; surrogates).
|
||||
((and (>= codepoint #xDC00) (< codepoint #xE000))
|
||||
(json-exception port))
|
||||
(else (integer->char codepoint)))))
|
||||
|
||||
(define (read-control-char port)
|
||||
(let ((ch (read-char port)))
|
||||
(cond
|
||||
((eqv? ch #\") #\")
|
||||
((eqv? ch #\\) #\\)
|
||||
((eqv? ch #\/) #\/)
|
||||
((eqv? ch #\b) #\bs)
|
||||
((eqv? ch #\f) #\ff)
|
||||
((eqv? ch #\n) #\lf)
|
||||
((eqv? ch #\r) #\cr)
|
||||
((eqv? ch #\t) #\ht)
|
||||
((eqv? ch #\u) (read-unicode-char port))
|
||||
(else (json-exception port)))))
|
||||
|
||||
(define (json-read-string port)
|
||||
(expect-delimiter port #\")
|
||||
(let loop ((chars '()) (ch (read-char port)))
|
||||
(cond
|
||||
;; Unexpected EOF.
|
||||
((eof-object? ch) (json-exception port))
|
||||
;; Unescaped control characters are not allowed.
|
||||
((control-char? ch) (json-exception port))
|
||||
;; End of string.
|
||||
((eqv? ch #\") (reverse-list->string chars))
|
||||
;; Escaped characters.
|
||||
((eqv? ch #\\)
|
||||
(loop (cons (read-control-char port) chars) (read-char port)))
|
||||
;; All other characters.
|
||||
(else
|
||||
(loop (cons ch chars) (read-char port))))))
|
||||
|
||||
;;
|
||||
;; Booleans and null parsing helpers
|
||||
;;
|
||||
|
||||
(define (json-read-true port)
|
||||
(expect-string port "true" #t))
|
||||
|
||||
(define (json-read-false port)
|
||||
(expect-string port "false" #f))
|
||||
|
||||
(define (json-read-null port null)
|
||||
(expect-string port "null" null))
|
||||
|
||||
;;
|
||||
;; Main parser functions
|
||||
;;
|
||||
|
||||
(define (json-read port null ordered)
|
||||
(skip-whitespaces port)
|
||||
(let ((ch (peek-char port)))
|
||||
(cond
|
||||
;; Unexpected EOF.
|
||||
((eof-object? ch) (json-exception port))
|
||||
;; Read JSON values.
|
||||
((eqv? ch #\t) (json-read-true port))
|
||||
((eqv? ch #\f) (json-read-false port))
|
||||
((eqv? ch #\n) (json-read-null port null))
|
||||
((eqv? ch #\{) (json-read-object port null ordered))
|
||||
((eqv? ch #\[) (json-read-array port null ordered))
|
||||
((eqv? ch #\") (json-read-string port))
|
||||
;; Anything else should be a number.
|
||||
(else (json-read-number port)))))
|
||||
|
||||
;;
|
||||
;; Public procedures
|
||||
;;
|
||||
|
||||
(define* (json->scm #:optional (port (current-input-port))
|
||||
#:key (null 'null) (ordered #f) (concatenated #f))
|
||||
"Parse a JSON document into native. Takes one optional argument,
|
||||
@var{port}, which defaults to the current input port from where the JSON
|
||||
document is read. It also takes a few of keyword arguments: @{null}: value for
|
||||
JSON's null, it defaults to the 'null symbol, @{ordered} to indicate whether
|
||||
JSON objects order should be preserved or not (the default) and @{concatenated}
|
||||
which can be used to tell the parser that more JSON documents might come after a
|
||||
properly parsed document."
|
||||
(let loop ((value (json-read port null ordered)))
|
||||
;; Skip any trailing whitespaces.
|
||||
(skip-whitespaces port)
|
||||
(cond
|
||||
;; If we reach the end the parsing succeeded.
|
||||
((eof-object? (peek-char port)) value)
|
||||
;; If there's anything else other than the end, check if user wants to keep
|
||||
;; parsing concatenated valid JSON documents, otherwise parser fails.
|
||||
(else
|
||||
(cond (concatenated value)
|
||||
(else (json-exception port)))))))
|
||||
|
||||
(define* (json-string->scm str #:key (null 'null) (ordered #f))
|
||||
"Parse a JSON document into native. Takes a string argument,
|
||||
@var{str}, that contains the JSON document. It also takes a couple of keyword
|
||||
argument: @{null}: value for JSON's null, it defaults to the 'null symbol and
|
||||
@{ordered} to indicate whether JSON objects order should be preserved or
|
||||
not (the default)."
|
||||
(call-with-input-string str (lambda (p) (json->scm p #:null null #:ordered ordered))))
|
||||
|
||||
(define* (json-seq->scm #:optional (port (current-input-port))
|
||||
#:key (null 'null) (ordered #f)
|
||||
(handle-truncate 'skip) (truncated-object 'truncated))
|
||||
"Lazy parse a JSON text sequence from the port @var{port}.
|
||||
This procedure returns a stream of parsed documents. The optional argument
|
||||
@var{port} defines the port to read from and defaults to the current input
|
||||
port. It also takes a few keyword arguments: @{null}: value for JSON's null
|
||||
(defaults to the 'null symbol), @{ordered} to indicate whether JSON objects
|
||||
order should be preserved or not (the default), @{handle-truncate}: defines how
|
||||
to handle data loss, @{truncated-object}: used to replace unparsable
|
||||
objects. Allowed values for @{handle-truncate} argument are 'throw (throw an
|
||||
exception), 'stop (stop parsing and end the stream), 'skip (default, skip
|
||||
corrupted fragment and return the next entry), 'replace (skip corrupted fragment
|
||||
and return @{truncated-object} instead)."
|
||||
(letrec ((handle-truncation
|
||||
(case handle-truncate
|
||||
((throw) json-exception)
|
||||
((stop) (const (eof-object)))
|
||||
((skip)
|
||||
(lambda (port)
|
||||
(read-delimited "\x1e" port 'peek)
|
||||
(read-entry port)))
|
||||
((replace)
|
||||
(lambda (port)
|
||||
(read-delimited "\x1e" port 'peek)
|
||||
truncated-object))))
|
||||
(read-entry
|
||||
(lambda (port)
|
||||
(let ((ch (read-char port)))
|
||||
(cond
|
||||
((eof-object? ch) ch)
|
||||
((not (eqv? ch #\rs))
|
||||
(handle-truncation port))
|
||||
(else
|
||||
(skip-record-separators port)
|
||||
(catch 'json-invalid
|
||||
(lambda ()
|
||||
(let ((next (json-read port null ordered)))
|
||||
(if (eqv? #\lf (peek-char port))
|
||||
(begin
|
||||
(read-char port)
|
||||
next)
|
||||
(handle-truncation port))))
|
||||
(lambda (_ port)
|
||||
(handle-truncation port)))))))))
|
||||
(port->stream port read-entry)))
|
||||
|
||||
(define* (json-seq-string->scm str #:key (null 'null) (ordered #f)
|
||||
(handle-truncate 'skip) (truncated-object 'truncated))
|
||||
"Lazy parse a JSON text sequence from the string @var{str}.
|
||||
This procedure returns a stream of parsed documents and also takes the same
|
||||
keyword arguments as @code{json-seq->scm}."
|
||||
(call-with-input-string str
|
||||
(lambda (p)
|
||||
(json-seq->scm p #:null null #:ordered ordered
|
||||
#:handle-truncate handle-truncate
|
||||
#:truncated-object truncated-object))))
|
||||
|
||||
;;; (json parser) ends here
|
230
sjson/record.scm
Normal file
230
sjson/record.scm
Normal file
|
@ -0,0 +1,230 @@
|
|||
;;; (json record) --- Guile JSON implementation.
|
||||
|
||||
;; Copyright (C) 2020-2021 Aleix Conchillo Flaque <aconchillo@gmail.com>
|
||||
;; Copyright (C) 2018, 2019 Ludovic Courtès <ludo@gnu.org>
|
||||
;;
|
||||
;; This file is part of guile-json.
|
||||
;;
|
||||
;; guile-json is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; guile-json is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with guile-json. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; JSON module for Guile
|
||||
|
||||
;; The initial code of this file was copied from GNU Guix:
|
||||
;; http://git.savannah.gnu.org/cgit/guix.git/tree/guix/json.scm
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-module (sjson record)
|
||||
#:use-module (sjson builder)
|
||||
#:use-module (sjson parser)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:export (<=> define-json-mapping define-json-type))
|
||||
|
||||
(define <=> '<=>)
|
||||
|
||||
(define-syntax-rule (define-json-reader json->record ctor spec ...)
|
||||
"Define JSON->RECORD as a procedure that converts a JSON representation,
|
||||
read from a port, string, or alist, into a record created by CTOR and following
|
||||
SPEC, a series of field specifications."
|
||||
(define (json->record input)
|
||||
(let ((table (cond ((port? input)
|
||||
(json->scm input))
|
||||
((string? input)
|
||||
(json-string->scm input))
|
||||
;; This allows to pass native values.
|
||||
((or (null? input) (pair? input))
|
||||
input))))
|
||||
(let-syntax ((extract-field (syntax-rules ()
|
||||
((_ table (field key scm->value value->scm))
|
||||
(scm->value (if (and (pair? (assoc key table)) (not (equal? 'null (cdr (assoc key table)))))
|
||||
(cdr (assoc key table)) *unspecified*)))
|
||||
((_ table (field key scm->value))
|
||||
(scm->value (if (and (pair? (assoc key table)) (not (equal? 'null (cdr (assoc key table)))))
|
||||
(cdr (assoc key table)) *unspecified*)))
|
||||
((_ table (field key))
|
||||
(if (and (pair? (assoc key table)) (not (equal? 'null (cdr (assoc key table)))))
|
||||
(cdr (assoc key table)) *unspecified*))
|
||||
((_ table (field))
|
||||
(if (pair? (assoc (symbol->string 'field) table)) (cdr (assoc (symbol->string 'field) table)) *unspecified*)))))
|
||||
(ctor (extract-field table spec) ...)))))
|
||||
|
||||
(define-syntax-rule (define-json-writer record->json spec ...)
|
||||
"Define RECORD->JSON as a procedure that converts a RECORD into its JSON
|
||||
representation following SPEC, a series of field specifications."
|
||||
(define (record->json record)
|
||||
(let-syntax ((extract-field (syntax-rules ()
|
||||
((_ (field getter key scm->value value->scm))
|
||||
(cons key (if (unspecified? (getter record)) *unspecified* (value->scm (getter record)))))
|
||||
((_ (field getter key scm->value))
|
||||
(cons key (getter record)))
|
||||
((_ (field getter key))
|
||||
(cons key (getter record)))
|
||||
((_ (field getter))
|
||||
(cons (symbol->string 'field) (getter record))))))
|
||||
(let* ((full-object `(,(extract-field spec) ...))
|
||||
(object (filter (lambda (p) (not (unspecified? (cdr p))))
|
||||
full-object)))
|
||||
(scm->json-string object)))))
|
||||
|
||||
(define-syntax-rule (define-native-reader scm->record ctor spec ...)
|
||||
"Define SCM->RECORD as a procedure that converts an alist into a record
|
||||
created by CTOR and following SPEC, a series of field specifications."
|
||||
(define (scm->record table)
|
||||
(let-syntax ((extract-field (syntax-rules ()
|
||||
((_ table (field key scm->value value->scm))
|
||||
(scm->value (if (pair? (assoc key table)) (cdr (assoc key table)) *unspecified*)))
|
||||
((_ table (field key scm->value))
|
||||
(scm->value (if (pair? (assoc key table)) (cdr (assoc key table)) *unspecified*)))
|
||||
((_ table (field key))
|
||||
(if (pair? (assoc key table)) (cdr (assoc key table)) *unspecified*))
|
||||
((_ table (field))
|
||||
(if (pair? (assoc (symbol->string 'field) table)) (cdr (assoc (symbol->string 'field) table)) *unspecified*)))))
|
||||
(ctor (extract-field table spec) ...))))
|
||||
|
||||
(define-syntax-rule (define-native-writer record->scm spec ...)
|
||||
"Define RECORD->SCM as a procedure that converts a RECORD into it an alist
|
||||
representation following SPEC, a series of field specifications."
|
||||
(define (record->scm record)
|
||||
(let-syntax ((extract-field (syntax-rules ()
|
||||
((_ (field getter key scm->value value->scm))
|
||||
(cons key (if (unspecified? (getter record)) *unspecified* (value->scm (getter record)))))
|
||||
((_ (field getter key scm->value))
|
||||
(cons key (getter record)))
|
||||
((_ (field getter key))
|
||||
(cons key (getter record)))
|
||||
((_ (field getter))
|
||||
(cons (symbol->string 'field) (getter record))))))
|
||||
(let ((full-object `(,(extract-field spec) ...)))
|
||||
(filter (lambda (p) (not (unspecified? (cdr p)))) full-object)))))
|
||||
|
||||
(define-syntax define-json-mapping
|
||||
(syntax-rules (<=>)
|
||||
"Define RTD as a record type with the given FIELDs and GETTERs, à la SRFI-9,
|
||||
and define JSON->RECORD as a conversion from JSON (from a port, string or alist)
|
||||
to a record of this type. Optionally, define RECORD->JSON as a conversion from a
|
||||
record of this type to a JSON string. Additionally, define SCM->RECORD as a
|
||||
conversion from an alist to a record of this type (equivalent to JSON->RECORD
|
||||
when passing an alist) and RECORD->SCM as a conversion from a record of this
|
||||
type to an alist."
|
||||
((_ rtd ctor pred json->record (field getter spec ...) ...)
|
||||
(begin
|
||||
(define-record-type rtd
|
||||
(ctor field ...)
|
||||
pred
|
||||
(field getter) ...)
|
||||
|
||||
(define-json-reader json->record ctor
|
||||
(field spec ...) ...)))
|
||||
((_ rtd ctor pred json->record <=> record->json (field getter spec ...) ...)
|
||||
(begin
|
||||
(define-record-type rtd
|
||||
(ctor field ...)
|
||||
pred
|
||||
(field getter) ...)
|
||||
|
||||
(define-json-reader json->record ctor
|
||||
(field spec ...) ...)
|
||||
|
||||
(define-json-writer record->json
|
||||
(field getter spec ...) ...)))
|
||||
((_ rtd ctor pred json->record <=> record->json <=> scm->record <=> record->scm (field getter spec ...) ...)
|
||||
(begin
|
||||
(define-record-type rtd
|
||||
(ctor field ...)
|
||||
pred
|
||||
(field getter) ...)
|
||||
|
||||
(define-json-reader json->record ctor
|
||||
(field spec ...) ...)
|
||||
|
||||
(define-json-writer record->json
|
||||
(field getter spec ...) ...)
|
||||
|
||||
(define-native-reader scm->record ctor
|
||||
(field spec ...) ...)
|
||||
|
||||
(define-native-writer record->scm
|
||||
(field getter spec ...) ...)))))
|
||||
|
||||
(define-syntax define-json-type
|
||||
(lambda (x)
|
||||
"Define RTD as a record type with the given FIELDs. This will automatically
|
||||
define a record and its constructor, predicate and fields with their getters as
|
||||
they would be defined by define-json-mapping."
|
||||
(define (gen-id template-id . args)
|
||||
(datum->syntax
|
||||
template-id
|
||||
(string->symbol
|
||||
(apply string-append
|
||||
(map (lambda (x)
|
||||
(if (string? x) x (symbol->string (syntax->datum x))))
|
||||
args)))))
|
||||
(define (cleanup-single-rtd template-id)
|
||||
(datum->syntax
|
||||
template-id
|
||||
(string->symbol
|
||||
(string-delete
|
||||
(lambda (c) (or (eq? c #\<) (eq? c #\>)))
|
||||
(symbol->string (syntax->datum template-id))))))
|
||||
(define (cleanup-vector-rtd template-id)
|
||||
(cleanup-single-rtd (datum->syntax template-id (vector-ref (syntax->datum template-id) 0))))
|
||||
|
||||
(define (cleanup-rtd template-id)
|
||||
(if (vector? (syntax->datum template-id))
|
||||
(cleanup-vector-rtd template-id)
|
||||
(cleanup-single-rtd template-id)))
|
||||
(syntax-case x (<=>)
|
||||
((_ rtd field ...)
|
||||
(with-syntax ((mapping-rtd #'rtd)
|
||||
(constructor (gen-id #'rtd "make-" (cleanup-rtd #'rtd)))
|
||||
(predicate (gen-id #'rtd (cleanup-rtd #'rtd) "?"))
|
||||
(json->record (gen-id #'rtd "json->" (cleanup-rtd #'rtd)))
|
||||
(record->json (gen-id #'rtd (cleanup-rtd #'rtd) "->json"))
|
||||
(scm->record (gen-id #'rtd "scm->" (cleanup-rtd #'rtd)))
|
||||
(record->scm (gen-id #'rtd (cleanup-rtd #'rtd) "->scm"))
|
||||
((fields ...)
|
||||
(map
|
||||
(lambda (f)
|
||||
(syntax-case f ()
|
||||
((name)
|
||||
#`(name #,(gen-id #'rtd (cleanup-rtd #'rtd) "-" #'name)))
|
||||
((name key)
|
||||
#`(name #,(gen-id #'rtd (cleanup-rtd #'rtd) "-" #'name) key))
|
||||
((name key field-rtd)
|
||||
#`(name
|
||||
#,(gen-id #'rtd (cleanup-rtd #'rtd) "-" #'name)
|
||||
key
|
||||
#,(if (vector? (syntax->datum #'field-rtd))
|
||||
#`(lambda (v) (if (unspecified? v)
|
||||
*unspecified*
|
||||
(map #,(gen-id #'field-rtd "scm->" (cleanup-rtd #'field-rtd))
|
||||
(vector->list v))))
|
||||
#`(lambda (v) (if (unspecified? v)
|
||||
*unspecified*
|
||||
(#,(gen-id #'field-rtd "scm->" (cleanup-rtd #'field-rtd)) v))))
|
||||
#,(if (vector? (syntax->datum #'field-rtd))
|
||||
#`(lambda (v)
|
||||
(list->vector
|
||||
(map #,(gen-id #'field-rtd (cleanup-rtd #'field-rtd) "->scm") v)))
|
||||
(gen-id #'field-rtd (cleanup-rtd #'field-rtd) "->scm" ))))))
|
||||
#'(field ...))))
|
||||
#'(define-json-mapping mapping-rtd
|
||||
constructor
|
||||
predicate
|
||||
json->record <=> record->json <=> scm->record <=> record->scm
|
||||
fields ...))))))
|
||||
|
||||
;;; (json record) ends here
|
201
swayipc/connection.scm
Executable file
201
swayipc/connection.scm
Executable file
|
@ -0,0 +1,201 @@
|
|||
(define-module (swayipc connection)
|
||||
#:use-module (ice-9 popen)
|
||||
#:use-module (ice-9 binary-ports)
|
||||
#:use-module (ice-9 hash-table)
|
||||
#:use-module (rnrs bytevectors)
|
||||
#:use-module (rnrs io ports)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (srfi srfi-18)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:use-module (srfi srfi-1)
|
||||
|
||||
#:export (RUN-COMMMAND-MSG-ID
|
||||
GET-WORKSPACES-MSG-ID
|
||||
SUBSCRIBE-MSG-ID
|
||||
GET-OUTPUTS-MSG-ID
|
||||
GET-TREE-MSG-ID
|
||||
GET-MARKS-MSG-ID
|
||||
GET-BAR-CONFIG-MSG-ID
|
||||
GET-VERSION-MSG-ID
|
||||
GET-BINDING-MODES-MSG-ID
|
||||
GET-CONFIG-MSG-ID
|
||||
SEND-TICK-MSG-ID
|
||||
SYNC-MSG-ID
|
||||
GET-BINDING-STATE-MSG-ID
|
||||
GET-INPUTS-MSG-ID
|
||||
GET-SEATS-MSG-ID
|
||||
|
||||
WORKSPACE-EVENT-REPLY
|
||||
OUTPUT-EVENT-REPLY
|
||||
MODE-EVENT-REPLY
|
||||
WINDOW-EVENT-REPLY
|
||||
BAR-CONFIG-UPDATE-EVENT-REPLY
|
||||
BINDING-EVENT-REPLY
|
||||
SHUTDOWN-EVENT-REPLY
|
||||
TICK-EVENT-REPLY
|
||||
BAR-STATE-UPDATE-EVENT-REPLY
|
||||
INPUT-EVENT-REPLY
|
||||
|
||||
SOCKET-PATH
|
||||
COMMAND-SOCKET
|
||||
LISTENER-SOCKET
|
||||
LISTENER-THREAD
|
||||
MSG-MAGIC
|
||||
MSG-MAGIC-BV
|
||||
start-event-listener-thread
|
||||
start-event-listener
|
||||
data-received-hook
|
||||
command-received-hook
|
||||
|
||||
SOCKET-COMMANDS-LISTENER-PATH
|
||||
COMMANDS-LISTENER-SOCKET
|
||||
COMMANDS-LISTENER-THREAD
|
||||
start-commands-listener-thread
|
||||
start-commands-listener
|
||||
|
||||
write-msg
|
||||
read-msg
|
||||
encode-msg))
|
||||
|
||||
;; sway messages and replies types
|
||||
;; man: sway-ipc(7): MESSAGES AND REPLIES
|
||||
(define RUN-COMMMAND-MSG-ID 0)
|
||||
(define GET-WORKSPACES-MSG-ID 1)
|
||||
(define SUBSCRIBE-MSG-ID 2)
|
||||
(define GET-OUTPUTS-MSG-ID 3)
|
||||
(define GET-TREE-MSG-ID 4)
|
||||
(define GET-MARKS-MSG-ID 5)
|
||||
(define GET-BAR-CONFIG-MSG-ID 6)
|
||||
(define GET-VERSION-MSG-ID 7)
|
||||
(define GET-BINDING-MODES-MSG-ID 8)
|
||||
(define GET-CONFIG-MSG-ID 9)
|
||||
(define SEND-TICK-MSG-ID 10)
|
||||
(define SYNC-MSG-ID 11)
|
||||
(define GET-BINDING-STATE-MSG-ID 12)
|
||||
(define GET-INPUTS-MSG-ID 100)
|
||||
(define GET-SEATS-MSG-ID 101)
|
||||
|
||||
(define WORKSPACE-EVENT-REPLY 2147483648)
|
||||
(define OUTPUT-EVENT-REPLY 2147483649)
|
||||
(define MODE-EVENT-REPLY 2147483650)
|
||||
(define WINDOW-EVENT-REPLY 2147483651)
|
||||
(define BAR-CONFIG-UPDATE-EVENT-REPLY 2147483652)
|
||||
(define BINDING-EVENT-REPLY 2147483653)
|
||||
(define SHUTDOWN-EVENT-REPLY 2147483654)
|
||||
(define TICK-EVENT-REPLY 2147483655)
|
||||
(define BAR-STATE-UPDATE-EVENT-REPLY 2147483656)
|
||||
(define INPUT-EVENT-REPLY 2147483657)
|
||||
|
||||
(define LISTENER-THREAD #:f)
|
||||
(define COMMANDS-LISTENER-THREAD #:f)
|
||||
(define MSG-MAGIC "i3-ipc")
|
||||
(define MSG-MAGIC-BV (string->utf8 MSG-MAGIC))
|
||||
|
||||
;; TODO: maybe also get from sway and i3 binaries
|
||||
(define SOCKET-PATH
|
||||
(and (getenv "SWAYSOCK")
|
||||
(getenv "I3SOCK")))
|
||||
|
||||
(define SOCKET-COMMANDS-LISTENER-PATH
|
||||
(string-append (dirname SOCKET-PATH) "/sway-commands-ipc.sock"))
|
||||
|
||||
(define COMMAND-SOCKET (socket AF_UNIX SOCK_STREAM 0))
|
||||
(connect COMMAND-SOCKET (make-socket-address AF_UNIX SOCKET-PATH))
|
||||
(define LISTENER-SOCKET (socket AF_UNIX SOCK_STREAM 0))
|
||||
(connect LISTENER-SOCKET (make-socket-address AF_UNIX SOCKET-PATH))
|
||||
(define COMMANDS-LISTENER-SOCKET (socket AF_UNIX SOCK_STREAM 0))
|
||||
|
||||
;; <magic-string> is i3-ipc, for compatibility with i3
|
||||
;; <payload-length> is a 32-bit integer in native byte order
|
||||
;; <payload-type> is a 32-bit integer in native byte order
|
||||
(define (encode-msg command-id payload)
|
||||
(let* ((bv (make-bytevector (+ 14 (string-length payload)))))
|
||||
;; <magic-string> <payload-length> <payload-type> <payload>
|
||||
(bytevector-copy! (string->utf8 "i3-ipc") 0 bv 0 6)
|
||||
(bytevector-u32-set! bv 6 (string-length payload) (native-endianness))
|
||||
(bytevector-u32-set! bv 10 command-id (native-endianness))
|
||||
|
||||
;; payload is optional
|
||||
(when (> (string-length payload) 0)
|
||||
(bytevector-copy! (string->utf8 payload) 0 bv 14 (string-length payload)))
|
||||
bv))
|
||||
|
||||
(define (write-msg sock command-id payload)
|
||||
(put-bytevector sock (encode-msg command-id payload)))
|
||||
|
||||
;; Mutex for synchronization
|
||||
(define mutex-table (make-hash-table))
|
||||
|
||||
(define (read-msg sock)
|
||||
(let* ((mutex (if (hash-get-handle mutex-table (fileno sock))
|
||||
(cdr (hash-get-handle mutex-table (fileno sock)))
|
||||
(hash-set! mutex-table (fileno sock) (make-mutex)))))
|
||||
(mutex-lock! mutex)
|
||||
(let* ((bv-header (get-bytevector-n sock 14))
|
||||
(payload-length (bytevector-u32-ref bv-header 6 (native-endianness)))
|
||||
(command-id (bytevector-u32-ref bv-header 10 (native-endianness)))
|
||||
(payload (utf8->string (get-bytevector-n sock payload-length))))
|
||||
(mutex-unlock! mutex)
|
||||
(list command-id (or payload "")))))
|
||||
|
||||
(define (read-from-socket sock)
|
||||
(let loop ()
|
||||
(let ((data (read-msg sock)))
|
||||
(run-hook data-received-hook
|
||||
(list-ref data 0)
|
||||
(list-ref data 1))
|
||||
(loop))))
|
||||
|
||||
(define data-received-hook
|
||||
;; data received: emitted on new data received via ipc.
|
||||
|
||||
;; Parameters:
|
||||
;; - arg1: command-id.
|
||||
;; - arg2: payload.
|
||||
(make-hook 2))
|
||||
|
||||
(define command-received-hook
|
||||
;; data received: emitted on new command received via ipc.
|
||||
|
||||
;; Parameters:
|
||||
;; - arg1: command-id.
|
||||
;; - arg2: payload.
|
||||
(make-hook 2))
|
||||
|
||||
(define (handle-client client)
|
||||
(let ((port (car client)))
|
||||
(let ((data (read-msg port)))
|
||||
(run-hook command-received-hook
|
||||
(list-ref data 0)
|
||||
(list-ref data 1)))
|
||||
|
||||
;; Close the connection
|
||||
(close-port port)))
|
||||
|
||||
(define (custom-exception-handler exc)
|
||||
(display "An error occurred while handling client connection\n"))
|
||||
|
||||
(define (start-server-socket sock)
|
||||
(listen sock 15)
|
||||
(let loop ()
|
||||
(let ((client (accept sock)))
|
||||
(handle-client client)
|
||||
(loop))))
|
||||
|
||||
(define (start-event-listener)
|
||||
(read-from-socket LISTENER-SOCKET))
|
||||
|
||||
(define (start-event-listener-thread)
|
||||
(set! LISTENER-THREAD (make-thread start-event-listener))
|
||||
(thread-start! LISTENER-THREAD))
|
||||
|
||||
(define (start-commands-listener)
|
||||
(when (file-exists? SOCKET-COMMANDS-LISTENER-PATH)
|
||||
(delete-file SOCKET-COMMANDS-LISTENER-PATH))
|
||||
|
||||
(bind COMMANDS-LISTENER-SOCKET (make-socket-address AF_UNIX SOCKET-COMMANDS-LISTENER-PATH))
|
||||
(start-server-socket COMMANDS-LISTENER-SOCKET))
|
||||
|
||||
(define (start-commands-listener-thread)
|
||||
(set! COMMANDS-LISTENER-THREAD (make-thread start-commands-listener))
|
||||
(thread-start! COMMANDS-LISTENER-THREAD))
|
1495
swayipc/dispatcher.scm
Normal file
1495
swayipc/dispatcher.scm
Normal file
File diff suppressed because it is too large
Load diff
173
swayipc/events.scm
Executable file
173
swayipc/events.scm
Executable file
|
@ -0,0 +1,173 @@
|
|||
(define-module (swayipc events)
|
||||
#:use-module (swayipc connection)
|
||||
#:use-module (swayipc records)
|
||||
|
||||
#:export (sway-subscribe-event
|
||||
sway-subscribe-workspace-change
|
||||
sway-subscribe-workspace-event
|
||||
sway-subscribe-output-event
|
||||
sway-subscribe-binding-mode-event
|
||||
sway-subscribe-window-event
|
||||
sway-subscribe-barconfig-update-event
|
||||
sway-subscribe-binding-event
|
||||
sway-subscribe-shutdown-event
|
||||
sway-subscribe-tick-event
|
||||
sway-subscribe-bar-state-event
|
||||
sway-subscribe-input-event
|
||||
sway-subscribe-all
|
||||
|
||||
sway-workspace-hook
|
||||
sway-output-hook
|
||||
sway-mode-hook
|
||||
sway-window-hook
|
||||
sway-bar-config-hook
|
||||
sway-shutdown-hook
|
||||
sway-tick-hook
|
||||
sway-bar-state-update-hook
|
||||
sway-input-hook))
|
||||
|
||||
(define (sway-subscribe-event event)
|
||||
"A client can subscribe to any events it wants to be notified of changes for."
|
||||
(write-msg LISTENER-SOCKET SUBSCRIBE-MSG-ID event)
|
||||
(json->sway-tick (list-ref (read-msg LISTENER-SOCKET) 1)))
|
||||
|
||||
(define (sway-subscribe-workspace-event)
|
||||
"Sent whenever an event involving a workspace occurs such as initialization
|
||||
of a new workspace or a different workspace gains focus."
|
||||
(sway-subscribe-event "['workspace']"))
|
||||
|
||||
(define (sway-subscribe-output-event)
|
||||
"Sent when outputs are updated."
|
||||
(sway-subscribe-event "['output']"))
|
||||
|
||||
(define (sway-subscribe-binding-mode-event)
|
||||
"Sent whenever the binding mode changes."
|
||||
(sway-subscribe-event "['mode']"))
|
||||
|
||||
(define (sway-subscribe-window-event)
|
||||
"Sent whenever an event involving a view occurs such as being reparented, focused, or closed."
|
||||
(sway-subscribe-event "['window']"))
|
||||
|
||||
(define (sway-subscribe-barconfig-update-event)
|
||||
"Sent whenever a bar config changes."
|
||||
(sway-subscribe-event "['barconfig_update']"))
|
||||
|
||||
(define (sway-subscribe-binding-event)
|
||||
"Sent when a configured binding is executed."
|
||||
(sway-subscribe-event "['binding']"))
|
||||
|
||||
(define (sway-subscribe-shutdown-event)
|
||||
"Sent when the ipc shuts down because sway is exiting."
|
||||
(sway-subscribe-event "['shutdown']"))
|
||||
|
||||
(define (sway-subscribe-tick-event)
|
||||
"Sent when an ipc client sends a SEND_TICK message."
|
||||
(sway-subscribe-event "['tick']"))
|
||||
|
||||
(define (sway-subscribe-bar-state-event)
|
||||
"Send when the visibility of a bar should change due to a modifier."
|
||||
(sway-subscribe-event "['bar_state_update']"))
|
||||
|
||||
(define (sway-subscribe-input-event)
|
||||
"Sent when something related to input devices changes."
|
||||
(sway-subscribe-event "['input']"))
|
||||
|
||||
(define (sway-subscribe-all)
|
||||
"subscribe to all available events."
|
||||
(sway-subscribe-event "['workspace', 'output', 'mode', 'window', 'barconfig_update',
|
||||
'binding', 'shutdown', 'tick', 'bar_state_update', 'input']"))
|
||||
|
||||
(define (custom-exception-handler exc command-id payload)
|
||||
(display "An error occurred while receiving event data\n")
|
||||
(display (string-append "command: " (number->string command-id) ", payload: " payload)))
|
||||
|
||||
(add-hook! data-received-hook
|
||||
(lambda (command-id payload)
|
||||
(with-exception-handler
|
||||
(lambda (exc)
|
||||
(custom-exception-handler exc command-id payload))
|
||||
(lambda () (handle-event command-id payload))
|
||||
#:unwind? #t)))
|
||||
|
||||
(define (handle-event command-id payload)
|
||||
(cond
|
||||
((= command-id WORKSPACE-EVENT-REPLY)
|
||||
(run-hook sway-workspace-hook (json->sway-workspace-event payload)))
|
||||
((= command-id OUTPUT-EVENT-REPLY)
|
||||
(run-hook sway-output-hook (json->sway-output-event payload)))
|
||||
((= command-id MODE-EVENT-REPLY)
|
||||
(run-hook sway-mode-hook (json->sway-mode-event payload)))
|
||||
((= command-id WINDOW-EVENT-REPLY)
|
||||
(run-hook sway-window-hook (json->sway-window-event payload)))
|
||||
((= command-id BAR-CONFIG-UPDATE-EVENT-REPLY)
|
||||
(run-hook sway-bar-config-hook (json->sway-bar-config payload)))
|
||||
((= command-id BINDING-EVENT-REPLY)
|
||||
(run-hook sway-binding-hook (json->sway-binding-event payload)))
|
||||
((= command-id SHUTDOWN-EVENT-REPLY)
|
||||
(run-hook sway-shutdown-hook (json->sway-shutdown-event payload)))
|
||||
((= command-id TICK-EVENT-REPLY)
|
||||
(run-hook sway-tick-hook (json->sway-tick-event payload)))
|
||||
((= command-id BAR-STATE-UPDATE-EVENT-REPLY)
|
||||
(run-hook sway-bar-state-update-hook (json->sway-bar-state-update-event payload)))
|
||||
((= command-id INPUT-EVENT-REPLY)
|
||||
(run-hook sway-input-hook (json->sway-input-event payload)))))
|
||||
|
||||
(define sway-workspace-hook
|
||||
;; workspace changed: emitted on workspace change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-workspace-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-output-hook
|
||||
;; output changed: emitted on output change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-output-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-mode-hook
|
||||
;; mode changed: emitted on mode change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-mode-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-window-hook
|
||||
;; window changed: emitted on window change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-window-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-binding-hook
|
||||
;; binding changed: emitted on binding change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-binding-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-bar-config-hook
|
||||
;; bar-config changed: emitted on bar-config change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-bar-config-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-shutdown-hook
|
||||
;; shutdown changed: emitted on shutdown change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-shutdown-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-tick-hook
|
||||
;; tick changed: emitted on tick change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-tick-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-bar-state-update-hook
|
||||
;; bar-state-update changed: emitted on bar-state-update change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-bar-state-update-event.
|
||||
(make-hook 1))
|
||||
|
||||
(define sway-input-hook
|
||||
;; input changed: emitted on input change.
|
||||
;; Parameters:
|
||||
;; - arg1: sway-input-event.
|
||||
(make-hook 1))
|
113
swayipc/info.scm
Executable file
113
swayipc/info.scm
Executable file
|
@ -0,0 +1,113 @@
|
|||
(define-module (swayipc info)
|
||||
#:use-module (swayipc connection)
|
||||
#:use-module (swayipc records)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (sjson parser)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:use-module (srfi srfi-1)
|
||||
|
||||
#:export (sway-get-workspaces
|
||||
sway-get-outputs
|
||||
sway-get-tree
|
||||
sway-get-marks
|
||||
sway-get-bars
|
||||
sway-get-bar-config
|
||||
sway-get-version
|
||||
sway-get-binding-modes
|
||||
sway-get-config
|
||||
sway-get-binding-state
|
||||
sway-get-inputs
|
||||
sway-get-seats))
|
||||
|
||||
(define (custom-exception-handler exc command-id payload)
|
||||
(display "An error occurred: ")
|
||||
(display (exp->string exc))
|
||||
(newline)
|
||||
(display (string-append "command: " (number->string command-id) ", payload: " payload)))
|
||||
|
||||
(define (sway-send-query message-id payload)
|
||||
"returns the ipc response from sway after sending the message-id and payload"
|
||||
(with-exception-handler
|
||||
(lambda (exc)
|
||||
(custom-exception-handler exc command-id payload))
|
||||
(lambda () (begin
|
||||
(write-msg COMMAND-SOCKET
|
||||
message-id
|
||||
payload)
|
||||
(let* ((out (read-msg COMMAND-SOCKET)))
|
||||
(list-ref out 1))))
|
||||
#:unwind? #t))
|
||||
|
||||
(define (sway-get-workspaces)
|
||||
"Retrieves the list of workspaces."
|
||||
(map
|
||||
(lambda (workspace)
|
||||
(scm->sway-workspace workspace))
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-WORKSPACES-MSG-ID "")))))
|
||||
|
||||
(define (sway-get-outputs)
|
||||
"Retrieve the list of outputs."
|
||||
(map
|
||||
(lambda (output)
|
||||
(scm->sway-output output))
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-OUTPUTS-MSG-ID "")))))
|
||||
|
||||
(define (sway-get-tree)
|
||||
"Retrieve a representation of the tree."
|
||||
(json->sway-tree (sway-send-query GET-TREE-MSG-ID "")))
|
||||
|
||||
(define (sway-get-marks)
|
||||
"Retrieve the currently set marks."
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-MARKS-MSG-ID ""))))
|
||||
|
||||
(define (sway-get-bars)
|
||||
"retrieves the list of configured bar IDs."
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-BAR-CONFIG-MSG-ID ""))))
|
||||
|
||||
(define (sway-get-bar-config bar-id)
|
||||
"retrieves the config associated with the specified by the bar ID."
|
||||
(json->sway-bar-config (sway-send-query GET-BAR-CONFIG-MSG-ID bar-id)))
|
||||
|
||||
(define (sway-get-version)
|
||||
"Retrieve version information about the sway process."
|
||||
(json->sway-version (sway-send-query GET-VERSION-MSG-ID "")))
|
||||
|
||||
(define (sway-get-binding-modes)
|
||||
"Retrieve the list of binding modes that currently configured."
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-BINDING-MODES-MSG-ID ""))))
|
||||
|
||||
(define (sway-get-config)
|
||||
"Retrieve the list of binding modes that currently configured."
|
||||
(json->sway-config (sway-send-query GET-CONFIG-MSG-ID "")))
|
||||
|
||||
(define (sway-get-binding-state)
|
||||
"Returns the currently active binding mode."
|
||||
(json->sway-binding-state (sway-send-query GET-BINDING-STATE-MSG-ID "")))
|
||||
|
||||
(define (sway-get-inputs)
|
||||
"Retrieve a list of the input devices currently available."
|
||||
(map
|
||||
(lambda (input)
|
||||
(scm->sway-input input))
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-INPUTS-MSG-ID "")))))
|
||||
|
||||
(define (sway-get-seats)
|
||||
"Retrieve a list of the seats currently configured."
|
||||
(map
|
||||
(lambda (seat)
|
||||
(scm->sway-seat seat))
|
||||
(vector->list
|
||||
(json-string->scm
|
||||
(sway-send-query GET-SEATS-MSG-ID "")))))
|
491
swayipc/records.scm
Executable file
491
swayipc/records.scm
Executable file
|
@ -0,0 +1,491 @@
|
|||
(define-module (swayipc records)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (sjson record)
|
||||
#:use-module (sjson parser)
|
||||
#:use-module (sjson builder)
|
||||
|
||||
#:export (<sway-rect>
|
||||
scm->sway-rect
|
||||
json->sway-rect
|
||||
sway-rect-x
|
||||
sway-rect-y
|
||||
sway-rect-width
|
||||
sway-rect-height
|
||||
|
||||
<sway-workspace>
|
||||
scm->sway-workspace
|
||||
json->sway-workspace
|
||||
sway-workspace-num
|
||||
sway-workspace-name
|
||||
sway-workspace-visible
|
||||
sway-workspace-focused
|
||||
sway-workspace-urgent
|
||||
sway-workspace-rect
|
||||
sway-workspace-output
|
||||
|
||||
<sway-mode>
|
||||
scm->sway-mode
|
||||
json->sway-mode
|
||||
sway-mode-width
|
||||
sway-mode-height
|
||||
sway-mode-refresh
|
||||
sway-mode-picture-aspect-ratio
|
||||
|
||||
<sway-output>
|
||||
scm->sway-output
|
||||
json->sway-output
|
||||
sway-output-name
|
||||
sway-output-make
|
||||
sway-output-model
|
||||
sway-output-serial
|
||||
sway-output-active
|
||||
sway-output-primary
|
||||
sway-output-scale
|
||||
sway-output-subpixel-hinting
|
||||
sway-output-transform
|
||||
sway-output-current-workspace
|
||||
sway-output-modes
|
||||
sway-output-current-mode
|
||||
|
||||
<sway-window-property>
|
||||
scm->sway-window-property
|
||||
json->sway-window-property
|
||||
class
|
||||
instance
|
||||
title
|
||||
transient-for
|
||||
|
||||
<sway-tree>
|
||||
scm->sway-tree
|
||||
json->sway-tree
|
||||
sway-tree-id
|
||||
sway-tree-name
|
||||
sway-tree-type
|
||||
sway-tree-border
|
||||
sway-tree-current-border-width
|
||||
sway-tree-layout
|
||||
sway-tree-orientation
|
||||
sway-tree-percent
|
||||
sway-tree-rect
|
||||
sway-tree-window-rect
|
||||
sway-tree-deco-rect
|
||||
sway-tree-geometry
|
||||
sway-tree-urgent
|
||||
sway-tree-sticky
|
||||
sway-tree-marks
|
||||
sway-tree-focused
|
||||
sway-tree-focus
|
||||
sway-tree-nodes
|
||||
sway-tree-floating-nodes
|
||||
sway-tree-representation
|
||||
sway-tree-fullscreen-mode
|
||||
sway-tree-app-id
|
||||
sway-tree-pid
|
||||
sway-tree-visible
|
||||
sway-tree-shell
|
||||
sway-tree-inhibit-idle
|
||||
sway-tree-idle-inhibitors
|
||||
sway-tree-window
|
||||
sway-tree-window-properties
|
||||
|
||||
<sway-bar-color>
|
||||
scm->sway-bar-color
|
||||
json->sway-bar-color
|
||||
sway-bar-color-background
|
||||
sway-bar-color-status-line
|
||||
sway-bar-color-separator
|
||||
sway-bar-color-focused-background
|
||||
sway-bar-color-focused-statusline
|
||||
sway-bar-color-focused-separator
|
||||
sway-bar-color-focused-workspace-text
|
||||
sway-bar-color-focused-workspace-background
|
||||
sway-bar-color-focused-workspace-border
|
||||
sway-bar-color-active-workspace-text
|
||||
sway-bar-color-active-workspace-background
|
||||
sway-bar-color-active-workspace-border
|
||||
sway-bar-color-inactive-workspace-text
|
||||
sway-bar-color-inactive-workspace-background
|
||||
sway-bar-color-inactive-workspace-border
|
||||
sway-bar-color-urgent-workspace-text
|
||||
sway-bar-color-urgent-workspace-background
|
||||
sway-bar-color-urgent-workspace-border
|
||||
sway-bar-color-binding-mode-text
|
||||
sway-bar-color-binding-mode-background
|
||||
sway-bar-color-binding-mode-border
|
||||
|
||||
<sway-bar-gap>
|
||||
scm->sway-bar-gap
|
||||
json->sway-bar-gap
|
||||
sway-bar-gap-top
|
||||
sway-bar-gap-right
|
||||
sway-bar-gap-bottom
|
||||
sway-bar-gap-left
|
||||
|
||||
<sway-bar-config>
|
||||
scm->sway-bar-config
|
||||
json->sway-bar-config
|
||||
sway-bar-config-id
|
||||
sway-bar-config-mode
|
||||
sway-bar-config-position
|
||||
sway-bar-config-status-command
|
||||
sway-bar-config-font
|
||||
sway-bar-config-workspace-buttons
|
||||
sway-bar-config-workspace-min-width
|
||||
sway-bar-config-binding-mode-indicator
|
||||
sway-bar-config-verbose
|
||||
sway-bar-config-colors
|
||||
sway-bar-config-gaps
|
||||
sway-bar-config-bar-height
|
||||
sway-bar-config-status-padding
|
||||
sway-bar-config-status-edge-padding
|
||||
|
||||
<sway-version>
|
||||
scm->sway-version
|
||||
json->sway-version
|
||||
sway-version-major
|
||||
sway-version-minor
|
||||
sway-version-patch
|
||||
sway-version-human-readable
|
||||
sway-version-loaded-config-file-name
|
||||
|
||||
<sway-config>
|
||||
scm->sway-config
|
||||
json->sway-config
|
||||
sway-config-config
|
||||
|
||||
<sway-tick>
|
||||
scm->sway-tick
|
||||
json->sway-tick
|
||||
sway-tick-success
|
||||
sway-tick-parse-error
|
||||
sway-tick-error
|
||||
|
||||
<sway-sync>
|
||||
scm->sway-sync
|
||||
json->sway-sync
|
||||
sway-sync-success
|
||||
|
||||
<sway-binding-state>
|
||||
scm->sway-binding-state
|
||||
json->sway-binding-state
|
||||
sway-binding-state-name
|
||||
|
||||
<sway-lib-input>
|
||||
scm->sway-lib-input
|
||||
json->sway-lib-input
|
||||
sway-lib-input-send-events
|
||||
sway-lib-input-tap
|
||||
sway-lib-input-tap-button-map
|
||||
sway-lib-input-tap-drag
|
||||
sway-lib-input-tap-drag-lock
|
||||
sway-lib-input-accel-speed
|
||||
sway-lib-input-accel-profile
|
||||
sway-lib-input-natural-scroll
|
||||
sway-lib-input-left-handed
|
||||
sway-lib-input-click-method
|
||||
sway-lib-input-middle-emulation
|
||||
sway-lib-input-scroll-method
|
||||
sway-lib-input-scroll-button
|
||||
sway-lib-input-scroll-button-lock
|
||||
sway-lib-input-dwt
|
||||
sway-lib-input-dwtp
|
||||
sway-lib-input-calibration-matrix
|
||||
|
||||
<sway-input>
|
||||
scm->sway-input
|
||||
json->sway-input
|
||||
sway-input-identifier
|
||||
sway-input-name
|
||||
sway-input-vendor
|
||||
sway-input-product
|
||||
sway-input-type
|
||||
sway-input-xkb-active-layout-name
|
||||
sway-input-xkb-layout-names
|
||||
sway-input-scroll-factor
|
||||
sway-input-libinput
|
||||
|
||||
<sway-seat>
|
||||
scm->sway-seat
|
||||
json->sway-seat
|
||||
sway-seat-name
|
||||
sway-seat-capabilities
|
||||
sway-seat-focus
|
||||
sway-seat-devices
|
||||
|
||||
<sway-workspace-event>
|
||||
scm->sway-workspace-event
|
||||
json->sway-workspace-event
|
||||
sway-workspace-event-change
|
||||
sway-workspace-event-old
|
||||
sway-workspace-event-current
|
||||
|
||||
<sway-output-event>
|
||||
scm->sway-output-event
|
||||
json->sway-output-event
|
||||
sway-output-event-change
|
||||
|
||||
<sway-mode-event>
|
||||
scm->sway-mode-event
|
||||
json->sway-mode-event
|
||||
sway-mode-event-change
|
||||
sway-mode-event-pango-markup
|
||||
|
||||
<sway-window-event>
|
||||
scm->sway-window-event
|
||||
json->sway-window-event
|
||||
sway-window-event-change
|
||||
sway-window-event-container
|
||||
|
||||
<sway-binding-event>
|
||||
scm->sway-binding-event
|
||||
json->sway-binding-event
|
||||
sway-binding-event-change
|
||||
sway-binding-event-binding
|
||||
|
||||
<sway-binding-event-binding>
|
||||
scm->sway-binding-event-binding
|
||||
json->sway-binding-event-binding
|
||||
sway-binding-event-binding-command
|
||||
sway-binding-event-binding-event-state-mask
|
||||
sway-binding-event-binding-input-code
|
||||
sway-binding-event-binding-sybmol
|
||||
sway-binding-event-binding-input-type
|
||||
|
||||
<sway-shutdown-event>
|
||||
scm->sway-shutdown-event
|
||||
json->sway-shutdown-event
|
||||
sway-shutdown-event-change
|
||||
|
||||
<sway-tick-event>
|
||||
scm->sway-tick-event
|
||||
json->sway-tick-event
|
||||
sway-tick-event-first
|
||||
sway-tick-event-paylaod
|
||||
|
||||
<sway-bar-state-update-event>
|
||||
scm->sway-bar-state-update-event
|
||||
json->sway-bar-state-update-event
|
||||
sway-bar-state-update-event-id
|
||||
sway-bar-state-update-event-visible-by-modifier
|
||||
|
||||
<sway-input-event>
|
||||
scm->sway-input-event
|
||||
json->sway-input-event
|
||||
sway-input-event-change
|
||||
sway-input-event-input))
|
||||
|
||||
(define-json-type <sway-rect>
|
||||
(x)
|
||||
(y)
|
||||
(width)
|
||||
(height))
|
||||
|
||||
(define-json-type <sway-workspace>
|
||||
(num)
|
||||
(name)
|
||||
(visible)
|
||||
(focused)
|
||||
(urgent)
|
||||
(rect "rect" <sway-rect>)
|
||||
(output))
|
||||
|
||||
(define-json-type <sway-mode>
|
||||
(picture-aspect-ratio "picture_aspect_ratio")
|
||||
(refresh)
|
||||
(height)
|
||||
(width))
|
||||
|
||||
(define-json-type <sway-output>
|
||||
(name)
|
||||
(make)
|
||||
(model)
|
||||
(serial)
|
||||
(active)
|
||||
(primary)
|
||||
(scale)
|
||||
(subpixel-hinting "subpixel_hinting")
|
||||
(transform)
|
||||
(current-workspace "current_workspace")
|
||||
(modes "modes" #(<sway-mode>))
|
||||
(current-mode "current_mode" <sway-mode>))
|
||||
|
||||
(define-json-type <sway-window-property>
|
||||
(class)
|
||||
(instance)
|
||||
(title)
|
||||
(transient-for "transient_for"))
|
||||
|
||||
(define-json-type <sway-tree>
|
||||
(id)
|
||||
(name)
|
||||
(type)
|
||||
(border)
|
||||
(current-border-width "current_border_width")
|
||||
(layout)
|
||||
(orientation)
|
||||
(percent)
|
||||
(rect "rect" <sway-rect>)
|
||||
(window-rect "window_rect" <sway-rect>)
|
||||
(deco-rect "deco_rect" <sway-rect>)
|
||||
(geometry "geometry" <sway-rect>)
|
||||
(urgent)
|
||||
(sticky)
|
||||
(marks)
|
||||
(focused)
|
||||
(focus)
|
||||
(nodes "nodes" #(<sway-tree>))
|
||||
(floating-nodes "floating_nodes" #(<sway-tree>))
|
||||
(representation)
|
||||
(fullscreen-mode "fullscreen_mode")
|
||||
(app-id "app_id")
|
||||
(pid)
|
||||
(visible)
|
||||
(shell)
|
||||
(inhibit-idle "inhibit_idle")
|
||||
(idle-inhibitors "idle_inhibitors")
|
||||
(window)
|
||||
(window-properties "window_properties" <sway-window-property>))
|
||||
|
||||
(define-json-type <sway-bar-color>
|
||||
(background)
|
||||
(status-line "statusline")
|
||||
(separator)
|
||||
(focused-background "focused_background")
|
||||
(focused-statusline "focused_statusline")
|
||||
(focused-separator "focused_separator")
|
||||
(focused-workspace-text "focused_workspace_text")
|
||||
(focused-workspace-background "focused_workspace_bg")
|
||||
(focused-workspace-border "focused_workspace_border")
|
||||
(active-workspace-text "active_workspace_text")
|
||||
(active-workspace-background "active_workspace_bg")
|
||||
(active-workspace-border "active_workspace_border")
|
||||
(inactive-workspace-text "inactive_workspace_text")
|
||||
(inactive-workspace-background "inactive_workspace_bg")
|
||||
(inactive-workspace-border "inactive_workspace_border")
|
||||
(urgent-workspace-text "urgent_workspace_text")
|
||||
(urgent-workspace-background "urgent_workspace_bg")
|
||||
(urgent-workspace-border "urgent_workspace_border")
|
||||
(binding-mode-text "binding_mode_text")
|
||||
(binding-mode-background "binding_mode_bg")
|
||||
(binding-mode-border "binding_mode_border"))
|
||||
|
||||
(define-json-type <sway-bar-gap>
|
||||
(top)
|
||||
(right)
|
||||
(bottom)
|
||||
(left))
|
||||
|
||||
(define-json-type <sway-bar-config>
|
||||
(id)
|
||||
(mode)
|
||||
(position)
|
||||
(status-command "status_command")
|
||||
(font)
|
||||
(workspace-buttons "workspace_buttons")
|
||||
(workspace-min-width "workspace_min_width")
|
||||
(binding-mode-indicator)
|
||||
(verbose)
|
||||
(colors "colors" <sway-bar-color>)
|
||||
(gaps "gaps" <sway-bar-gap>)
|
||||
(bar-height "bar_height")
|
||||
(status-padding "status_padding")
|
||||
(status-edge-padding "status_edge_padding"))
|
||||
|
||||
(define-json-type <sway-version>
|
||||
(major)
|
||||
(minor)
|
||||
(patch)
|
||||
(human-readable "human_readable")
|
||||
(loaded-config-file-name "loaded_config_file_name"))
|
||||
|
||||
(define-json-type <sway-config>
|
||||
(config))
|
||||
|
||||
(define-json-type <sway-tick>
|
||||
(success)
|
||||
(parse-error "parse_error")
|
||||
(error))
|
||||
|
||||
(define-json-type <sway-sync>
|
||||
(success))
|
||||
|
||||
(define-json-type <sway-binding-state>
|
||||
(name))
|
||||
|
||||
(define-json-type <sway-lib-input>
|
||||
(send-events "send_events")
|
||||
(tap)
|
||||
(tap-button-map "tap_button_map")
|
||||
(tap-drag "tap_drag")
|
||||
(tap-drag-lock "tap_drag_lock")
|
||||
(accel-speed "accel_speed")
|
||||
(accel-profile "accel_profile")
|
||||
(natural-scroll "natural_scroll")
|
||||
(left-handed "left_handed")
|
||||
(click-method "click_method")
|
||||
(middle-emulation "middle_emulation")
|
||||
(scroll-method "scroll_method")
|
||||
(scroll-button "scroll_button")
|
||||
(scroll-button-lock "scroll_button_lock")
|
||||
(dwt)
|
||||
(dwtp)
|
||||
(calibration-matrix "calibration_matrix"))
|
||||
|
||||
(define-json-type <sway-input>
|
||||
(identifier)
|
||||
(name)
|
||||
(vendor)
|
||||
(product)
|
||||
(type)
|
||||
(xkb-active-layout-name "xkb_active_layout_name")
|
||||
(xkb-layout-names "xkb_layout_names")
|
||||
(scroll-factor "scroll_factor")
|
||||
(libinput <sway-lib-input>))
|
||||
|
||||
(define-json-type <sway-seat>
|
||||
(name)
|
||||
(capabilities)
|
||||
(focus)
|
||||
(devices "devices" #(<sway-input>)))
|
||||
|
||||
(define-json-type <sway-workspace-event>
|
||||
(change)
|
||||
(old "old" <sway-tree>)
|
||||
(current "current" <sway-tree>))
|
||||
|
||||
(define-json-type <sway-output-event>
|
||||
(change))
|
||||
|
||||
(define-json-type <sway-mode-event>
|
||||
(change)
|
||||
(pango-markup "pango_markup"))
|
||||
|
||||
(define-json-type <sway-window-event>
|
||||
(change)
|
||||
(container "container" <sway-tree))
|
||||
|
||||
(define-json-type <sway-binding-event>
|
||||
(change)
|
||||
(binding "binding" <sway-binding-event-binding))
|
||||
|
||||
(define-json-type <sway-binding-event-binding>
|
||||
(command)
|
||||
(event-state-mask "event_state_mask")
|
||||
(input-code "input_code")
|
||||
(sybmol)
|
||||
(input-type "input_type"))
|
||||
|
||||
(define-json-type <sway-shutdown-event>
|
||||
(change))
|
||||
|
||||
(define-json-type <sway-tick-event>
|
||||
(first)
|
||||
(paylaod))
|
||||
|
||||
(define-json-type <sway-bar-state-update-event>
|
||||
(id)
|
||||
(visible-by-modifier "visible_by_modifier"))
|
||||
|
||||
(define-json-type <sway-input-event>
|
||||
(change)
|
||||
(input "input" <sway-lib-input>))
|
Loading…
Reference in a new issue