Il 02/01/2012 14:54, Abdelrazak Younes ha scritto:
First comment: please use QTcpSocket and signal/slot connections.
Still pending, I'll convert to QTcpSocket soon.
Please, find attached the 2nd draft/skeleton of this. Changes:
1) a new BufferView is created for each new connection, all'em are added
to remote_views_ associated with a (local) BufferView
2) a server receives messages from any of the remote_views_, then it
applies them and forwards them to all the remote_views_ except the one
from which it received
3) a client simply receives messages and applies them locally
4) both server and client echo the locally dispatched LFUNs to all the
remote_views_.
In my previous patch, the client and remote bufferviews were only 2 and
they were copying the cursor movements of each other as well, so they
were actually only one single cursor etc. This time, instead, the remote
users have their separate bufferview and cursors, and they can operate
independently from one another.
Shortly, this should allow also more clients to connect to the same
BufferView (which will have bound previously through a A-x
collaborate-bind <port>), then each change made by anyone will be
forwarded to all the others. Things work in so far the changes don't
overlap. How to detect overlaps, and how to handle them, is the *dark
part* of this feature IMO. Also, nothing prevents a LyX instance to
share remotely more than 1 buffer with different clients. Just, each
shared buffer needs to collaborate-bind on a different port.
If you want to try the patch, you need to launch 2 lyx instances, create
a new empty doc in both of them, then having one to collaborate-bind
<port>, the other to collaborate-connect <host> <port>, but beware that
it will soon crash close to some metrics-related or cursor-related
assertion. Also, if you really wanted to try with different hosts, then
you'd better change the bind() host/iface address in BufferView.cpp from
"127.0.0.1" to "0.0.0.0".
I wanted to share a video, but xvidcap crashes deterministically, so I
can't :-(.
Still a small connection-setup protocol is missing (easy task), in which
the server sends the whole buffer to which the client connected over the
network, so that the client will create a brand new WorkArea and work on it.
T.
Index: src/LyXAction.cpp
===================================================================
--- src/LyXAction.cpp (revisione 40556)
+++ src/LyXAction.cpp (copia locale)
@@ -3534,6 +3534,27 @@
*/
{ LFUN_INSET_COPY_AS, "inset-copy-as", ReadOnly | NoUpdate | AtPoint, Edit },
+/*!
+ * \var lyx::FuncCode lyx::LFUN_COLLABORATE_BIND
+ * \li Action: Accept connections from remote LyX instance for collaborative editing
+ * \li Syntax: connect port
+ * \li Params: <port>: The IP port where to bind
+ * \li Origin: tommaso, Jan 1st 2012
+ * \endvar
+ */
+ { LFUN_COLLABORATE_BIND, "collaborate-bind", ReadOnly, Buffer },
+
+/*!
+ * \var lyx::FuncCode lyx::LFUN_COLLABORATE_CONNECT
+ * \li Action: Connect to remote LyX instance for collaborative editing
+ * \li Syntax: connect host port
+ * \li Params: <host>: The host name or IP address
+ * \li Params: <port>: The IP port where to connect to
+ * \li Origin: tommaso, Jan 1st 2012
+ * \endvar
+ */
+ { LFUN_COLLABORATE_CONNECT, "collaborate-connect", ReadOnly, Buffer },
+
{ LFUN_NOACTION, "", Noop, Hidden }
#ifndef DOXYGEN_SHOULD_SKIP_THIS
};
Index: src/BufferView.cpp
===================================================================
--- src/BufferView.cpp (revisione 40556)
+++ src/BufferView.cpp (copia locale)
@@ -210,6 +210,7 @@
DecorationUpdate
};
+
} // anon namespace
@@ -291,12 +292,67 @@
///
map<string, Inset *> edited_insets_;
+
+ ///
+ Socket sock_;
+ ///
+ SocketConnection *p_conn_;
+ /// Whether the p_conn_ is a client-side or server-side connection
+ bool client_conn_;
+ ///
+ QTimer timer_;
+ ///
+ vector<BufferView *> remote_views_;
+
+ /// Send to all remote_views_ except the one with connection p_excl_conn
+ void send(FuncRequest const & cmd, SocketConnection *p_excl_conn = 0);
+ ///
+ int try_recv(FuncRequest & cmd);
};
+void BufferView::Private::send(FuncRequest const & cmd, SocketConnection *p_excl_conn) {
+ vector<unsigned char> v;
+ printf("Serializing cmd\n");
+ cmd.serializeTo(v);
+ vector<BufferView *>::const_iterator it = remote_views_.begin();
+ for (; it != remote_views_.end(); ++it) {
+ if ((*it)->d->p_conn_ == p_excl_conn)
+ continue;
+ printf("Sending chunk of %lu bytes (%ld)\n", v.size(), v.end()-v.begin());
+ if ((*it)->d->p_conn_->send_chunk(v) != 0) {
+ delete (*it)->d->p_conn_;
+ (*it)->d->p_conn_ = 0;
+ delete *it;
+ cursor_.bv().message(_("Lost connection!"));
+ }
+ }
+ printf("Done\n");
+}
+
+
+int BufferView::Private::try_recv(FuncRequest & cmd) {
+ vector<unsigned char> v;
+ int rv = p_conn_->try_recv_chunk(v);
+ if (rv != 0) {
+ if (rv == -EWOULDBLOCK)
+ return rv;
+ else {
+ delete p_conn_;
+ p_conn_ = 0;
+ }
+ return rv;
+ }
+ printf("Received chunk of %lu bytes. Deserializing cmd\n", v.size());
+ cmd.serializeFrom(v);
+ printf("Done.\n");
+ return 0;
+}
+
+
BufferView::BufferView(Buffer & buf)
- : width_(0), height_(0), full_screen_(false), buffer_(buf),
- d(new Private(*this))
+ : QObject(0), width_(0), height_(0), full_screen_(false), buffer_(buf),
+ d(new Private(*this))
{
d->xsel_cache_.set = false;
d->intl_.initKeyMapper(lyxrc.use_kbmap);
@@ -307,11 +363,22 @@
d->cursor_.setCurrentFont();
buffer_.updatePreviews();
+
+ connect(&d->timer_, SIGNAL(timeout()), this, SLOT(periodicTimer()));
+ d->timer_.setInterval(500);
+ d->p_conn_ = 0;
+ d->client_conn_ = true;
}
BufferView::~BufferView()
{
+ vector<BufferView *>::const_iterator it = d->remote_views_.begin();
+ for (; it != d->remote_views_.end(); ++it) {
+ delete *it;
+ }
+ if (d->p_conn_)
+ d->p_conn_->close();
// current buffer is going to be switched-off, save cursor pos
// Ideally, the whole cursor stack should be saved, but session
// currently can only handle bottom (whole document) level pit and pos.
@@ -329,6 +396,51 @@
}
+/// @TODO Perhaps split into periodic poll for incoming connections,
+/// and the one for receiving messages.
+void BufferView::periodicTimer()
+{
+ LYXERR(Debug::ACTION, "Tick");
+ SocketConnection *p_conn;
+ if (d->sock_.accept(&p_conn) == 0) {
+ BufferView *p_bv = new BufferView(buffer());
+ p_bv->d->p_conn_ = p_conn;
+ p_bv->d->client_conn_ = false;
+ d->remote_views_.push_back(p_bv);
+ d->timer_.start();
+ message(_("New connection from remote LyX instance"));
+ }
+
+ vector<BufferView *>::const_iterator it = d->remote_views_.begin();
+ for (; it != d->remote_views_.end(); ++it) {
+ if (!(*it)->d->p_conn_)
+ continue;
+
+ int rv;
+ do {
+ FuncRequest cmd;
+ rv = (*it)->d->try_recv(cmd);
+ if (rv == 0) {
+ LYXERR(Debug::ACTION, "Dispatching received command from remote: "
+ "action[" << cmd.action() << ']'
+ << " arg[" << to_utf8(cmd.argument()) << ']'
+ << " x[" << cmd.x() << ']'
+ << " y[" << cmd.y() << ']'
+ << " button[" << cmd.button() << ']');
+ DispatchResult dr;
+ (*it)->dispatch(cmd, dr, false);
+ if (!dr.dispatched())
+ (*it)->d->cursor_.dispatch(cmd);
+ buffer().changed(true);
+ // Clients are only notified, whilst server notifies other clients
+ if (!(*it)->d->client_conn_)
+ d->send(cmd, (*it)->d->p_conn_);
+ }
+ } while (rv == 0);
+ }
+}
+
+
int BufferView::rightMargin() const
{
// The additional test for the case the outliner is opened.
@@ -1073,6 +1185,8 @@
case LFUN_KEYMAP_PRIMARY:
case LFUN_KEYMAP_SECONDARY:
case LFUN_KEYMAP_TOGGLE:
+ case LFUN_COLLABORATE_BIND:
+ case LFUN_COLLABORATE_CONNECT:
flag.setEnabled(true);
break;
@@ -1207,7 +1321,7 @@
}
-void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
+void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr, bool enable_send)
{
//lyxerr << [ cmd = " << cmd << "]" << endl;
@@ -1231,6 +1345,10 @@
buffer_.undo().beginUndoGroup();
FuncCode const act = cmd.action();
+
+ if (enable_send && act != LFUN_COLLABORATE_BIND && act != LFUN_COLLABORATE_CONNECT)
+ d->send(cmd);
+
switch (act) {
case LFUN_BUFFER_PARAMS_APPLY: {
@@ -2003,6 +2121,32 @@
break;
}
+ case LFUN_COLLABORATE_BIND: {
+ d->sock_.close();
+ d->timer_.stop();
+ if (d->sock_.bind("127.0.0.1", to_utf8(cmd.argument())) == 0) {
+ d->timer_.start();
+ message(_("Listening for incoming connections..."));
+ } else
+ message(_("Could not bind!"));
+ }
+
+ case LFUN_COLLABORATE_CONNECT: {
+ string const & host = cmd.getArg(0);
+ string const & port = cmd.getArg(1);
+ SocketConnection *p_conn;
+ if (Socket::connect(host, port, &p_conn) == 0) {
+ BufferView *p_bv = new BufferView(buffer());
+ p_bv->d->p_conn_ = p_conn;
+ p_bv->d->client_conn_ = true;
+ d->remote_views_.push_back(p_bv);
+ d->timer_.start();
+ message(_("Connected to remote LyX instance"));
+ } else {
+ message(_("Could not connect"));
+ }
+ }
+
default:
// OK, so try the Buffer itself...
buffer_.dispatch(cmd, dr);
Index: src/FuncRequest.h
===================================================================
--- src/FuncRequest.h (revisione 40556)
+++ src/FuncRequest.h (copia locale)
@@ -18,6 +18,7 @@
#include "frontends/mouse_state.h"
+#include <vector>
namespace lyx {
@@ -83,6 +84,11 @@
/// eating all characters up to the end of the command line
std::string getLongArg(unsigned int i) const;
+ ///
+ void serializeTo(std::vector<unsigned char> & v) const;
+ ///
+ void serializeFrom(std::vector<unsigned char> const & v);
+
///
static FuncRequest const unknown;
///
Index: src/frontends/qt4/GuiView.cpp
===================================================================
--- src/frontends/qt4/GuiView.cpp (revisione 40556)
+++ src/frontends/qt4/GuiView.cpp (copia locale)
@@ -692,6 +692,8 @@
dialog->prepareView();
if ((dialog = findOrBuild("findreplaceadv", true)))
dialog->prepareView();
+ if ((dialog = findOrBuild("chat", true)))
+ dialog->prepareView();
if (!restoreState(settings.value("layout").toByteArray(), 0))
initToolbars();
@@ -3809,7 +3811,7 @@
char const * const dialognames[] = {
-"aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
+"aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character", "chat",
"citation", "compare", "comparehistory", "document", "errorlist", "ert",
"external", "file", "findreplace", "findreplaceadv", "float", "graphics",
"href", "include", "index", "index_print", "info", "listings", "label", "line",
@@ -3997,6 +3999,7 @@
Dialog * createGuiBibtex(GuiView & lv);
Dialog * createGuiChanges(GuiView & lv);
Dialog * createGuiCharacter(GuiView & lv);
+Dialog * createGuiChat(GuiView & lv);
Dialog * createGuiCitation(GuiView & lv);
Dialog * createGuiCompare(GuiView & lv);
Dialog * createGuiCompareHistory(GuiView & lv);
@@ -4049,6 +4052,8 @@
return createGuiChanges(*this);
if (name == "character")
return createGuiCharacter(*this);
+ if (name == "chat")
+ return createGuiChat(*this);
if (name == "citation")
return createGuiCitation(*this);
if (name == "compare")
Index: src/frontends/qt4/GuiChat.h
===================================================================
--- src/frontends/qt4/GuiChat.h (revisione 0)
+++ src/frontends/qt4/GuiChat.h (revisione 0)
@@ -0,0 +1,101 @@
+// -*- C++ -*-
+/**
+ * \file Chat.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef QGUICHAT_H
+#define QGUICHAT_H
+
+#include "GuiWorkArea.h"
+
+#include "DockView.h"
+// This is needed so that ui_ChatUi.h can find qt_()
+#include "qt_helpers.h"
+#include "ui_ChatUi.h"
+
+#include "BufferView.h"
+#include "Buffer.h"
+#include "LyX.h"
+#include "Text.h"
+#include "lyxfind.h"
+
+#include "support/Socket.h"
+
+#include <QDialog>
+#include <QTimer>
+
+#include <string>
+
+namespace lyx {
+namespace frontend {
+
+class ChatWidget : public QTabWidget, public Ui::ChatUi
+{
+ Q_OBJECT
+
+public:
+ ChatWidget(GuiView & view);
+ ~ChatWidget();
+ bool initialiseParams(std::string const & params);
+
+private:
+ ///
+ GuiView & view_;
+ QTimer timer_;
+
+ bool eventFilter(QObject *obj, QEvent *event);
+
+ void virtual showEvent(QShowEvent *ev);
+ void virtual hideEvent(QHideEvent *ev);
+
+ void hideDialog();
+
+ Socket sock_;
+ SocketConnection *p_conn_;
+
+protected Q_SLOTS:
+ void on_sendPB_clicked();
+ void on_connectPB_clicked();
+ void on_disconnectPB_clicked();
+ void periodicTimer();
+};
+
+
+class GuiChat : public DockView
+{
+ Q_OBJECT
+public:
+ GuiChat(
+ GuiView & parent, ///< the main window where to dock.
+ Qt::DockWidgetArea area = Qt::BottomDockWidgetArea, ///< Position of the dock (and also drawer)
+ Qt::WindowFlags flags = 0);
+
+ ~GuiChat();
+
+ bool initialiseParams(std::string const &);
+ void clearParams() {}
+ void dispatchParams() {}
+ bool isBufferDependent() const { return true; }
+
+ /// update
+ void updateView() {}
+
+protected:
+ virtual bool wantInitialFocus() const { return true; }
+
+private:
+ /// The encapsulated widget.
+ ChatWidget * widget_;
+};
+
+
+} // namespace frontend
+} // namespace lyx
+
+#endif // QGUICHAT_H
Index: src/frontends/qt4/Makefile.am
===================================================================
--- src/frontends/qt4/Makefile.am (revisione 40556)
+++ src/frontends/qt4/Makefile.am (copia locale)
@@ -58,6 +58,7 @@
Action.cpp \
BulletsModule.cpp \
ButtonController.cpp \
+ GuiChat.cpp \
ColorCache.cpp \
CustomizedWidgets.cpp \
EmptyTable.cpp \
@@ -176,6 +177,7 @@
MOCHEADER = \
Action.h \
BulletsModule.h \
+ GuiChat.h \
CustomizedWidgets.h \
EmptyTable.h \
FancyLineEdit.h \
@@ -272,6 +274,7 @@
BulletsUi.ui \
ChangesUi.ui \
CharacterUi.ui \
+ ChatUi.ui \
CitationUi.ui \
ColorUi.ui \
CompareUi.ui \
Index: src/frontends/qt4/ui/ChatUi.ui
===================================================================
--- src/frontends/qt4/ui/ChatUi.ui (revisione 0)
+++ src/frontends/qt4/ui/ChatUi.ui (revisione 0)
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChatUi</class>
+ <widget class="QWidget" name="ChatUi">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>308</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>&Chat:</string>
+ </property>
+ <property name="buddy">
+ <cstring>chat_work_area_</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="3">
+ <widget class="lyx::frontend::EmbeddedWorkArea" name="chat_work_area_" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>200</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLineEdit" name="hostLE"/>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="portLE">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="sendPB">
+ <property name="text">
+ <string>Send</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="connectPB">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>lyx::frontend::EmbeddedWorkArea</class>
+ <extends>QWidget</extends>
+ <header>GuiWorkArea.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
Index: src/frontends/qt4/GuiChat.cpp
===================================================================
--- src/frontends/qt4/GuiChat.cpp (revisione 0)
+++ src/frontends/qt4/GuiChat.cpp (revisione 0)
@@ -0,0 +1,270 @@
+/**
+ * \file GuiChat.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "GuiChat.h"
+
+#include "CutAndPaste.h"
+#include "Lexer.h"
+#include "GuiApplication.h"
+#include "GuiView.h"
+#include "GuiWorkArea.h"
+#include "qt_helpers.h"
+#include "Language.h"
+
+#include "BufferParams.h"
+#include "BufferList.h"
+#include "TextClass.h"
+#include "Cursor.h"
+#include "FuncRequest.h"
+#include "lyxfind.h"
+
+#include "frontends/alert.h"
+
+#include "support/debug.h"
+#include "support/filetools.h"
+#include "support/FileName.h"
+#include "support/gettext.h"
+#include "support/lassert.h"
+#include "support/lstrings.h"
+
+#include <QCloseEvent>
+#include <QLineEdit>
+#include <QMenu>
+
+using namespace std;
+using namespace lyx::support;
+
+namespace lyx {
+namespace frontend {
+
+
+ChatWidget::ChatWidget(GuiView & view)
+ : QTabWidget(&view), view_(view), p_conn_(0)
+{
+ setupUi(this);
+ chat_work_area_->setGuiView(view_);
+ chat_work_area_->init();
+ chat_work_area_->setFrameStyle(QFrame::StyledPanel);
+
+ setFocusProxy(chat_work_area_);
+
+ // We don't want two cursors blinking.
+ chat_work_area_->stopBlinkingCursor();
+
+ hostLE->setText(toqstr("127.0.0.1"));
+ portLE->setText(toqstr("9999"));
+ sendPB->setEnabled(false);
+
+ connect(&timer_, SIGNAL(timeout()), this, SLOT(periodicTimer()));
+ timer_.setInterval(500);
+
+ sock_.bind("0.0.0.0", "9999");
+}
+
+
+ChatWidget::~ChatWidget() {
+ timer_.stop();
+ delete p_conn_;
+}
+
+void ChatWidget::periodicTimer()
+{
+ if (p_conn_ == 0) {
+ if (sock_.accept(&p_conn_) != 0) {
+ return;
+ }
+ view_.message(_("Connected from remote"));
+ sendPB->setEnabled(true);
+ }
+ string s;
+ int rv = p_conn_->try_recv_string(s);
+ if (rv == 0 && s.size() > 0) {
+ printf("Received %lu bytes: %s\n", s.size(), s.c_str());
+ if (view_.documentBufferView()) {
+ BufferView & bv = *view_.documentBufferView();
+ Buffer *new_buf = new Buffer("/tmp/tmp.internal", false);
+ new_buf->readString(s);
+ DispatchResult dr;
+ view_.setCurrentWorkArea(view_.currentMainWorkArea());
+ view_.dispatch(FuncRequest(LFUN_BUFFER_END), dr);
+ view_.dispatch(FuncRequest(LFUN_BREAK_PARAGRAPH), dr);
+ view_.dispatch(FuncRequest(LFUN_SELF_INSERT, "R: "), dr);
+ ErrorList & el = bv.buffer().errorList("Parse");
+ cap::pasteParagraphList(bv.cursor(), new_buf->paragraphs(),
+ new_buf->params().documentClassPtr(), el);
+ delete new_buf;
+ bv.buffer().changed(true);
+ view_.setCurrentWorkArea(chat_work_area_);
+ }
+ } else if (rv != -EWOULDBLOCK) {
+ view_.message(_("Disconnected from remote"));
+ delete p_conn_;
+ p_conn_ = 0;
+ sendPB->setEnabled(false);
+ }
+}
+
+
+bool ChatWidget::eventFilter(QObject * obj, QEvent * event)
+{
+ if (event->type() != QEvent::KeyPress
+ || (obj != chat_work_area_))
+ return QWidget::eventFilter(obj, event);
+
+ QKeyEvent * e = static_cast<QKeyEvent *> (event);
+ switch (e->key()) {
+ case Qt::Key_Escape:
+ if (e->modifiers() == Qt::NoModifier) {
+ hideDialog();
+ return true;
+ }
+ break;
+
+ case Qt::Key_Enter:
+ case Qt::Key_Return: {
+ // with shift we send the whole chat contents and clean it up
+ if (e->modifiers() == Qt::ShiftModifier)
+ on_sendPB_clicked();
+ return true;
+ break;
+ }
+
+ default:
+ break;
+ }
+ // standard event processing
+ return QWidget::eventFilter(obj, event);
+}
+
+
+void ChatWidget::on_connectPB_clicked() {
+ string const & host = fromqstr(hostLE->text());
+ string const & port = fromqstr(portLE->text());
+ delete p_conn_;
+ p_conn_ = 0;
+ if (Socket::connect(host, port, &p_conn_) == 0) {
+ view_.message(_("Connected"));
+ sendPB->setEnabled(true);
+ } else {
+ view_.message(_("Could not connect"));
+ sendPB->setEnabled(false);
+ }
+}
+
+
+void ChatWidget::on_disconnectPB_clicked() {
+}
+
+
+void ChatWidget::hideDialog()
+{
+ dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "chat"));
+}
+
+
+void ChatWidget::on_sendPB_clicked()
+{
+ if (p_conn_ != 0) {
+ ostringstream oss;
+ Buffer & msg_buf = chat_work_area_->bufferView().buffer();
+ msg_buf.write(oss);
+ std::string const & s = oss.str();
+ p_conn_->send_string(s);
+ if (view_.documentBufferView()) {
+ BufferView & bv = *view_.documentBufferView();
+ DispatchResult dr;
+ view_.setCurrentWorkArea(view_.currentMainWorkArea());
+ view_.dispatch(FuncRequest(LFUN_BUFFER_END), dr);
+ view_.dispatch(FuncRequest(LFUN_BREAK_PARAGRAPH), dr);
+ view_.dispatch(FuncRequest(LFUN_SELF_INSERT, "L: "), dr);
+ ErrorList & el = bv.buffer().errorList("Parse");
+ cap::pasteParagraphList(bv.cursor(), msg_buf.paragraphs(),
+ msg_buf.params().documentClassPtr(), el);
+ bv.buffer().changed(true);
+ }
+ chat_work_area_->setFocus();
+ dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
+ dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
+ dispatch(FuncRequest(LFUN_CUT));
+ }
+}
+
+
+void ChatWidget::showEvent(QShowEvent * /* ev */)
+{
+ LYXERR(Debug::DEBUG, "showEvent()" << endl);
+ chat_work_area_->installEventFilter(this);
+ timer_.start();
+
+ view_.setCurrentWorkArea(chat_work_area_);
+ LYXERR(Debug::FIND, "Selecting entire chat buffer");
+ dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
+ dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
+}
+
+
+void ChatWidget::hideEvent(QHideEvent *ev)
+{
+ chat_work_area_->removeEventFilter(this);
+ timer_.stop();
+ delete p_conn_;
+ p_conn_ = 0;
+ this->QWidget::hideEvent(ev);
+}
+
+
+bool ChatWidget::initialiseParams(std::string const & /*params*/)
+{
+ return true;
+}
+
+
+GuiChat::GuiChat(GuiView & parent,
+ Qt::DockWidgetArea area, Qt::WindowFlags flags)
+ : DockView(parent, "chat", qt_("LyX Chat"),
+ area, flags)
+{
+ widget_ = new ChatWidget(parent);
+ setWidget(widget_);
+ setFocusProxy(widget_);
+}
+
+
+GuiChat::~GuiChat()
+{
+ setFocusProxy(0);
+ delete widget_;
+}
+
+
+bool GuiChat::initialiseParams(std::string const & params)
+{
+ return widget_->initialiseParams(params);
+}
+
+
+Dialog * createGuiChat(GuiView & lv)
+{
+ GuiChat * gui = new GuiChat(lv, Qt::BottomDockWidgetArea);
+#ifdef Q_WS_MACX
+ // On Mac show and floating
+ gui->setFloating(true);
+#endif
+ return gui;
+}
+
+
+} // namespace frontend
+} // namespace lyx
+
+
+#include "moc_GuiChat.cpp"
Index: src/Makefile.am
===================================================================
--- src/Makefile.am (revisione 40556)
+++ src/Makefile.am (copia locale)
@@ -186,7 +186,8 @@
VCBackend.cpp \
version.cpp \
VSpace.cpp \
- WordList.cpp
+ WordList.cpp \
+ moc_BufferView.cpp
HEADERFILESCORE = \
Author.h \
@@ -315,7 +316,7 @@
######################### Qt stuff ##############################
-MOCHEADER = Compare.h
+MOCHEADER = Compare.h BufferView.h
if INSTALL_WINDOWS
Index: src/support/Socket.cpp
===================================================================
--- src/support/Socket.cpp (revisione 0)
+++ src/support/Socket.cpp (revisione 0)
@@ -0,0 +1,344 @@
+// -*- C++ -*-
+/**
+ * \file Socket.cpp
+ *
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "Socket.h"
+
+#include "support/lassert.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <arpa/inet.h>
+
+using namespace std;
+
+
+//#define logsyserr(msg)
+#define logsyserr(msg) perror(msg)
+
+
+static int set_non_block(int fd) {
+ int flags;
+ flags = fcntl(fd, F_GETFL, 0);
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ logsyserr("fcntl() failed");
+ return -1;
+ }
+ return 0;
+}
+
+
+static int clear_non_block(int fd) {
+ int flags;
+ flags = fcntl(fd, F_GETFL, 0);
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ logsyserr("fcntl() failed");
+ return -1;
+ }
+ return 0;
+}
+
+
+Socket::Socket() {
+ sockfd = -1;
+}
+
+
+Socket::~Socket() {
+ if (sockfd != -1)
+ ::close(sockfd);
+}
+
+
+int Socket::connect(string const & host, string const & port,
+ SocketConnection **p_pconn) {
+ struct addrinfo hint;
+ struct addrinfo *result, *rp;
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_family = AF_INET;
+ hint.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo(host.c_str(), port.c_str(), &hint, &result) < 0) {
+ logsyserr("getaddrinfo() failed");
+ return -1;
+ }
+ int sockfd;
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sockfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sockfd == -1)
+ continue;
+
+ if (::connect(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+
+ ::close(sockfd);
+ }
+
+ if (rp == NULL) {
+ logsyserr("connect() failed");
+ freeaddrinfo(result);
+ return -1;
+ }
+
+ if (set_non_block(sockfd) < 0) {
+ ::close(sockfd);
+ return -1;
+ }
+
+ *p_pconn = new SocketConnection(sockfd);
+ return 0;
+}
+
+
+int Socket::bind(std::string const & host, std::string const & port) {
+ struct addrinfo hint;
+ struct addrinfo *result, *rp;
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_family = AF_INET;
+ hint.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo(host.c_str(), port.c_str(), &hint, &result) < 0) {
+ logsyserr("getaddrinfo() failed");
+ return -1;
+ }
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sockfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sockfd == -1)
+ continue;
+
+ if (::bind(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+
+ ::close(sockfd);
+ }
+ if (rp == NULL) {
+ logsyserr("bind() failed");
+ freeaddrinfo(result);
+ return -1;
+ }
+
+ freeaddrinfo(result);
+
+ if (::listen(sockfd, 1024) < 0) {
+ logsyserr("listen() failed");
+ ::close(sockfd);
+ sockfd = -1;
+ return -1;
+ }
+
+ int optval = 1;
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
+ logsyserr("setsockopt() failed");
+ ::close(sockfd);
+ sockfd = -1;
+ return -1;
+ }
+
+ if (set_non_block(sockfd) < 0) {
+ ::close(sockfd);
+ sockfd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int Socket::accept(SocketConnection **p_pconn) {
+ int fd = ::accept(sockfd, (struct sockaddr *)NULL, (socklen_t *)NULL);
+ if (fd < 0) {
+ logsyserr("accept() failed");
+ return -1;
+ }
+ *p_pconn = new SocketConnection(fd);
+ return 0;
+}
+
+
+int Socket::close() {
+ if (sockfd == -1)
+ return -1;
+ ::close(sockfd);
+ sockfd = -1;
+ return 0;
+}
+
+
+SocketConnection::SocketConnection(int fd) : sockfd(fd) { }
+
+
+SocketConnection::SocketConnection() {
+ sockfd = -1;
+}
+
+
+SocketConnection::~SocketConnection() {
+ if (sockfd != -1)
+ ::close(sockfd);
+}
+
+
+int SocketConnection::send_chunk(vector<unsigned char> const & v)
+{
+ if (sockfd == -1)
+ return -1;
+ clear_non_block(sockfd);
+ size_t len = v.size();
+ printf("len=%lu\n", len);
+ uint32_t data = htonl(len);
+ if (write(sockfd, &data, sizeof(data)) != sizeof(data)) {
+ logsyserr("write() failed 1");
+ return -1;
+ }
+ const unsigned char *buf = &(v[0]);
+ while (len > 0) {
+ printf("Writing %lu bytes...\n", len);
+ ssize_t written = write(sockfd, buf, len);
+ if (written < 0) {
+ logsyserr("write() failed 2");
+ return -1;
+ }
+ len -= written;
+ buf += written;
+ }
+ LASSERT(len == 0, /**/);
+ return 0;
+}
+
+
+int SocketConnection::try_recv_chunk(vector<unsigned char> & v) {
+ if (sockfd == -1)
+ return -1;
+ if (set_non_block(sockfd) < 0)
+ return -1;
+ uint32_t msg_len;
+ ssize_t bytes_read = read(sockfd, &msg_len, sizeof(msg_len));
+ if (bytes_read < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ return -EWOULDBLOCK;
+ else {
+ logsyserr("read() failed 1");
+ return -1;
+ }
+ }
+ if (bytes_read != sizeof(msg_len)) {
+ ::close(sockfd);
+ return -1;
+ }
+ msg_len = ntohl(msg_len);
+ printf("try_recv_chunk(): msg_len=%u\n", msg_len);
+
+ v.resize(msg_len);
+ unsigned char *buf_tmp = &(v[0]);
+ size_t left = msg_len;
+ if (clear_non_block(sockfd) < 0)
+ return -1;
+ while (left > 0) {
+ ssize_t bytes_read = read(sockfd, buf_tmp, left);
+ if (bytes_read < 0) {
+ logsyserr("read() failed 2");
+ return -1;
+ }
+ left -= bytes_read;
+ buf_tmp += bytes_read;
+ }
+ return 0;
+}
+
+
+int SocketConnection::send_string(string const & s) {
+ if (sockfd == -1)
+ return -1;
+ clear_non_block(sockfd);
+ const char *buf = s.c_str();
+ size_t len = s.size();
+ uint32_t data = htonl(len);
+ if (write(sockfd, &data, sizeof(data)) != sizeof(data)) {
+ logsyserr("write() failed");
+ return -1;
+ }
+ while (len > 0) {
+ ssize_t written = write(sockfd, buf, len);
+ if (written < 0) {
+ logsyserr("write() failed");
+ return -1;
+ }
+ len -= written;
+ buf += written;
+ }
+ return 0;
+}
+
+
+int SocketConnection::try_recv_string(string & s) {
+ if (sockfd == -1)
+ return -1;
+ if (set_non_block(sockfd) < 0)
+ return -1;
+
+ uint32_t msg_len;
+ ssize_t bytes_read = read(sockfd, &msg_len, sizeof(msg_len));
+ if (bytes_read < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ return -EWOULDBLOCK;
+ else {
+ logsyserr("read() failed 1");
+ return -1;
+ }
+ }
+ if (bytes_read != sizeof(msg_len)) {
+ ::close(sockfd);
+ return -1;
+ }
+ msg_len = ntohl(msg_len);
+ printf("try_recv_string(): msg_len=%u\n", msg_len);
+
+ char *buf = new char[msg_len + 1];
+ if (buf == 0)
+ return -1; //< Not enough memory!
+ size_t left = msg_len;
+ if (clear_non_block(sockfd) < 0) {
+ delete buf;
+ return -1;
+ }
+ char *buf_tmp = buf;
+ while (left > 0) {
+ ssize_t bytes_read = read(sockfd, buf_tmp, left);
+ if (bytes_read < 0) {
+ logsyserr("read() failed 2");
+ delete buf;
+ return -1;
+ }
+ left -= bytes_read;
+ buf_tmp += bytes_read;
+ }
+ *buf_tmp = 0; //< Adding null string terminator
+ s = string(buf);
+ delete buf;
+ return 0;
+}
+
+
+int SocketConnection::close() {
+ if (sockfd == -1)
+ return -1;
+ ::close(sockfd);
+ sockfd = -1;
+ return 0;
+}
Index: src/support/Socket.h
===================================================================
--- src/support/Socket.h (revisione 0)
+++ src/support/Socket.h (revisione 0)
@@ -0,0 +1,58 @@
+// -*- C++ -*-
+/**
+ * \file Socket.h
+ *
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef LYX_SOCKET_H
+#define LYX_SOCKET_H
+
+#include <vector>
+#include <string>
+
+#include <errno.h>
+
+/// All int return codes are 0 for no error or negative for error
+class SocketConnection {
+public:
+ SocketConnection();
+ SocketConnection(int sockfd);
+ ~SocketConnection();
+ int close();
+
+ int send_chunk(std::vector<unsigned char> const & v);
+ int try_recv_chunk(std::vector<unsigned char> & v);
+
+ int send_string(std::string const & s);
+
+ /// Return 0 if succesfully read, -EWOULDBLOCK if it would block,
+ /// or -1 for generic socket error
+ int try_recv_string(std::string & s);
+
+private:
+ int sockfd;
+};
+
+
+class Socket {
+public:
+ Socket();
+ ~Socket();
+
+ static int connect(std::string const & host, std::string const & port,
+ SocketConnection **p_pconn);
+ int bind(std::string const & host, std::string const & port);
+ int accept(SocketConnection **p_pconn);
+ int close();
+
+private:
+ int sockfd;
+};
+
+#endif // LYX_SOCKET_H
Index: src/support/Makefile.am
===================================================================
--- src/support/Makefile.am (revisione 40556)
+++ src/support/Makefile.am (copia locale)
@@ -89,6 +89,8 @@
socktools.cpp \
socktools.h \
strfwd.h \
+ Socket.cpp \
+ Socket.h \
Systemcall.cpp \
Systemcall.h \
SystemcallPrivate.h \
Index: src/FuncCode.h
===================================================================
--- src/FuncCode.h (revisione 40556)
+++ src/FuncCode.h (copia locale)
@@ -451,6 +451,8 @@
LFUN_BUFFER_EXPORT_AS, // tommaso 20111006
// 350
LFUN_CLIPBOARD_PASTE_SIMPLE, // tommaso, 20111028
+ LFUN_COLLABORATE_BIND, // tommaso, 20120101
+ LFUN_COLLABORATE_CONNECT, // tommaso, 20120101
LFUN_LASTACTION // end of the table
};
Index: src/FuncRequest.cpp
===================================================================
--- src/FuncRequest.cpp (revisione 40556)
+++ src/FuncRequest.cpp (copia locale)
@@ -19,6 +19,7 @@
#include <iostream>
#include <sstream>
#include <vector>
+#include <stdio.h>
using namespace std;
using namespace lyx::support;
@@ -123,6 +124,58 @@
}
+/// Preliminary: no string-based conversions are actually needed!
+/// The right thing to use seems some boost binary archive and serialization stuff.
+void FuncRequest::serializeTo(std::vector<unsigned char> & v) const
+{
+ ostringstream oss;
+ oss << (int)action_;
+ oss << " " << (int)origin_;
+ oss << " " << x_ << " " << y_;
+ oss << " " << (int)button_;
+ oss << " " << to_utf8(argument_);
+ string s = oss.str();
+ v.assign(s.begin(), s.end());
+
+ v.push_back(0);
+ printf("Serialized to: %s\n", (char*)&v[0]);
+ v.pop_back();
+}
+
+
+void FuncRequest::serializeFrom(std::vector<unsigned char> const & v)
+{
+ {
+ vector<unsigned char> w = v;
+ w.push_back((unsigned char)0);
+ printf("Deserializing: %s\n", (char*)&w[0]);
+ w.pop_back();
+ }
+
+ string s;
+ s.assign(v.begin(), v.end());
+ istringstream iss(s);
+ int i;
+ iss >> i;
+ action_ = (FuncCode)i;
+ iss >> i;
+ origin_ = (Origin)i;
+ iss >> x_ >> y_;
+ iss >> i;
+ button_ = (mouse_button::state)i;
+ char ch;
+ iss >> noskipws >> ch; // Skip " "
+ string t;
+ do {
+ iss >> ch;
+ if (!iss.fail())
+ t = t + ch;
+ } while (!iss.eof());
+ printf("t='%s'\n", t.c_str());
+ argument_ = from_utf8(string(t));
+}
+
+
bool operator==(FuncRequest const & lhs, FuncRequest const & rhs)
{
return lhs.action() == rhs.action() && lhs.argument() == rhs.argument();
Index: src/BufferView.h
===================================================================
--- src/BufferView.h (revisione 40556)
+++ src/BufferView.h (copia locale)
@@ -19,6 +19,8 @@
#include "support/strfwd.h"
#include "support/types.h"
+#include "support/Socket.h"
+#include <QTimer>
namespace lyx {
@@ -80,7 +82,8 @@
* \sa Buffer
* \sa CoordCache
*/
-class BufferView {
+class BufferView : public QObject {
+ Q_OBJECT
public:
///
explicit BufferView(Buffer & buffer);
@@ -201,7 +204,7 @@
/// \return true if we've made a decision
bool getStatus(FuncRequest const & cmd, FuncStatus & flag);
/// execute the given function.
- void dispatch(FuncRequest const & cmd, DispatchResult & dr);
+ virtual void dispatch(FuncRequest const & cmd, DispatchResult & dr, bool enable_send = true);
/// request an X11 selection.
/// \return the selected string.
@@ -346,6 +349,7 @@
///
void updateDocumentClass(DocumentClass const * const olddc);
+
///
int width_;
///
@@ -357,6 +361,10 @@
struct Private;
Private * const d;
+
+private Q_SLOTS:
+ /// Periodic poll of the listening socket and connection(s), if any
+ void periodicTimer();
};
/// some space for drawing the 'nested' markers (in pixel)