diff options
author | Thomas Letan <lthms@soap.coffee> | 2022-08-28 10:45:06 +0200 |
---|---|---|
committer | Thomas Letan <lthms@soap.coffee> | 2022-08-28 11:45:54 +0200 |
commit | 7b1c53f355553639a8e9e0d2e378ef309220b0cd (patch) | |
tree | f56ddc15d7027420bcd2d1b7ec4f0b97b2f74090 | |
parent | Various tweaks before removing the Lwt layer (diff) |
Remove Lwt and use Poll instead
-rw-r--r-- | bin/spatial/dune | 2 | ||||
-rw-r--r-- | bin/spatial/main.ml | 160 | ||||
-rw-r--r-- | bin/spatial/ribbon.ml | 8 | ||||
-rw-r--r-- | bin/spatial/state.ml | 150 | ||||
-rw-r--r-- | bin/spatialmsg/dune | 2 | ||||
-rw-r--r-- | bin/spatialmsg/main.ml | 4 | ||||
-rw-r--r-- | lib/mltp_ipc/dune | 2 | ||||
-rw-r--r-- | lib/mltp_ipc/socket.ml | 137 | ||||
-rw-r--r-- | lib/mltp_ipc/socket.mli | 50 | ||||
-rw-r--r-- | lib/spatial_ipc/spatial_ipc.ml | 66 | ||||
-rw-r--r-- | lib/spatial_ipc/spatial_ipc.mli | 19 | ||||
-rw-r--r-- | lib/sway_ipc/dune | 2 | ||||
-rw-r--r-- | lib/sway_ipc/sway_ipc.ml | 64 | ||||
-rw-r--r-- | lib/sway_ipc/sway_ipc.mli | 32 |
14 files changed, 316 insertions, 382 deletions
diff --git a/bin/spatial/dune b/bin/spatial/dune index 3682cc9..f0fd72b 100644 --- a/bin/spatial/dune +++ b/bin/spatial/dune @@ -1,3 +1,3 @@ (executable (name main) - (libraries spatial-sway.ipc sway-ipc lwt lwt.unix)) + (libraries poll spatial-sway.ipc sway-ipc unix)) diff --git a/bin/spatial/main.ml b/bin/spatial/main.ml index af62117..ee2efac 100644 --- a/bin/spatial/main.ml +++ b/bin/spatial/main.ml @@ -1,7 +1,5 @@ open Sway_ipc_types -type input = From_sway of Event.t | From_client of Spatial_ipc.socket - let workspace_handle (ev : Event.workspace_event) state = match ev.change with | Focus -> @@ -10,29 +8,23 @@ let workspace_handle (ev : Event.workspace_event) state = | Some workspace -> State.set_current_workspace workspace state | None -> state in - Lwt.return (state, true, None) - | Init | Empty | Move | Rename | Urgent | Reload -> - Lwt.return (state, false, None) + (state, true, None) + | Init | Empty | Move | Rename | Urgent | Reload -> (state, false, None) let window_handle (ev : Event.window_event) state = - let open Lwt.Syntax in match ev.change with | Event.New -> - let* () = - Lwt_io.printf "created window %Ld (%s)\n" ev.container.id - (Option.value ~default:"<meh>" ev.container.app_id) - in let state = State.register_window false 2 state.State.current_workspace state ev.container in - Lwt.return (state, true, None) + (state, true, None) | Event.Close -> let state = State.unregister_window state ev.container.id in - Lwt.return (state, true, None) + (state, true, None) | Event.Focus | Event.Title | Event.Fullscreen_mode | Event.Move | Event.Mark | Event.Urgent -> - Lwt.return (state, false, None) + (state, false, None) | Event.Floating -> ( match ev.container.node_type with | Con -> @@ -40,77 +32,83 @@ let window_handle (ev : Event.window_event) state = State.register_window false 2 state.State.current_workspace state ev.container in - Lwt.return (state, true, None) + (state, true, None) | Floating_con -> - let* () = - Lwt_io.printf "window %Ld (%s) turned floating\n" ev.container.id - (Option.value ~default:"<meh>" ev.container.app_id) - in let state = State.unregister_window state ev.container.id in - Lwt.return (state, true, Some ev.container.id) - | _ -> Lwt.return (state, false, None)) + (state, true, Some ev.container.id) + | _ -> (state, false, None)) -let event_handle ev state = - let open Lwt.Syntax in - Lwt.try_bind - (fun () -> - let* state, arrange, force_focus = - match ev with - | From_sway (Event.Workspace ev) -> workspace_handle ev state - | From_sway (Window ev) -> window_handle ev state - | From_sway _ -> assert false - | From_client socket -> - Lwt.try_bind - (fun () -> - let+ handle_res = - Spatial_ipc.( - handle_next_command ~socket state - { handler = State.client_command_handle }) - in - match handle_res with Some x -> x | _ -> (state, false, None)) - Lwt.return - (fun exn -> - let* () = Spatial_ipc.close socket in - raise exn) - in - let+ () = - if arrange then - let* () = State.arrange_current_workspace ?force_focus state in - (* TODO: Make this more general *) - let* _ = - Lwt_process.(exec @@ shell "/usr/bin/pkill -SIGRTMIN+8 waybar") - in - Lwt.return () - else Lwt.return () - in - state) - Lwt.return - (fun exn -> - let+ _ = - Lwt_io.printf "something went wrong with an event:\n%s\n" - (Printexc.to_string exn) - in - state) +let with_nonblock_socket socket f = + Unix.clear_nonblock socket; + let res = f () in + Unix.set_nonblock socket; + res + +let rec go poll state sway_socket server_socket = + try + match Poll.wait poll (Poll.Timeout.after 10_000_000_000_000L) with + | `Timeout -> go poll state sway_socket server_socket + | `Ok -> + let ref_state = ref state in + let ref_arrange = ref false in + let ref_force_focus = ref None in + Poll.iter_ready poll ~f:(fun fd _event -> + try + let state, arrange, force_focus = + if fd = sway_socket then + with_nonblock_socket fd @@ fun () -> + match Sway_ipc.read_event fd with + | Workspace ev -> workspace_handle ev !ref_state + | Window ev -> window_handle ev !ref_state + | _ -> assert false + else if fd = server_socket then ( + let client = Spatial_ipc.accept fd in + Unix.set_nonblock client; + Poll.(set poll client Event.read); + (!ref_state, false, None)) + else + with_nonblock_socket fd @@ fun () -> + match + Spatial_ipc.handle_next_command ~socket:fd !ref_state + { handler = State.client_command_handle } + with + | Some res -> res + | None -> (!ref_state, false, None) + in + Poll.(set poll fd Event.read); + ref_arrange := arrange || !ref_arrange; + ref_state := state; + ref_force_focus := force_focus + with + | Mltp_ipc.Socket.Connection_closed + when fd <> sway_socket && fd <> server_socket + -> + Unix.set_nonblock fd; + Poll.(set poll fd Event.none); + Unix.close fd); + if !ref_arrange then ( + State.arrange_current_workspace ?force_focus:!ref_force_focus + !ref_state; + (* TODO: Be more configurable about that *) + ignore (Unix.system "/usr/bin/pkill -SIGRTMIN+8 waybar")); + Poll.clear poll; + go poll !ref_state sway_socket server_socket + with Unix.Unix_error (EINTR, _, _) -> + go poll state sway_socket server_socket + +let () = + Printexc.record_backtrace true; + let poll = Poll.create () in + + let sway_socket = Sway_ipc.subscribe [ Window; Workspace ] in + Unix.set_nonblock sway_socket; + Poll.(set poll sway_socket Event.read); -let merge_streams l = - Lwt_stream.from (fun () -> Lwt.pick (List.map Lwt_stream.get l)) + let server_socket = Spatial_ipc.create_server () in + Unix.set_nonblock server_socket; + Poll.(set poll server_socket Event.read); -let main () = - let open Lwt.Syntax in - let* stream_sway = Sway_ipc.subscribe [ Window; Workspace ] in - let* stream_client = Spatial_ipc.create_server () in - let stream = - merge_streams - [ - Lwt_stream.map (fun x -> From_sway x) stream_sway; - Lwt_stream.map (fun x -> From_client x) stream_client; - ] - in - let* state = State.init false 2 in - let* () = State.arrange_current_workspace state in - let string = Format.asprintf "%a" State.pp state in - let* () = Lwt_io.printf "%s\n" string in - let* _ = Lwt_stream.fold_s event_handle stream state in - Lwt_io.printf "one of the stream has ended\n" + let state = State.init false 2 in + State.arrange_current_workspace state; -let () = Lwt_main.run @@ main () + go poll state sway_socket server_socket diff --git a/bin/spatial/ribbon.ml b/bin/spatial/ribbon.ml index 155b44b..24777ac 100644 --- a/bin/spatial/ribbon.ml +++ b/bin/spatial/ribbon.ml @@ -169,7 +169,7 @@ let split_visible ribbon = let rec split_visible acc f = function | x :: rst when f = 0 -> Some (List.rev acc, x, rst) | x :: rst -> split_visible (x :: acc) (f - 1) rst - | [] -> None + | [] -> raise (Invalid_argument "Ribbon.split_visible") in match ribbon.visible with None -> None | Some (f, l) -> split_visible [] f l @@ -244,10 +244,12 @@ let move_window_right ribbon = match pop_front left with | Some (x, left) -> (* Case: [|a b {f}|] - Result: [|b a {f}|] *) + Result: [|b a {f}|] + In this case, focus remains in the same place, so + [f - 1] *) { ribbon with - visible = Some (f, push_back x left @ [ focus ] @ right); + visible = Some (f - 1, push_back x left @ [ focus ] @ right); } | None -> (* Case: [|{f}|] diff --git a/bin/spatial/state.ml b/bin/spatial/state.ml index 495436b..e978166 100644 --- a/bin/spatial/state.ml +++ b/bin/spatial/state.ml @@ -85,10 +85,9 @@ let arrange_workspace_commands ?force_focus workspace state = | None -> [] let arrange_workspace ?force_focus ~socket workspace state = - let open Lwt.Syntax in let cmds = arrange_workspace_commands ?force_focus workspace state in - let* _replies = Sway_ipc.send_command ~socket (Run_command cmds) in - Lwt.return () + let _replies = Sway_ipc.send_command ~socket (Run_command cmds) in + () let arrange_current_workspace ?force_focus state = Sway_ipc.with_socket (fun socket -> @@ -117,9 +116,8 @@ let unregister_window state window = | None -> state let init default_full_view default_maximum_visible = - let open Lwt.Syntax in - let* cw = Sway_ipc.get_current_workspace () in - let+ tree = Sway_ipc.get_tree () in + let cw = Sway_ipc.get_current_workspace () in + let tree = Sway_ipc.get_tree () in let workspaces = Node.filter (fun x -> x.node_type = Workspace) tree in List.fold_left (fun state workspace -> @@ -133,80 +131,76 @@ let init default_full_view default_maximum_visible = (empty cw.name) workspaces let client_command_handle : - type a. - state -> a Spatial_ipc.t -> ((state * bool * int64 option) option * a) Lwt.t - = + type a. state -> a Spatial_ipc.t -> (state * bool * int64 option) * a = fun state cmd -> let open Spatial_ipc in - Lwt.return - @@ (match cmd with - | Run_command cmd -> - let res = - match cmd with - | Focus Left -> - ( { - state with - workspaces = - Workspaces_registry.update state.current_workspace - (function - | Some ribbon -> Some (Ribbon.move_focus_left ribbon) - | None -> None) - state.workspaces; - }, - true, - None ) - | Focus Right -> - ( { - state with - workspaces = - Workspaces_registry.update state.current_workspace - (function - | Some ribbon -> Some (Ribbon.move_focus_right ribbon) - | None -> None) - state.workspaces; - }, - true, - None ) - | Move Left -> - (move_window_left state.current_workspace state, true, None) - | Move Right -> - (move_window_right state.current_workspace state, true, None) - | Maximize Toggle -> - (toggle_full_view state.current_workspace state, true, None) - | Maximize _ -> - (* TODO: implement [On] and [Off] cases *) - (state, false, None) - | Split Incr -> - ( incr_maximum_visible_size state.current_workspace state, - true, - None ) - | Split Decr -> - ( decr_maximum_visible_size state.current_workspace state, - true, - None ) - in - (Some res, { success = true }) - | Get_windows -> ( - let ribbon = - Workspaces_registry.find_opt state.current_workspace - state.workspaces - in - ( None, - match ribbon with - | None -> { focus = None; windows = [] } - | Some ribbon -> ( - match ribbon.visible with - | Some (f, l) -> - { - focus = Some f; - windows = - List.map - (fun id -> - (Windows_registry.find id state.windows).app_id) - (l @ ribbon.hidden); - } - | None -> { focus = None; windows = [] }) )) - : _ * a) + (match cmd with + | Run_command cmd -> + let res = + match cmd with + | Focus Left -> + ( { + state with + workspaces = + Workspaces_registry.update state.current_workspace + (function + | Some ribbon -> Some (Ribbon.move_focus_left ribbon) + | None -> None) + state.workspaces; + }, + true, + None ) + | Focus Right -> + ( { + state with + workspaces = + Workspaces_registry.update state.current_workspace + (function + | Some ribbon -> Some (Ribbon.move_focus_right ribbon) + | None -> None) + state.workspaces; + }, + true, + None ) + | Move Left -> + (move_window_left state.current_workspace state, true, None) + | Move Right -> + (move_window_right state.current_workspace state, true, None) + | Maximize Toggle -> + (toggle_full_view state.current_workspace state, true, None) + | Maximize _ -> + (* TODO: implement [On] and [Off] cases *) + (state, false, None) + | Split Incr -> + ( incr_maximum_visible_size state.current_workspace state, + true, + None ) + | Split Decr -> + ( decr_maximum_visible_size state.current_workspace state, + true, + None ) + in + (res, { success = true }) + | Get_windows -> ( + let ribbon = + Workspaces_registry.find_opt state.current_workspace state.workspaces + in + ( (state, false, None), + match ribbon with + | None -> { focus = None; windows = [] } + | Some ribbon -> ( + match ribbon.visible with + | Some (f, l) -> + { + focus = Some f; + windows = + List.map + (fun id -> + (Windows_registry.find id state.windows).app_id) + (l @ ribbon.hidden); + } + | None -> { focus = None; windows = [] }) )) + : _ * a) let pp fmt state = Format.( diff --git a/bin/spatialmsg/dune b/bin/spatialmsg/dune index 23ecccc..3f5fa06 100644 --- a/bin/spatialmsg/dune +++ b/bin/spatialmsg/dune @@ -1,3 +1,3 @@ (executable (name main) - (libraries mltp-ipc spatial-sway.ipc lwt lwt.unix clap)) + (libraries mltp-ipc spatial-sway.ipc clap unix)) diff --git a/bin/spatialmsg/main.ml b/bin/spatialmsg/main.ml index 50b40c4..9e7be1b 100644 --- a/bin/spatialmsg/main.ml +++ b/bin/spatialmsg/main.ml @@ -11,13 +11,13 @@ let () = Clap.close (); let cmd = command_of_string_exn cmd in - let { success } = Lwt_main.run (send_command (Run_command cmd)) in + let { success } = send_command (Run_command cmd) in if not success then exit 1 | "get_windows" -> ( let cmd = Clap.optional_int ~placeholder:"INDEX" () in Clap.close (); - let reply = Lwt_main.run (send_command Get_windows) in + let reply = send_command Get_windows in match (cmd, reply.focus) with | Some idx, Some focus when idx < List.length reply.windows -> diff --git a/lib/mltp_ipc/dune b/lib/mltp_ipc/dune index 6289fab..00ab551 100644 --- a/lib/mltp_ipc/dune +++ b/lib/mltp_ipc/dune @@ -1,4 +1,4 @@ (library (name mltp_ipc) (public_name mltp-ipc) - (libraries lwt lwt.unix)) + (libraries unix)) diff --git a/lib/mltp_ipc/socket.ml b/lib/mltp_ipc/socket.ml index e90ff65..7a1f50a 100644 --- a/lib/mltp_ipc/socket.ml +++ b/lib/mltp_ipc/socket.ml @@ -2,90 +2,85 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. *) -type socket = Lwt_io.input_channel * Lwt_io.output_channel * Lwt_unix.file_descr -type error = Bad_magic_string of string | Connection_closed +type socket = Unix.file_descr -let ( let*! ) x k = Lwt.bind x k +exception Bad_magic_string of string +exception Connection_closed -let connect socket_path : socket Lwt.t = - let open Lwt.Syntax in - let socket = Lwt_unix.socket PF_UNIX SOCK_STREAM 0 in - let+ () = Lwt_unix.connect socket (ADDR_UNIX socket_path) in - let socket_in = Lwt_io.of_fd ~mode:Input socket in - let socket_out = Lwt_io.of_fd ~mode:Output socket in - (socket_in, socket_out, socket) +let connect socket_path : socket = + let socket = Unix.socket PF_UNIX SOCK_STREAM 0 in + let () = Unix.connect socket (ADDR_UNIX socket_path) in + socket -let close (_, _, s) = Lwt_unix.close s +let close s = Unix.close s let with_socket socket_path f = - let open Lwt.Syntax in - let* socket = connect socket_path in - Lwt.try_bind - (fun () -> - let* res = f socket in - let* () = close socket in - Lwt.return res) - Lwt.return - (fun exn -> - let* () = close socket in - raise exn) + let socket = connect socket_path in + try + let res = f socket in + close socket; + res + with exn -> + close socket; + raise exn -let catch_end_of_file f = - Lwt.try_bind f Lwt_result.return @@ function - | End_of_file -> Lwt_result.fail Connection_closed - | exn -> Lwt.fail exn - -let rec read_all ~count ((socket, _, _) as s) = - let open Lwt_result.Syntax in - let* payload = catch_end_of_file (fun () -> Lwt_io.read ~count socket) in - if String.length payload = count then Lwt_result.return payload - else - let+ rest = read_all ~count:(count - String.length payload) s in - payload ^ rest +let read_all ~count s = + let buffer = Bytes.create count in + let rec aux ~left = + let read = Unix.read s buffer (count - left) left in + if read = 0 then raise End_of_file; + let remainder = left - read in + if remainder = 0 then buffer else aux ~left:remainder + in + aux ~left:count |> Bytes.to_string let read_magic_string ~magic_string socket = - let open Lwt_result.Syntax in - let* msg = read_all ~count:(String.length magic_string) socket in - if msg <> magic_string then - let*! () = close socket in - Lwt_result.fail (Bad_magic_string msg) - else Lwt_result.return () + let msg = read_all ~count:(String.length magic_string) socket in + if msg <> magic_string then ( + close socket; + raise (Bad_magic_string msg)) -let write_raw_message ~magic_string (_, socket, _) raw = - let msg = Raw_message.to_string ~magic_string raw in - catch_end_of_file @@ fun () -> Lwt_io.write socket msg +let write_raw_message ~magic_string s raw = + try + let msg = Raw_message.to_string ~magic_string raw in + let _ = Unix.write_substring s msg 0 (String.length msg) in + () + with + | End_of_file -> raise Connection_closed + | exn -> + close s; + raise exn let read_raw_message ~magic_string socket = - let open Lwt_result.Syntax in - let* () = read_magic_string ~magic_string socket in - let* msg = read_all ~count:4 socket in - let size = Raw_message.string_to_int32 msg in - let* msg = read_all ~count:4 socket in - let msg_type = Raw_message.string_to_int32 msg in - if size <> 0l then - let* payload = read_all ~count:(Int32.to_int size) socket in - Lwt_result.return (msg_type, payload) - else Lwt_result.return (msg_type, "") + try + read_magic_string ~magic_string socket; + let msg = read_all ~count:4 socket in + let size = Raw_message.string_to_int32 msg in + let msg = read_all ~count:4 socket in + let msg_type = Raw_message.string_to_int32 msg in + if size <> 0l then + let payload = read_all ~count:(Int32.to_int size) socket in + (msg_type, payload) + else (msg_type, "") + with + | End_of_file -> raise Connection_closed + | exn -> + close socket; + raise exn let rec read_next_raw_message ~magic_string socket f = - let open Lwt_result.Syntax in - let* raw = read_raw_message ~magic_string socket in - if f raw then Lwt_result.return raw - else read_next_raw_message ~magic_string socket f + let raw = read_raw_message ~magic_string socket in + if f raw then raw else read_next_raw_message ~magic_string socket f -let socket_handler server () = - let open Lwt.Syntax in - let+ socket, _ = Lwt_unix.accept server in - let socket_in = Lwt_io.of_fd ~mode:Input socket in - let socket_out = Lwt_io.of_fd ~mode:Output socket in - Some (socket_in, socket_out, socket) +let accept server = + let socket, _ = Unix.accept server in + socket let create_server path = - let open Lwt.Syntax in - let socket = Lwt_unix.socket PF_UNIX SOCK_STREAM 0 in - let* socket_exists = Lwt_unix.file_exists path in - let* () = if socket_exists then Lwt_unix.unlink path else Lwt.return () in - let sockaddr = Lwt_unix.ADDR_UNIX path in - let+ () = Lwt_unix.bind socket sockaddr in - let () = Lwt_unix.listen socket 100 in - Lwt_stream.from (socket_handler socket) + let socket = Unix.socket PF_UNIX SOCK_STREAM 0 in + let socket_exists = Sys.file_exists path in + if socket_exists then Unix.unlink path; + let sockaddr = Unix.ADDR_UNIX path in + Unix.bind socket sockaddr; + Unix.listen socket 100; + socket diff --git a/lib/mltp_ipc/socket.mli b/lib/mltp_ipc/socket.mli index f351676..77d01ce 100644 --- a/lib/mltp_ipc/socket.mli +++ b/lib/mltp_ipc/socket.mli @@ -10,56 +10,48 @@ {li A 32-bit integer representing the {b L}ength of the payload} {li The {b P}ayload itself}} *) -type socket +type socket = Unix.file_descr (** A socket to communicate with a peer using the so-called MTLP protocol. *) -val connect : string -> socket Lwt.t +val connect : string -> socket (** Establish a bi-directional connection with a peer. *) -val close : socket -> unit Lwt.t +val close : socket -> unit (** Close a bi-directional connection with a peer. *) -val with_socket : string -> (socket -> 'a Lwt.t) -> 'a Lwt.t +val with_socket : string -> (socket -> 'a) -> 'a (** [with_socket path k] establishes a bi-connection with a peer using the UNIX socket located at [path], hands over the socket to the continuation [k], and takes care of closing the connection prior to returning the result, even in case of an exception. *) -type error = - | Bad_magic_string of string - (** When trying to read a MTLP message, the magic string was not - correct. *) - | Connection_closed - (** When trying to receive from or send a message to a closed - bi-directional connection. *) +exception Bad_magic_string of string +(** When trying to read a MTLP message, the magic string was not + correct. *) -val read_raw_message : - magic_string:string -> socket -> (Raw_message.t, error) result Lwt.t +exception Connection_closed +(** When trying to receive from or send a message to a closed + bi-directional connection. *) + +val read_raw_message : magic_string:string -> socket -> Raw_message.t (** [read_raw_message ~magic_string socket] reads a MTLP message from [socket]. - This function may fail with the following errors: - - {ul {li [Bad_magic_string] (closes [socket] when it happens)} - {li [Connection_closed]}} *) + @raise Bad_magic_string (closes [socket] when it happens) + @raise Connection_closed *) val read_next_raw_message : - magic_string:string -> - socket -> - (Raw_message.t -> bool) -> - (Raw_message.t, error) result Lwt.t + magic_string:string -> socket -> (Raw_message.t -> bool) -> Raw_message.t (** [read_next_raw_message ~magic_string socket f] returns the next raw message received by [socket] which satisfies [f]’s conditions. Messages that don’t satisfy [f]’s conditions are ignored. - This function may fail with the following errors: - - {ul {li [Bad_magic_string] (closes [socket] when it happens)} - {li [Connection_closed]}} *) + @raise Bad_magic_string (closes [socket] when it happens) + @raise Connection_closed *) -val write_raw_message : - magic_string:string -> socket -> Raw_message.t -> (unit, error) result Lwt.t -(** This function may fail with [Connection_closed]. *) +val write_raw_message : magic_string:string -> socket -> Raw_message.t -> unit +(** @raise Connection_closed *) -val create_server : string -> socket Lwt_stream.t Lwt.t +val create_server : string -> socket +val accept : socket -> socket diff --git a/lib/spatial_ipc/spatial_ipc.ml b/lib/spatial_ipc/spatial_ipc.ml index 8d00d70..662582a 100644 --- a/lib/spatial_ipc/spatial_ipc.ml +++ b/lib/spatial_ipc/spatial_ipc.ml @@ -121,60 +121,38 @@ let of_raw_message (op, payload) = | 1l -> Some (Packed Get_windows) | _ -> None -exception Spatial_ipc_error of Socket.error - type socket = Socket.socket -let connect () : socket Lwt.t = Socket.connect socket_path +let connect () : socket = Socket.connect socket_path let close socket = Socket.close socket -let trust_spatial f = - let open Lwt.Syntax in - let* x = f () in - match x with Ok x -> Lwt.return x | Error e -> raise (Spatial_ipc_error e) - -let with_socket f = Socket.with_socket socket_path f - -let socket_from_option = function - | Some socket -> Lwt.return socket - | None -> connect () +let with_socket ?socket f = + match socket with + | Some socket -> f socket + | None -> Socket.with_socket socket_path f let send_command ?socket cmd = - let open Lwt.Syntax in - let* s = socket_from_option socket in + with_socket ?socket @@ fun socket -> let ((op, _) as raw) = to_raw_message cmd in - let* () = - trust_spatial @@ fun () -> Socket.write_raw_message ~magic_string s raw - in - let* op', payload = - trust_spatial @@ fun () -> Socket.read_raw_message ~magic_string s - in + Socket.write_raw_message ~magic_string socket raw; + let op', payload = Socket.read_raw_message ~magic_string socket in assert (op = op'); - let* () = if Option.is_none socket then close s else Lwt.return () in - - Lwt.return @@ reply_of_string_exn cmd payload + reply_of_string_exn cmd payload -type ('a, 'b) handler = { handler : 'r. 'a -> 'r t -> ('b option * 'r) Lwt.t } +type ('a, 'b) handler = { handler : 'r. 'a -> 'r t -> 'b * 'r } let handle_next_command ~socket input { handler } = - let open Lwt.Syntax in - let* res = Socket.read_raw_message ~magic_string socket in - match res with - | Ok ((op, _) as raw) -> ( - let cmd = of_raw_message raw in - match cmd with - | Some (Packed cmd) -> - let* output, reply = handler input cmd in - let* _ = - Socket.write_raw_message ~magic_string socket - (op, reply_to_string cmd reply) - in - Lwt.return output - | None -> - let* _ = Socket.write_raw_message ~magic_string socket (op, "") in - Lwt.return None) - | Error _ -> - let* _ = Socket.write_raw_message ~magic_string socket (-1l, "") in - Lwt.return None + let ((op, _) as raw) = Socket.read_raw_message ~magic_string socket in + let cmd = of_raw_message raw in + match cmd with + | Some (Packed cmd) -> + let output, reply = handler input cmd in + Socket.write_raw_message ~magic_string socket + (op, reply_to_string cmd reply); + Some output + | None -> + Socket.write_raw_message ~magic_string socket (op, ""); + None let create_server () = Socket.create_server socket_path +let accept = Socket.accept diff --git a/lib/spatial_ipc/spatial_ipc.mli b/lib/spatial_ipc/spatial_ipc.mli index c60d5e8..d742867 100644 --- a/lib/spatial_ipc/spatial_ipc.mli +++ b/lib/spatial_ipc/spatial_ipc.mli @@ -26,16 +26,15 @@ type 'a t = | Run_command : command -> run_command_reply t | Get_windows : get_windows_reply t -type socket +type socket = Unix.file_descr -val connect : unit -> socket Lwt.t -val close : socket -> unit Lwt.t -val with_socket : (socket -> 'a Lwt.t) -> 'a Lwt.t -val send_command : ?socket:socket -> 'a t -> 'a Lwt.t +val connect : unit -> socket +val close : socket -> unit +val with_socket : ?socket:socket -> (socket -> 'a) -> 'a +val send_command : ?socket:socket -> 'a t -> 'a -type ('a, 'b) handler = { handler : 'r. 'a -> 'r t -> ('b option * 'r) Lwt.t } +type ('a, 'b) handler = { handler : 'r. 'a -> 'r t -> 'b * 'r } -val handle_next_command : - socket:socket -> 'u -> ('u, 'v) handler -> 'v option Lwt.t - -val create_server : unit -> socket Lwt_stream.t Lwt.t +val handle_next_command : socket:socket -> 'u -> ('u, 'v) handler -> 'v option +val create_server : unit -> socket +val accept : socket -> socket diff --git a/lib/sway_ipc/dune b/lib/sway_ipc/dune index 86ccb22..5ebc71d 100644 --- a/lib/sway_ipc/dune +++ b/lib/sway_ipc/dune @@ -1,4 +1,4 @@ (library (name sway_ipc) (public_name sway-ipc) - (libraries json-decoder mltp-ipc sway-ipc.types lwt lwt.unix)) + (libraries json-decoder mltp-ipc sway-ipc.types unix)) diff --git a/lib/sway_ipc/sway_ipc.ml b/lib/sway_ipc/sway_ipc.ml index 13ec1af..73554c8 100644 --- a/lib/sway_ipc/sway_ipc.ml +++ b/lib/sway_ipc/sway_ipc.ml @@ -5,8 +5,6 @@ open Sway_ipc_types open Mltp_ipc -exception Sway_ipc_error of Socket.error - let magic_string = "i3-ipc" let sway_sock_path () = @@ -16,54 +14,36 @@ let sway_sock_path () = type socket = Socket.socket -let connect () : socket Lwt.t = Socket.connect (sway_sock_path ()) +let connect () = Socket.connect (sway_sock_path ()) let close socket = Socket.close socket -let trust_sway f = - let open Lwt.Syntax in - let* x = f () in - match x with Ok x -> Lwt.return x | Error e -> raise (Sway_ipc_error e) - -let with_socket f = Socket.with_socket (sway_sock_path ()) f +let with_socket ?socket f = + match socket with + | Some socket -> f socket + | None -> Socket.with_socket (sway_sock_path ()) f -let socket_from_option = function - | Some socket -> Lwt.return socket - | None -> connect () - -let send_command ?socket cmd = - let open Lwt.Syntax in - let* socket = socket_from_option socket in +let send_command : type a. ?socket:socket -> a Message.t -> a = + fun ?socket cmd -> + with_socket ?socket @@ fun socket -> let ((op, _) as raw) = Message.to_raw_message cmd in - let* () = - trust_sway @@ fun () -> Socket.write_raw_message ~magic_string socket raw - in - let* op', payload = - trust_sway @@ fun () -> Socket.read_raw_message ~magic_string socket - in + Socket.write_raw_message ~magic_string socket raw; + let op', payload = Socket.read_raw_message ~magic_string socket in assert (op = op'); - Lwt.return @@ Json_decoder.of_string_exn (Message.reply_decoder cmd) payload + Json_decoder.of_string_exn (Message.reply_decoder cmd) payload + +let subscribe events = + let socket = connect () in + let ({ success } : Message.subscribe_reply) = + send_command ~socket (Subscribe events) + in + if success then socket else failwith "could not subscribe" -let subscribe ?socket events = - let open Lwt.Syntax in - let* socket = socket_from_option socket in - let+ { success } = send_command ~socket (Subscribe events) in - if success then - Lwt_stream.from (fun () -> - let open Lwt.Syntax in - let+ ev = - Socket.read_next_raw_message ~magic_string socket (fun (code, _) -> - List.exists - (fun ev_type -> ev_type = Event.event_type_of_code code) - events) - in - match ev with - | Ok ev -> Some (Event.event_of_raw_message ev) - | Error _ -> None) - else failwith "Something went wrong" +let read_event socket = + let raw = Socket.read_next_raw_message ~magic_string socket (fun _ -> true) in + Event.event_of_raw_message raw let get_tree ?socket () = send_command ?socket Get_tree let get_current_workspace ?socket () = - let open Lwt.Syntax in - let+ workspaces = send_command ?socket Get_workspaces in + let workspaces = send_command ?socket Get_workspaces in List.find (fun w -> w.Workspace.focused) workspaces diff --git a/lib/sway_ipc/sway_ipc.mli b/lib/sway_ipc/sway_ipc.mli index 753a0df..1c04b69 100644 --- a/lib/sway_ipc/sway_ipc.mli +++ b/lib/sway_ipc/sway_ipc.mli @@ -2,27 +2,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. *) -type socket +type socket = Unix.file_descr (** A socket to interact with Sway. *) -exception Sway_ipc_error of Mltp_ipc.Socket.error - -val connect : unit -> socket Lwt.t +val connect : unit -> socket (** [connect ()] establishes a connection with Sway. This connection can be ended by using {!close}. When possible, it is advised to use {!with_socket}. *) -val close : socket -> unit Lwt.t +val close : socket -> unit (** [close socket] puts an end to a connection with Sway. *) -val with_socket : (socket -> 'a Lwt.t) -> 'a Lwt.t -(** [with_socket k] establishes a bi-connection with Sway, hands over - the socket to the continuation [k], and takes care of closing the - connection prior to returning the result, even in case of an - exception. *) +val with_socket : ?socket:socket -> (socket -> 'a) -> 'a +(** [with_socket ?socket k] establishes a bi-connection with Sway, + hands over the socket to the continuation [k], and takes care of + closing the connection prior to returning the result, even in case + of an exception. *) -val send_command : ?socket:socket -> 'a Sway_ipc_types.Message.t -> 'a Lwt.t +val send_command : ?socket:socket -> 'a Sway_ipc_types.Message.t -> 'a (** [send_command ?socket msg] sends the command [msg] to Sway (by establishing (either by using [socket], or by establishing a fresh connection if [socket] is omitted), and returns the result sent @@ -31,25 +29,23 @@ val send_command : ?socket:socket -> 'a Sway_ipc_types.Message.t -> 'a Lwt.t This is a low-level helpers. It is advised to use specialized helpers whenever they are available. *) -val subscribe : - ?socket:socket -> - Sway_ipc_types.Event.event_type list -> - Sway_ipc_types.Event.t Lwt_stream.t Lwt.t +val read_event : socket -> Sway_ipc_types.Event.t + +val subscribe : Sway_ipc_types.Event.event_type list -> socket (** [subscribe ?socket evs] returns a stream of events sent by Sway, matching the event types listed in [evs]. The socket passed as argument should not be used to send commands afterwards. *) -val get_tree : ?socket:socket -> unit -> Sway_ipc_types.Node.t Lwt.t +val get_tree : ?socket:socket -> unit -> Sway_ipc_types.Node.t (** [get_tree ?socket ()] returns the current state of the tree manipulated by Sway. If [socket] is omitted, a fresh connection is established with Sway. *) -val get_current_workspace : - ?socket:socket -> unit -> Sway_ipc_types.Workspace.t Lwt.t +val get_current_workspace : ?socket:socket -> unit -> Sway_ipc_types.Workspace.t (** [get_current_workspace ?socket ()] returns the workspace currently focused by Sway. |