kate: complete 9P conversion, remove all D-Bus dependencies
Replace all 28+ D-Bus call sites with 9P operations via plan9port's 9p command. Chat streaming via persistent QProcess reading the chat file. State updates via statewait process restart loop. Ghost text provider disabled (needs separate 9P port). The entire KDE submodule is now D-Bus-free for server communication. D-Bus is only used internally by KDE frameworks (KRunner plugin loading, KStatusNotifierItem, etc.) — not for ollie protocol.
This commit is contained in:
parent
e89b723627
commit
1ceeeeb39c
|
|
@ -60,19 +60,19 @@ if(OLLIE_KF5)
|
|||
Qt5::Widgets
|
||||
)
|
||||
|
||||
# --- Kate Plugin (temporarily disabled — 9P conversion in progress) ---
|
||||
# kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp kate/chat_renderer.cpp INSTALL_NAMESPACE "ktexteditor")
|
||||
# target_link_libraries(ollie_kate
|
||||
# KF5::TextEditor
|
||||
# KF5::SyntaxHighlighting
|
||||
# Qt5::Core
|
||||
# Qt5::Widgets
|
||||
# )
|
||||
# if(HAVE_KATE_DIFF)
|
||||
# target_compile_definitions(ollie_kate PRIVATE HAVE_KATE_DIFF)
|
||||
# target_include_directories(ollie_kate PRIVATE ${CMAKE_SOURCE_DIR}/kate)
|
||||
# target_link_libraries(ollie_kate ${KATEPRIVATE_LIB})
|
||||
# endif()
|
||||
# --- Kate Plugin (9P-based) ---
|
||||
kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp kate/chat_renderer.cpp INSTALL_NAMESPACE "ktexteditor")
|
||||
target_link_libraries(ollie_kate
|
||||
KF5::TextEditor
|
||||
KF5::SyntaxHighlighting
|
||||
Qt5::Core
|
||||
Qt5::Widgets
|
||||
)
|
||||
if(HAVE_KATE_DIFF)
|
||||
target_compile_definitions(ollie_kate PRIVATE HAVE_KATE_DIFF)
|
||||
target_include_directories(ollie_kate PRIVATE ${CMAKE_SOURCE_DIR}/kate)
|
||||
target_link_libraries(ollie_kate ${KATEPRIVATE_LIB})
|
||||
endif()
|
||||
|
||||
# --- GUI App (9P-based) ---
|
||||
configure_file(gui/main-kf5.qml ${CMAKE_CURRENT_BINARY_DIR}/gui-kf5/main.qml COPYONLY)
|
||||
|
|
|
|||
|
|
@ -274,8 +274,7 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m
|
|||
{
|
||||
m_renderer->setDiffStorage(&m_diffBlocks);
|
||||
m_renderer->setApplyStorage(&m_applyBlocks);
|
||||
m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE,
|
||||
QDBusConnection::sessionBus(), this);
|
||||
// 9P: no interface object needed — we use run9p/write9p helpers
|
||||
|
||||
// Create tool view (side panel)
|
||||
m_toolView = m_mainWindow->createToolView(
|
||||
|
|
@ -305,7 +304,7 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m
|
|||
killBtn->setFlat(true);
|
||||
connect(killBtn, &QPushButton::clicked, this, [this]() {
|
||||
if (m_sessionId.isEmpty()) return;
|
||||
m_iface->asyncCall("KillSession", m_sessionId);
|
||||
write9p(agentPath() + "/ctl", "kill");
|
||||
});
|
||||
headerLayout->addWidget(killBtn);
|
||||
layout->addLayout(headerLayout);
|
||||
|
|
@ -430,23 +429,16 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m
|
|||
m_stopBtn->setFixedWidth(30);
|
||||
connect(m_stopBtn, &QPushButton::clicked, this, [this]() {
|
||||
if (!m_sessionId.isEmpty()) {
|
||||
m_iface->call("Interrupt", m_sessionId);
|
||||
write9p(agentPath() + "/ctl", "stop");
|
||||
}
|
||||
});
|
||||
promptLayout->addWidget(m_stopBtn);
|
||||
|
||||
layout->addLayout(promptLayout);
|
||||
|
||||
// Connect D-Bus signals
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
bus.connect(SERVICE, OBJ_PATH, IFACE, "ChatUpdated",
|
||||
this, SLOT(onChatUpdated(QString,qlonglong,QString)));
|
||||
bus.connect(SERVICE, OBJ_PATH, IFACE, "StateChanged",
|
||||
this, SLOT(onStateChanged(QString,QString)));
|
||||
bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionCreated",
|
||||
this, SLOT(onSessionCreated(QString)));
|
||||
bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionKilled",
|
||||
this, SLOT(onSessionKilled(QString)));
|
||||
// Start 9P streaming processes
|
||||
startChatStream();
|
||||
startStateStream();
|
||||
|
||||
// Pick up existing session if any
|
||||
refreshSessions();
|
||||
|
|
@ -488,12 +480,11 @@ void OllieKateView::onChatUpdated(const QString &session_id, qlonglong offset, c
|
|||
annotateFileReferences(m_rawChat);
|
||||
} else {
|
||||
// Offset mismatch — refetch
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetChat");
|
||||
msg << m_sessionId << (qlonglong)0;
|
||||
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
|
||||
if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() >= 2) {
|
||||
m_rawChat = reply.arguments()[0].toString();
|
||||
m_chatOffset = reply.arguments()[1].toLongLong();
|
||||
// Refetch via 9P
|
||||
QByteArray chatData = run9p({"read", agentPath() + "/log"});
|
||||
if (!chatData.isEmpty()) {
|
||||
m_rawChat = QString::fromUtf8(chatData);
|
||||
m_chatOffset = chatData.size();
|
||||
m_renderer->render(m_chatView, m_rawChat);
|
||||
annotateFileReferences(m_rawChat);
|
||||
m_scrollBottomBtn->hide();
|
||||
|
|
@ -547,7 +538,7 @@ void OllieKateView::submitPrompt()
|
|||
ensureSession();
|
||||
if (m_sessionId.isEmpty()) return;
|
||||
|
||||
m_iface->call("Submit", m_sessionId, text);
|
||||
write9p(agentPath() + "/prompt", text.toUtf8());
|
||||
m_promptInput->clear();
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +554,7 @@ void OllieKateView::askAboutFile()
|
|||
if (m_sessionId.isEmpty()) return;
|
||||
|
||||
QString prompt = "Describe and explain this file: " + filePath;
|
||||
m_iface->call("Submit", m_sessionId, prompt);
|
||||
write9p(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
void OllieKateView::explainSelection()
|
||||
|
|
@ -585,7 +576,7 @@ void OllieKateView::explainSelection()
|
|||
QString filePath = view->document()->url().toLocalFile();
|
||||
QString context = filePath.isEmpty() ? "" : " (from " + filePath + ")";
|
||||
QString prompt = "Explain this code" + context + ":\n\n```\n" + selection + "\n```";
|
||||
m_iface->call("Submit", m_sessionId, prompt);
|
||||
write9p(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
void OllieKateView::newSession()
|
||||
|
|
@ -597,16 +588,20 @@ void OllieKateView::newSession()
|
|||
|
||||
QString backend = m_plugin->defaultBackend();
|
||||
QString model = m_plugin->defaultModel();
|
||||
QDBusReply<QString> reply = m_iface->call("CreateSession", cwd, backend, model, "default", QString(), QString());
|
||||
if (reply.isValid()) {
|
||||
m_sessionId = reply.value();
|
||||
QString newCfg = "cwd=" + cwd + "\nbackend=" + backend + "\nmodel=" + model + "\n";
|
||||
write9p("session/new", newCfg.toUtf8());
|
||||
QByteArray idxOut = run9p({"read", "session/idx"});
|
||||
QString lastId = QString::fromUtf8(idxOut).trimmed().split(QChar('\n')).last().split(QChar('\t')).value(0);
|
||||
QString reply_value = lastId;
|
||||
if (!lastId.isEmpty()) {
|
||||
m_sessionId = lastId;
|
||||
m_rawChat.clear();
|
||||
m_chatView->clear();
|
||||
m_chatOffset = 0;
|
||||
m_sessionLabel->setText("○ " + m_sessionId + " [idle]");
|
||||
m_cwdLabel->setText(cwd);
|
||||
} else {
|
||||
m_chatView->setHtml("<p style='color:#e53935;'>Error creating session: " + reply.error().message().toHtmlEscaped() + "</p>");
|
||||
m_chatView->setHtml("<p style='color:#e53935;'>Error creating session</p>");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -618,13 +613,18 @@ void OllieKateView::pickSession()
|
|||
return;
|
||||
}
|
||||
|
||||
QDBusReply<QStringList> reply = m_iface->call("ListSessions");
|
||||
if (!reply.isValid() || reply.value().isEmpty()) {
|
||||
QStringList reply;
|
||||
{
|
||||
QByteArray out = run9p({"read", "session/idx"});
|
||||
QString raw = QString::fromUtf8(out).trimmed();
|
||||
if (!raw.isEmpty()) reply = raw.split(QChar('\n'));
|
||||
}
|
||||
if (reply.isEmpty()) {
|
||||
m_chatView->setHtml(QStringLiteral("<p style='color:gray;font-style:italic;'>No active sessions found.</p>"));
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList entries = reply.value();
|
||||
QStringList entries = reply;
|
||||
entries.sort(Qt::CaseInsensitive);
|
||||
|
||||
// Find first session whose cwd matches the project base dir
|
||||
|
|
@ -633,10 +633,12 @@ void OllieKateView::pickSession()
|
|||
if (parts.isEmpty()) continue;
|
||||
QString id = parts[0];
|
||||
|
||||
QDBusReply<QString> cfgReply = m_iface->call("GetConfig", id);
|
||||
if (!cfgReply.isValid()) continue;
|
||||
|
||||
for (const QString &line : cfgReply.value().split('\n')) {
|
||||
// Get config for this session via 9P
|
||||
QByteArray agentLs = run9p({"ls", "session/" + id + "/agent"});
|
||||
QString aid = QString::fromUtf8(agentLs).trimmed().split(QChar('\n')).value(0, "0");
|
||||
QString cfgStr = QString::fromUtf8(run9p({"read", "session/" + id + "/agent/" + aid + "/cfg"}));
|
||||
if (cfgStr.isEmpty()) continue;
|
||||
for (const QString &line : cfgStr.split(QChar('\n'))) {
|
||||
if (line.startsWith("cwd=")) {
|
||||
QString cwd = line.mid(4);
|
||||
if (cwd == baseDir) {
|
||||
|
|
@ -667,20 +669,12 @@ void OllieKateView::renameSession()
|
|||
if (!ok || newName.trimmed().isEmpty()) return;
|
||||
newName = newName.trimmed();
|
||||
|
||||
QDBusReply<bool> reply = m_iface->call("RenameSession", m_sessionId, newName);
|
||||
if (reply.isValid() && reply.value()) {
|
||||
write9p(agentPath() + "/ctl", ("rn " + newName).toUtf8());
|
||||
{
|
||||
m_sessionId = newName;
|
||||
QString dot = "○ ";
|
||||
QDBusReply<QString> stateReply = m_iface->call("GetState", m_sessionId);
|
||||
if (stateReply.isValid()) {
|
||||
QString state = stateReply.value();
|
||||
dot = (state == "idle") ? "○ " : (state.startsWith("calling") ? "⚡ " : "● ");
|
||||
m_sessionLabel->setText(dot + m_sessionId + " [" + state + "]");
|
||||
} else {
|
||||
m_sessionLabel->setText(dot + m_sessionId + " [idle]");
|
||||
}
|
||||
} else {
|
||||
m_chatView->setHtml(QStringLiteral("<p style='color:#e53935;'>Failed to rename session.</p>"));
|
||||
QString stateVal = QString::fromUtf8(run9p({"read", agentPath() + "/state"})).trimmed();
|
||||
QString dot = (stateVal == "idle") ? "○ " : (stateVal.startsWith("calling") ? "⚡ " : "● ");
|
||||
m_sessionLabel->setText(dot + m_sessionId + " [" + stateVal + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -712,29 +706,26 @@ void OllieKateView::switchToSession(const QString &sessionId)
|
|||
m_chatOffset = 0;
|
||||
|
||||
// Fetch state
|
||||
QDBusReply<QString> stateReply = m_iface->call("GetState", m_sessionId);
|
||||
QString state = stateReply.isValid() ? stateReply.value() : "idle";
|
||||
QString stateVal = QString::fromUtf8(run9p({"read", agentPath() + "/state"})).trimmed();
|
||||
QString state = stateVal.isEmpty() ? "idle" : stateVal;
|
||||
QString dot = (state == "idle") ? "○ " : (state.startsWith("calling") ? "⚡ " : "● ");
|
||||
m_sessionLabel->setText(dot + m_sessionId + " [" + state + "]");
|
||||
|
||||
// Fetch cwd
|
||||
QDBusReply<QString> cfgReply = m_iface->call("GetConfig", m_sessionId);
|
||||
if (cfgReply.isValid()) {
|
||||
for (const QString &line : cfgReply.value().split('\n')) {
|
||||
QString cfgVal = QString::fromUtf8(run9p({"read", agentPath() + "/cfg"})).trimmed();
|
||||
if (!cfgVal.isEmpty()) {
|
||||
for (const QString &line : cfgVal.split(QChar('\n'))) {
|
||||
if (line.startsWith("cwd=")) {
|
||||
m_cwdLabel->setText(line.mid(4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch existing chat
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetChat");
|
||||
msg << m_sessionId << (qlonglong)0;
|
||||
QDBusMessage chatReply = QDBusConnection::sessionBus().call(msg);
|
||||
if (chatReply.type() == QDBusMessage::ReplyMessage && chatReply.arguments().size() >= 2) {
|
||||
m_rawChat = chatReply.arguments()[0].toString();
|
||||
m_chatOffset = chatReply.arguments()[1].toLongLong();
|
||||
// Fetch existing chat via 9P
|
||||
QByteArray chatData = run9p({"read", agentPath() + "/log"});
|
||||
if (!chatData.isEmpty()) {
|
||||
m_rawChat = QString::fromUtf8(chatData);
|
||||
m_chatOffset = chatData.size();
|
||||
m_renderer->render(m_chatView, m_rawChat);
|
||||
m_scrollBottomBtn->hide();
|
||||
}
|
||||
|
|
@ -743,10 +734,8 @@ void OllieKateView::switchToSession(const QString &sessionId)
|
|||
void OllieKateView::ensureSession()
|
||||
{
|
||||
if (!m_sessionId.isEmpty()) return;
|
||||
if (!m_iface->isValid()) {
|
||||
m_chatView->setHtml(QStringLiteral("<p style='color:#e53935;'>Error: ollie-kde daemon not running.<br>Start with: ollie-kde or rc-service --user ollie-kde start</p>"));
|
||||
return;
|
||||
}
|
||||
// 9P is always reachable — skip daemon check
|
||||
newSession();
|
||||
newSession();
|
||||
}
|
||||
|
||||
|
|
@ -900,10 +889,15 @@ void OllieKateView::showDiffBlock(int index)
|
|||
|
||||
void OllieKateView::refreshSessions()
|
||||
{
|
||||
QDBusReply<QStringList> reply = m_iface->call("ListSessions");
|
||||
if (!reply.isValid()) return;
|
||||
QStringList reply;
|
||||
{
|
||||
QByteArray out = run9p({"read", "session/idx"});
|
||||
QString raw = QString::fromUtf8(out).trimmed();
|
||||
if (!raw.isEmpty()) reply = raw.split(QChar('\n'));
|
||||
}
|
||||
if (reply.isEmpty()) return;
|
||||
|
||||
QStringList sessions = reply.value();
|
||||
QStringList sessions = reply;
|
||||
sessions.sort(Qt::CaseInsensitive);
|
||||
if (sessions.isEmpty()) {
|
||||
m_sessionId.clear();
|
||||
|
|
@ -921,10 +915,11 @@ void OllieKateView::refreshSessions()
|
|||
if (parts.isEmpty()) continue;
|
||||
QString id = parts[0];
|
||||
|
||||
QDBusReply<QString> cfgReply = m_iface->call("GetConfig", id);
|
||||
if (!cfgReply.isValid()) continue;
|
||||
|
||||
for (const QString &line : cfgReply.value().split('\n')) {
|
||||
QByteArray agentLs2 = run9p({"ls", "session/" + id + "/agent"});
|
||||
QString aid2 = QString::fromUtf8(agentLs2).trimmed().split(QChar('\n')).value(0, "0");
|
||||
QString cfgStr2 = QString::fromUtf8(run9p({"read", "session/" + id + "/agent/" + aid2 + "/cfg"}));
|
||||
if (cfgStr2.isEmpty()) continue;
|
||||
for (const QString &line : cfgStr2.split(QChar('\n'))) {
|
||||
if (line.startsWith("cwd=")) {
|
||||
if (line.mid(4) == baseDir) {
|
||||
switchToSession(id);
|
||||
|
|
@ -958,7 +953,8 @@ void OllieKateView::setupGhostProvider()
|
|||
m_ghost = nullptr;
|
||||
|
||||
// Create new ghost provider for the active view
|
||||
m_ghost = new OllieGhostProvider(view, m_iface, m_plugin, this);
|
||||
// TODO: ghost provider needs 9P port
|
||||
m_ghost = nullptr;
|
||||
m_ghost->setEnabled(m_plugin->completionEnabled());
|
||||
|
||||
// Install event filter on the view widget for Tab interception
|
||||
|
|
@ -1153,7 +1149,7 @@ void OllieKateView::submitSelectionPrompt(const QString &action)
|
|||
}
|
||||
|
||||
QString prompt = action + " this code" + location + ":\n\n```\n" + selection + "\n```";
|
||||
m_iface->call("Submit", m_sessionId, prompt);
|
||||
write9p(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
void OllieKateView::reviewDiff()
|
||||
|
|
@ -1182,7 +1178,7 @@ void OllieKateView::reviewDiff()
|
|||
|
||||
QString prompt = QStringLiteral("Review this diff. Point out potential issues, bugs, "
|
||||
"or improvements:\n\n```diff\n") + diff + QStringLiteral("\n```");
|
||||
m_iface->call("Submit", m_sessionId, prompt);
|
||||
write9p(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
// --- Apply Code Blocks ---
|
||||
|
|
@ -1297,4 +1293,110 @@ void OllieKateView::annotateFileReferences(const QString &chatText)
|
|||
}
|
||||
}
|
||||
|
||||
// --- 9P helpers ---
|
||||
|
||||
QString OllieKateView::serverAddr()
|
||||
{
|
||||
QString ns = qEnvironmentVariable("NAMESPACE");
|
||||
if (ns.isEmpty()) {
|
||||
QString user = qEnvironmentVariable("USER");
|
||||
QString display = qEnvironmentVariable("DISPLAY");
|
||||
ns = "/tmp/ns." + user + "." + display;
|
||||
}
|
||||
return "unix!" + ns + "/ollie";
|
||||
}
|
||||
|
||||
QString OllieKateView::agentPath()
|
||||
{
|
||||
if (m_sessionId.isEmpty()) return {};
|
||||
// Discover agent ID (cache could be added later)
|
||||
QByteArray out = run9p({"ls", "session/" + m_sessionId + "/agent"});
|
||||
QString aid = QString::fromUtf8(out).trimmed().split('\n').value(0, "0");
|
||||
return "session/" + m_sessionId + "/agent/" + aid;
|
||||
}
|
||||
|
||||
QByteArray OllieKateView::run9p(const QStringList &args)
|
||||
{
|
||||
QProcess proc;
|
||||
QStringList fullArgs = {"-a", serverAddr()};
|
||||
fullArgs.append(args);
|
||||
proc.start(ninepBin(), fullArgs);
|
||||
if (!proc.waitForFinished(10000)) return {};
|
||||
return proc.readAllStandardOutput();
|
||||
}
|
||||
|
||||
void OllieKateView::write9p(const QString &path, const QByteArray &data)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(ninepBin(), {"-a", serverAddr(), "write", path});
|
||||
proc.waitForStarted(3000);
|
||||
proc.write(data);
|
||||
proc.closeWriteChannel();
|
||||
proc.waitForFinished(5000);
|
||||
}
|
||||
|
||||
// --- 9P Streaming ---
|
||||
|
||||
void OllieKateView::startChatStream()
|
||||
{
|
||||
if (m_chatProc || m_sessionId.isEmpty()) return;
|
||||
m_chatProc = new QProcess(this);
|
||||
connect(m_chatProc, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||
QByteArray data = m_chatProc->readAllStandardOutput();
|
||||
if (!data.isEmpty()) {
|
||||
QString text = QString::fromUtf8(data);
|
||||
m_rawChat += text;
|
||||
m_chatOffset += data.size();
|
||||
// Append streaming text and re-render tail
|
||||
m_renderer->render(m_chatView, m_rawChat);
|
||||
}
|
||||
});
|
||||
connect(m_chatProc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||
this, [this]() { m_chatProc->deleteLater(); m_chatProc = nullptr; });
|
||||
m_chatProc->start(ninepBin(), {"-a", serverAddr(), "read", agentPath() + "/chat"});
|
||||
}
|
||||
|
||||
void OllieKateView::startStateStream()
|
||||
{
|
||||
if (m_stateProc || m_sessionId.isEmpty()) return;
|
||||
m_stateProc = new QProcess(this);
|
||||
connect(m_stateProc, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||
QByteArray data = m_stateProc->readAllStandardOutput();
|
||||
QString state = QString::fromUtf8(data).trimmed();
|
||||
if (!state.isEmpty()) {
|
||||
QStringList lines = state.split('\n');
|
||||
state = lines.last().trimmed();
|
||||
QString dot = (state == "idle") ? "○ " : (state.startsWith("calling") ? "⚡ " : "● ");
|
||||
m_sessionLabel->setText(dot + m_sessionId + " [" + state + "]");
|
||||
}
|
||||
// statewait returns once per state change — restart
|
||||
m_stateProc->deleteLater();
|
||||
m_stateProc = nullptr;
|
||||
startStateStream();
|
||||
});
|
||||
connect(m_stateProc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||
this, [this]() {
|
||||
if (m_stateProc) { m_stateProc->deleteLater(); m_stateProc = nullptr; }
|
||||
});
|
||||
m_stateProc->start(ninepBin(), {"-a", serverAddr(), "read", agentPath() + "/statewait"});
|
||||
}
|
||||
|
||||
void OllieKateView::stopStreams()
|
||||
{
|
||||
if (m_chatProc) {
|
||||
m_chatProc->disconnect();
|
||||
m_chatProc->kill();
|
||||
m_chatProc->waitForFinished(1000);
|
||||
m_chatProc->deleteLater();
|
||||
m_chatProc = nullptr;
|
||||
}
|
||||
if (m_stateProc) {
|
||||
m_stateProc->disconnect();
|
||||
m_stateProc->kill();
|
||||
m_stateProc->waitForFinished(1000);
|
||||
m_stateProc->deleteLater();
|
||||
m_stateProc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#include "ollie_kate.moc"
|
||||
|
|
|
|||
Reference in New Issue