Il 28/12/2011 23:23, Tommaso Cucinotta ha scritto:
Il 28/12/2011 19:31, Pavel Sanda ha scritto:
how hard would it be to enhance your patch so collaborate editing of
lyx document is possible?
I thought to this just a little: from my patch, we can merely reuse the
socket set-up and non-blocking polling structure for avoiding multiple
threads. However, the structure of a collaborative editor should be
quite different, as instead of transmitting document fragments, it
should actually transmit users' actions.
Luckily, LyX has a quite enforced separation between the GUI and
the model, i.e., the (in)famous LFUN dispatching mechanism. Therefore,
what is needed is simply a "RemoteView", with a client and a server part.
The client part, instead of (or in addition to) dispatching the LFUN to
the local view, serializes the whole LFUN (let's neglect commands
inserting external material such as graphics, for now) through the
socket,
where the server part will periodically poll other user's LFUNs and
dispatch actually them to the local view (local to the remote user :-) ).
This way you would have the
user acting on his/her own local document, and the remote user
copying the same actions on his/her own remote document.
I'm only thinking to 2 users, not 3 or more, but the logic should not be
that different.
If you want to see just a preliminary hack that kind of works, please,
find it attached.
The BufferView has been extended so as to allow for incoming connections
from a remote LyX instance, as well as to connect to a remote LyX instance.
All LFUNs dispatched to the view are also copied to the remote instance, and
any LFUN coming from it is dispatched locally.
In order to try the patch, you need to:
1) launch LyX, and create a new buffer
2) issue the "collaborate-bind 8888" command in the mini-buffer (8888 is
the TCP port, you can use whatever you prefer)
3) launch another LyX, and create a new buffer
4) issue the "collaborate-connect 127.0.0.1 8888" command in the
mini-buffer (change destination host and port as needed)
Now the two views are kept synchronized, i.e., any LFUN dispatched to
one of them is forwarded to the other.
I don't pretend the design to be anyway correct, just a hack made for
fun :-).
While working on it, some weird things happens to fall under my eyes,
such as why in GuiView there is this
dispatchToBufferView() method that tries to dispatch to the buffer view
and after to its cursor, but why
doesn't the BufferView::dispatch() method itself dispatch to its own
cursor ?
Btw, any comment is welcome.
T.
ps: happy new year !!!!!
Index: src/LyXAction.cpp
===================================================================
--- src/LyXAction.cpp (revisione 40553)
+++ 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 40553)
+++ src/BufferView.cpp (copia locale)
@@ -210,6 +210,7 @@
DecorationUpdate
};
+
} // anon namespace
@@ -295,8 +296,8 @@
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,6 +308,10 @@
d->cursor_.setCurrentFont();
buffer_.updatePreviews();
+
+ connect(&timer_, SIGNAL(timeout()), this, SLOT(periodicTimer()));
+ timer_.setInterval(500);
+ p_conn_ = 0;
}
@@ -329,6 +334,36 @@
}
+void BufferView::periodicTimer()
+{
+ LYXERR(Debug::ACTION, "Tick");
+ if (p_conn_ == 0) {
+ if (sock_.accept(&p_conn_) != 0) {
+ return;
+ }
+ message(_("Connected from remote"));
+ }
+ FuncRequest cmd;
+ int rv;
+ do {
+ rv = 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;
+ BufferView::dispatch(cmd, dr, false);
+ if (!dr.dispatched())
+ d->cursor_.dispatch(cmd);
+ buffer().changed(true);
+ }
+ } while (rv == 0);
+}
+
+
int BufferView::rightMargin() const
{
// The additional test for the case the outliner is opened.
@@ -1073,6 +1108,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 +1244,40 @@
}
-void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
+void BufferView::send(FuncRequest const & cmd) {
+ vector<unsigned char> v;
+ printf("Serializing cmd\n");
+ cmd.serializeTo(v);
+ printf("Sending chunk of %lu bytes (%ld)\n", v.size(), v.end()-v.begin());
+ if (p_conn_->send_chunk(v) != 0) {
+ delete p_conn_;
+ p_conn_ = 0;
+ message(_("Lost connection!"));
+ }
+ printf("Done\n");
+}
+
+
+int BufferView::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;
+}
+
+
+void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr, bool enable_send)
{
//lyxerr << [ cmd = " << cmd << "]" << endl;
@@ -1231,6 +1301,10 @@
buffer_.undo().beginUndoGroup();
FuncCode const act = cmd.action();
+
+ if (enable_send && act != LFUN_COLLABORATE_BIND && act != LFUN_COLLABORATE_CONNECT && p_conn_)
+ send(cmd);
+
switch (act) {
case LFUN_BUFFER_PARAMS_APPLY: {
@@ -2003,6 +2077,33 @@
break;
}
+ case LFUN_COLLABORATE_BIND: {
+ if (p_conn_ != 0)
+ delete p_conn_;
+ sock_.close();
+ timer_.stop();
+ if (sock_.bind("127.0.0.1", to_utf8(cmd.argument())) == 0) {
+ timer_.start();
+ message(_("Listening for incoming connections..."));
+ } else
+ message(_("Could not bind!"));
+ }
+
+ case LFUN_COLLABORATE_CONNECT: {
+ if (p_conn_ != 0) {
+ p_conn_->close();
+ p_conn_ = 0;
+ }
+ string const & host = cmd.getArg(0);
+ string const & port = cmd.getArg(1);
+ if (Socket::connect(host, port, &p_conn_) == 0) {
+ message(_("Connected"));
+ timer_.start();
+ } else {
+ message(_("Could not connect"));
+ }
+ }
+
default:
// OK, so try the Buffer itself...
buffer_.dispatch(cmd, dr);
Index: src/FuncRequest.h
===================================================================
--- src/FuncRequest.h (revisione 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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 40553)
+++ 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,7 +349,20 @@
///
void updateDocumentClass(DocumentClass const * const olddc);
+
///
+ void send(FuncRequest const & cmd);
+ ///
+ int try_recv(FuncRequest & cmd);
+
+ ///
+ Socket sock_;
+ ///
+ SocketConnection *p_conn_;
+ ///
+ QTimer timer_;
+
+ ///
int width_;
///
int height_;
@@ -357,6 +373,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)