summaryrefslogtreecommitdiffstats
path: root/zellij-client/src/fake_client.rs
blob: 475298e95890e73b06a612336e30fe0e050944fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! The `[fake_client]` is used to attach to a running server session
//! and dispatch actions, that are specified through the command line.
//! Multiple actions at the same time can be dispatched.
use log::debug;
use std::sync::{Arc, Mutex};
use std::{fs, path::PathBuf, thread};

use crate::{
    command_is_executing::CommandIsExecuting, input_handler::input_actions,
    os_input_output::ClientOsApi, stdin_ansi_parser::StdinAnsiParser, stdin_handler::stdin_loop,
    ClientInfo, ClientInstruction, InputInstruction,
};
use zellij_utils::{
    channels::{self, ChannelWithContext, SenderWithContext},
    cli::CliArgs,
    data::{ClientId, Style},
    errors::ContextType,
    input::{actions::Action, config::Config, layout::LayoutFromYaml, options::Options},
    ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
};

pub fn start_fake_client(
    os_input: Box<dyn ClientOsApi>,
    _opts: CliArgs,
    config: Config,
    config_options: Options,
    info: ClientInfo,
    _layout: Option<LayoutFromYaml>,
    actions: Vec<Action>,
) {
    debug!("Starting fake Zellij client!");
    let session_name = info.get_session_name();

    // TODO: Ideally the `fake_client` would not need to specify these options,
    // but the `[NewTab:]` action depends on this state being
    // even in this client.
    let palette = config.themes.clone().map_or_else(
        || os_input.load_palette(),
        |t| {
            t.theme_config(&config_options)
                .unwrap_or_else(|| os_input.load_palette())
        },
    );

    let full_screen_ws = os_input.get_terminal_size_using_fd(0);
    let client_attributes = ClientAttributes {
        size: full_screen_ws,
        style: Style {
            colors: palette,
            rounded_corners: config.ui.unwrap_or_default().pane_frames.rounded_corners,
        },
        keybinds: config.keybinds.clone(),
    };

    let first_msg = ClientToServerMsg::AttachClient(client_attributes, config_options.clone());

    let zellij_ipc_pipe: PathBuf = {
        let mut sock_dir = zellij_utils::consts::ZELLIJ_SOCK_DIR.clone();
        fs::create_dir_all(&sock_dir).unwrap();
        zellij_utils::shared::set_permissions(&sock_dir, 0o700).unwrap();
        sock_dir.push(session_name);
        sock_dir
    };
    os_input.connect_to_server(&*zellij_ipc_pipe);
    os_input.send_to_server(first_msg);

    let mut command_is_executing = CommandIsExecuting::new();

    let (send_client_instructions, receive_client_instructions): ChannelWithContext<
        ClientInstruction,
    > = channels::bounded(50);
    let send_client_instructions = SenderWithContext::new(send_client_instructions);

    let (send_input_instructions, receive_input_instructions): ChannelWithContext<
        InputInstruction,
    > = channels::bounded(50);
    let send_input_instructions = SenderWithContext::new(send_input_instructions);

    std::panic::set_hook({
        use zellij_utils::errors::handle_panic;
        let send_client_instructions = send_client_instructions.clone();
        Box::new(move |info| {
            handle_panic(info, &send_client_instructions);
        })
    });

    let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
    let _stdin_thread = thread::Builder::new()
        .name("stdin_handler".to_string())
        .spawn({
            let os_input = os_input.clone();
            let send_input_instructions = send_input_instructions.clone();
            move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser)
        });

    let clients: Vec<ClientId>;
    os_input.send_to_server(ClientToServerMsg::ListClients);
    #[allow(clippy::collapsible_match)]
    loop {
        if let Some((msg, _)) = os_input.recv_from_server() {
            if let ServerToClientMsg::ActiveClients(active_clients) = msg {
                clients = active_clients;
                break;
            }
        }
    }
    debug!("The connected client id's are: {:?}.", clients);

    let _input_thread = thread::Builder::new()
        .name("input_handler".to_string())
        .spawn({
            let send_client_instructions = send_client_instructions.clone();
            let command_is_executing = command_is_executing.clone();
            let os_input = os_input.clone();
            let default_mode = config_options.default_mode.unwrap_or_default();
            let session_name = session_name.to_string();
            move || {
                input_actions(
                    os_input,
                    config,
                    config_options,
                    command_is_executing,
                    clients,
                    send_client_instructions,
                    default_mode,
                    receive_input_instructions,
                    actions,
                    session_name,
                )
            }
        });

    let router_thread = thread::Builder::new()
        .name("router".to_string())
        .spawn({
            let os_input = os_input.clone();
            let mut should_break = false;
            move || loop {
                if let Some((instruction, err_ctx)) = os_input.recv_from_server() {
                    err_ctx.update_thread_ctx();
                    if let ServerToClientMsg::Exit(_) = instruction {
                        should_break = true;
                    }
                    send_client_instructions.send(instruction.into()).unwrap();
                    if should_break {
                        break;
                    }
                }
            }
        })
        .unwrap();

    loop {
        let (client_instruction, mut err_ctx) = receive_client_instructions
            .recv()
            .expect("failed to receive app instruction on channel");

        err_ctx.add_call(ContextType::Client((&client_instruction).into()));
        match client_instruction {
            ClientInstruction::Exit(_) => {
                os_input.send_to_server(ClientToServerMsg::ClientExited);
                break;
            },
            ClientInstruction::Error(_) => {
                let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit, None));
                // handle_error(backtrace);
            },
            ClientInstruction::Render(_) => {
                // This is a fake client, that doesn't render, but
                // dispatches actions.
            },
            ClientInstruction::UnblockInputThread => {
                command_is_executing.unblock_input_thread();
            },
            ClientInstruction::SwitchToMode(input_mode) => {
                send_input_instructions
                    .send(InputInstruction::SwitchToMode(input_mode))
                    .unwrap();
            },
            _ => {},
        }
    }
    router_thread.join().unwrap();
}