#include "remote.hh" #include "display_buffer.hh" #include "debug.hh" #include #include namespace Kakoune { enum class RemoteUIMsg { PrintStatus, MenuShow, MenuSelect, MenuHide, Draw }; struct socket_error{}; class Message { public: Message(int sock) : m_socket(sock) {} ~Message() { if (m_stream.size() == 0) return; int res = ::write(m_socket, m_stream.data(), m_stream.size()); if (res == 0) throw peer_disconnected{}; } void write(const char* val, size_t size) { m_stream.insert(m_stream.end(), val, val + size); } private: std::vector m_stream; int m_socket; }; template void write(Message& msg, const T& val) { msg.write((const char*)&val, sizeof(val)); }; void write(Message& msg, const String& str) { write(msg, str.length()); msg.write(str.c_str(), (int)str.length()); }; template void write(Message& msg, const memoryview& view) { write(msg, view.size()); for (auto& val : view) write(msg, val); }; template void write(Message& msg, const std::vector& vec) { write(msg, memoryview(vec)); } void write(Message& msg, const DisplayAtom& atom) { write(msg, atom.fg_color); write(msg, atom.bg_color); write(msg, atom.attribute); write(msg, atom.content.content()); } void write(Message& msg, const DisplayLine& line) { write(msg, line.atoms()); } void write(Message& msg, const DisplayBuffer& display_buffer) { write(msg, display_buffer.lines()); } void read(int socket, char* buffer, size_t size) { int res = ::read(socket, buffer, size); if (res == 0) throw peer_disconnected{}; if (res == -1) throw socket_error{}; assert(res == size); } template T read(int socket) { char value[sizeof(T)]; read(socket, value, sizeof(T)); return *(T*)(value); }; template<> String read(int socket) { ByteCount length = read(socket); if (length == 0) return String{}; char buffer[2048]; assert(length < 2048); read(socket, buffer, (int)length); return String(buffer, buffer+(int)length); }; template std::vector read_vector(int socket) { uint32_t size = read(socket); std::vector res; res.reserve(size); while (size--) res.push_back(read(socket)); return res; }; template<> DisplayAtom read(int socket) { Color fg_color = read(socket); Color bg_color = read(socket); Attribute attribute = read(socket); DisplayAtom atom(AtomContent(read(socket))); atom.fg_color = fg_color; atom.bg_color = bg_color; atom.attribute = attribute; return atom; } template<> DisplayLine read(int socket) { return DisplayLine(0, read_vector(socket)); } template<> DisplayBuffer read(int socket) { DisplayBuffer db; db.lines() = read_vector(socket); return db; } RemoteUI::RemoteUI(int socket) : m_socket(socket) { write_debug("remote client connected: " + int_to_str(m_socket)); } RemoteUI::~RemoteUI() { write_debug("remote client disconnected: " + int_to_str(m_socket)); } void RemoteUI::print_status(const String& status, CharCount cursor_pos) { Message msg(m_socket); write(msg, RemoteUIMsg::PrintStatus); write(msg, status); write(msg, cursor_pos); } void RemoteUI::menu_show(const memoryview& choices, const DisplayCoord& anchor, MenuStyle style) { Message msg(m_socket); write(msg, RemoteUIMsg::MenuShow); write(msg, choices); write(msg, anchor); write(msg, style); } void RemoteUI::menu_select(int selected) { Message msg(m_socket); write(msg, RemoteUIMsg::MenuSelect); write(msg, selected); } void RemoteUI::menu_hide() { Message msg(m_socket); write(msg, RemoteUIMsg::MenuHide); } void RemoteUI::draw(const DisplayBuffer& display_buffer, const String& mode_line) { Message msg(m_socket); write(msg, RemoteUIMsg::Draw); write(msg, display_buffer); write(msg, mode_line); } static const Key::Modifiers resize_modifier = (Key::Modifiers)0x80; bool RemoteUI::is_key_available() { timeval tv; fd_set rfds; FD_ZERO(&rfds); FD_SET(m_socket, &rfds); tv.tv_sec = 0; tv.tv_usec = 0; int res = select(m_socket+1, &rfds, NULL, NULL, &tv); return res == 1; } Key RemoteUI::get_key() { Key key = read(m_socket); if (key.modifiers == resize_modifier) { m_dimensions = { (int)(key.key >> 16), (int)(key.key & 0xFFFF) }; return Key::Invalid; } return key; } DisplayCoord RemoteUI::dimensions() { return m_dimensions; } RemoteClient::RemoteClient(int socket, UserInterface* ui) : m_socket(socket), m_ui(ui), m_dimensions(ui->dimensions()) { Key key{ resize_modifier, Codepoint(((int)m_dimensions.line << 16) | (int)m_dimensions.column) }; Message msg(socket); write(msg, key); } void RemoteClient::process_next_message() { RemoteUIMsg msg = read(m_socket); switch (msg) { case RemoteUIMsg::PrintStatus: { auto status = read(m_socket); auto cursor_pos = read(m_socket); m_ui->print_status(status, cursor_pos); break; } case RemoteUIMsg::MenuShow: { auto choices = read_vector(m_socket); auto anchor = read(m_socket); auto style = read(m_socket); m_ui->menu_show(choices, anchor, style); break; } case RemoteUIMsg::MenuSelect: m_ui->menu_select(read(m_socket)); break; case RemoteUIMsg::MenuHide: m_ui->menu_hide(); break; case RemoteUIMsg::Draw: { DisplayBuffer display_buffer = read(m_socket); String mode_line = read(m_socket); m_ui->draw(display_buffer, mode_line); break; } } } void RemoteClient::write_next_key() { DisplayCoord dimensions = m_ui->dimensions(); Message msg(m_socket); if (dimensions != m_dimensions) { m_dimensions = dimensions; Key key{ resize_modifier, Codepoint(((int)dimensions.line << 16) | (int)dimensions.column) }; write(msg, key); } write(msg, m_ui->get_key()); } }