feat: add network monitoring and IP address management

- introduce NetworkMonitor class to track network addresses change
- replace QLineEdit with QComboBox for interface selection in settings
- implement automatic IP detection and suggestion logic
- add signal-slot connections for real-time network updates

Log: add network monitoring and IP address management.
This commit is contained in:
re2zero
2025-12-11 22:07:26 +08:00
committed by Nick Bolton
parent 16d35349d3
commit cbebaad3b8
12 changed files with 518 additions and 59 deletions

View File

@ -54,6 +54,8 @@ add_library(${target} STATIC
core/CommandProcess.h
core/CoreProcess.cpp
core/CoreProcess.h
core/NetworkMonitor.cpp
core/NetworkMonitor.h
core/ServerConnection.cpp
core/ServerConnection.h
core/ServerMessage.cpp

View File

@ -86,7 +86,9 @@ MainWindow::MainWindow()
m_actionSettings{new QAction(this)},
m_actionStartCore{new QAction(this)},
m_actionRestartCore{new QAction(this)},
m_actionStopCore{new QAction(this)}
m_actionStopCore{new QAction(this)},
m_networkMonitor{new NetworkMonitor(this)},
m_currentIPValid(true)
{
ui->setupUi(this);
@ -165,6 +167,11 @@ MainWindow::MainWindow()
}
MainWindow::~MainWindow()
{
// Stop network monitoring
if (m_networkMonitor) {
m_networkMonitor->stopMonitoring();
}
m_guiDupeChecker->close();
m_coreProcess.cleanup();
}
@ -198,8 +205,6 @@ void MainWindow::setupControls()
ui->btnConfigureServer->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
updateNetworkInfo();
if (Settings::value(Settings::Core::LastVersion).toString() != kVersion) {
Settings::setValue(Settings::Core::LastVersion, kVersion);
}
@ -334,6 +339,8 @@ void MainWindow::connectSlots()
connect(ui->btnEditName, &QPushButton::clicked, this, &MainWindow::showHostNameEditor);
connect(ui->lineEditName, &QLineEdit::editingFinished, this, &MainWindow::setHostName);
connect(m_networkMonitor, &NetworkMonitor::ipAddressesChanged, this, &MainWindow::updateIpLabel);
}
void MainWindow::toggleLogVisible(bool visible)
@ -425,6 +432,12 @@ void MainWindow::coreProcessError(CoreProcess::Error error)
void MainWindow::startCore()
{
// Save current IP state when server starts
if (m_coreProcess.mode() == CoreMode::Server) {
m_serverStartIPs = m_networkMonitor->getAvailableIPv4Addresses();
m_serverStartSuggestedIP = m_networkMonitor->getSuggestedIPv4Address();
}
m_coreProcess.start();
m_actionStartCore->setVisible(false);
m_actionRestartCore->setVisible(true);
@ -521,7 +534,6 @@ void MainWindow::coreModeToggled()
void MainWindow::updateModeControls(bool serverMode)
{
ui->lblIpAddresses->setVisible(serverMode);
ui->serverOptions->setVisible(serverMode);
ui->clientOptions->setVisible(!serverMode);
ui->lblNoMode->setVisible(false);
@ -534,6 +546,15 @@ void MainWindow::updateModeControls(bool serverMode)
updateModeControlLabels();
toggleCanRunCore((!serverMode && !ui->lineHostname->text().isEmpty()) || serverMode);
ui->lblIpAddresses->setVisible(serverMode);
if (serverMode) {
// Initialize network monitoring
updateNetworkInfo();
m_networkMonitor->startMonitoring();
} else {
m_networkMonitor->stopMonitoring();
}
}
void MainWindow::updateModeControlLabels()
@ -590,43 +611,7 @@ void MainWindow::updateSecurityIcon(bool visible)
void MainWindow::updateNetworkInfo()
{
static const auto colorText = QStringLiteral(R"(<span style="color:%1;">%2</span>)");
QStringList ipList;
QString suggestedAddress;
bool hinted = false;
const auto addresses = QNetworkInterface::allAddresses();
for (const auto &address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol && address != QHostAddress(QHostAddress::LocalHost) &&
!address.isInSubnet(QHostAddress::parseSubnet("169.254.0.0/16"))) {
// usually 192.168.x.x is a useful ip for the user, so indicate
// this by coloring it in the "link" color
if (!hinted && address.isInSubnet(QHostAddress::parseSubnet("192.168/16"))) {
suggestedAddress = address.toString();
ipList.append(colorText.arg(palette().link().color().name(), suggestedAddress));
hinted = true;
} else {
ipList.append(address.toString());
}
}
}
if (ipList.isEmpty()) {
ui->lblIpAddresses->setText(colorText.arg(palette().linkVisited().color().name(), tr("No IP Detected")));
ui->lblIpAddresses->setToolTip(tr("Unable to detect an IP address. Check your network connection is active."));
return;
}
ui->lblIpAddresses->setText(tr("Suggested IP: %1").arg(suggestedAddress.isEmpty() ? ipList.first() : suggestedAddress)
);
if (auto toolTipBase = tr("<p>If connecting via the hostname fails, try %1</p>"); ipList.count() < 2) {
ui->lblIpAddresses->setToolTip(toolTipBase.arg(tr("the suggested IP.")));
} else {
ui->lblIpAddresses->setToolTip(toolTipBase.arg(tr("one of the following IPs:<br/>%1").arg(ipList.join("<br/>"))));
}
updateIpLabel(m_networkMonitor->getAvailableIPv4Addresses());
}
void MainWindow::serverConnectionConfigureClient(const QString &clientName)
@ -915,10 +900,12 @@ void MainWindow::updateStatus()
break;
case Stopped:
updateNetworkInfo();
setStatus(tr("%1 is not running").arg(kAppName));
break;
case Started: {
updateNetworkInfo();
switch (connection) {
using enum CoreConnectionState;
@ -1258,3 +1245,92 @@ void MainWindow::handleNewClientPromptRequest(const QString &clientName, bool us
bool result = deskflow::gui::messages::showNewClientPrompt(this, clientName, usePeerAuth);
m_serverConnection.handleNewClientResult(clientName, result);
}
void MainWindow::updateIpLabel(const QList<QHostAddress> &addresses)
{
if (m_coreProcess.mode() != CoreMode::Server) {
return;
}
static const auto colorText = QStringLiteral(R"(<span style="color:%1;">%2</span>)");
if (addresses.isEmpty()) {
ui->lblIpAddresses->setText(colorText.arg(palette().linkVisited().color().name(), tr("No IP Detected")));
ui->lblIpAddresses->setToolTip(tr("Unable to detect an IP address. Check your network connection is active."));
return;
}
// Get all available IPs for tooltip
QStringList ipList;
for (const auto &address : addresses) {
ipList.append(address.toString());
}
QString labelText;
QString toolTipText;
// If we have a fixed IP we will use it
if (const auto ip = Settings::value(Settings::Core::Interface).toString(); !ip.isEmpty()) {
labelText = tr("Using IP: ");
toolTipText = tr("Selected as the interface in settings.");
if (ipList.contains(ip, Qt::CaseInsensitive)) {
labelText.append(ip);
} else {
labelText.append(colorText.arg(palette().linkVisited().color().name(), ip));
toolTipText.append(tr("\nInterface is not active. Unable to start server."));
}
} else {
labelText = tr("Suggested IP: ");
toolTipText = tr("<p>If connecting via the hostname fails, try %1</p>");
static auto s_toolTipSuggestIP = tr("the suggested IP.");
static auto s_toolTipIpList = tr("one of the following IPs:<br/>%1");
// Determine which IP to show and tooltip based on server state
if (m_coreProcess.isStarted()) {
// ipList should only include valid ip from servers start
ipList.clear();
for (const auto &address : std::as_const(m_serverStartIPs)) {
if (addresses.contains(address))
ipList.append(address.toString());
}
QString suggestedIP = m_serverStartSuggestedIP.toString();
if ((suggestedIP != m_currentIpAddress.toString()) || !addresses.contains(m_serverStartSuggestedIP)) {
m_currentIPValid = false;
for (const auto &address : std::as_const(m_serverStartIPs)) {
if (addresses.contains(address)) {
suggestedIP = address.toString();
m_currentIpAddress = address;
m_currentIPValid = true;
break;
}
}
} else {
m_currentIPValid = true;
}
if (m_currentIPValid) {
labelText.append(suggestedIP);
} else {
labelText.append(colorText.arg(palette().linkVisited().color().name(), suggestedIP));
toolTipText.append(tr("\nA bound IP is now invalid, you may need to restart the server."));
}
} else {
// Server is not running - update normally
const auto suggestedIp = m_networkMonitor->getSuggestedIPv4Address();
QString displayIP = !suggestedIp.isNull() ? suggestedIp.toString() : ipList.first();
m_currentIpAddress = !suggestedIp.isNull() ? suggestedIp : QHostAddress();
m_currentIPValid = !suggestedIp.isNull();
labelText.append(displayIP);
}
if (ipList.count() < 2) {
toolTipText = toolTipText.arg(s_toolTipSuggestIP);
} else {
toolTipText = toolTipText.arg(s_toolTipIpList.arg(ipList.join("<br/>")));
}
}
ui->lblIpAddresses->setText(labelText);
ui->lblIpAddresses->setToolTip(toolTipText);
}

View File

@ -8,6 +8,7 @@
#pragma once
#include <QHostAddress>
#include <QMainWindow>
#include <QMutex>
#include <QProcess>
@ -20,6 +21,7 @@
#include "config/ServerConfig.h"
#include "gui/core/ClientConnection.h"
#include "gui/core/CoreProcess.h"
#include "gui/core/NetworkMonitor.h"
#include "gui/core/ServerConnection.h"
#include "gui/core/WaylandWarnings.h"
#include "net/Fingerprint.h"
@ -58,6 +60,7 @@ class MainWindow : public QMainWindow
{
using CoreMode = Settings::CoreMode;
using CoreProcess = deskflow::gui::CoreProcess;
using NetworkMonitor = deskflow::gui::NetworkMonitor;
Q_OBJECT
@ -152,6 +155,8 @@ private:
void toggleCanRunCore(bool enableButtons);
void remoteHostChanged(const QString &newRemoteHost);
void handleNewClientPromptRequest(const QString &clientName, bool usePeerAuth);
void updateIpLabel(const QList<QHostAddress> &addresses);
/**
* @brief showClientError
* @param error Error Type
@ -218,4 +223,13 @@ private:
QAction *m_actionStartCore = nullptr;
QAction *m_actionRestartCore = nullptr;
QAction *m_actionStopCore = nullptr;
// Network monitoring
NetworkMonitor *m_networkMonitor = nullptr;
QHostAddress m_currentIpAddress;
// Server IP strategy optimization
QList<QHostAddress> m_serverStartIPs;
QHostAddress m_serverStartSuggestedIP;
bool m_currentIPValid;
};

View File

@ -0,0 +1,143 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "NetworkMonitor.h"
#include <QAbstractSocket>
#include <QList>
#include <QNetworkInterface>
#include <QSet>
#include <QTimer>
namespace deskflow::gui {
bool NetworkMonitor::isVirtualInterface(const QString &interfaceName) const
{
// Common virtual network interface patterns
static const QStringList virtualPatterns = {
QStringLiteral("vboxnet"), // VirtualBox host-only networks
QStringLiteral("vmnet"), // VMware virtual networks
QStringLiteral("docker"), // Docker bridge networks
QStringLiteral("virbr"), // libvirt bridge networks
QStringLiteral("veth"), // Virtual ethernet
QStringLiteral("br-"), // Bridge interfaces (some are virtual)
QStringLiteral("tun"), // Tunnel interfaces
QStringLiteral("tap"), // TAP interfaces
QStringLiteral("utun"), // User tunnel (macOS)
QStringLiteral("awdl"), // Apple Wireless Direct Link
QStringLiteral("p2p"), // Peer-to-peer
QStringLiteral("llw"), // Link-local wireless
QStringLiteral("anpi"), // Apple network interface
};
return virtualPatterns.contains(interfaceName, Qt::CaseInsensitive);
}
NetworkMonitor::NetworkMonitor(QObject *parent) : QObject(parent), m_checkTimer(new QTimer(this)), m_isMonitoring(false)
{
connect(m_checkTimer, &QTimer::timeout, this, &NetworkMonitor::updateNetworkState);
}
void NetworkMonitor::startMonitoring(int intervalMs)
{
if (m_isMonitoring) {
return;
}
updateNetworkState();
m_checkTimer->start(intervalMs);
m_isMonitoring = true;
}
void NetworkMonitor::stopMonitoring()
{
if (!m_isMonitoring) {
return;
}
m_checkTimer->stop();
m_isMonitoring = false;
}
QList<QHostAddress> NetworkMonitor::getAvailableIPv4Addresses() const
{
QList<QHostAddress> physicalIPs;
QList<QHostAddress> virtualIPs;
QSet<QHostAddress> uniqueAddresses;
const auto allInterfaces = QNetworkInterface::allInterfaces();
for (const auto &interface : allInterfaces) {
if (!(interface.flags() & QNetworkInterface::IsUp) || !(interface.flags() & QNetworkInterface::IsRunning) ||
(interface.flags() & QNetworkInterface::IsLoopBack)) {
continue;
}
const bool isVirtual = isVirtualInterface(interface.name());
const auto addressEntries = interface.addressEntries();
for (const auto &entry : addressEntries) {
const QHostAddress address = entry.ip();
if (address.protocol() != QAbstractSocket::IPv4Protocol ||
address.isInSubnet(QHostAddress::parseSubnet(QStringLiteral("169.254/16"))) || address.isLoopback() ||
uniqueAddresses.contains(address)) {
continue;
}
uniqueAddresses.insert(address);
if (isVirtual) {
virtualIPs.append(address);
} else {
physicalIPs.append(address);
}
}
}
auto physicalComparator = [](const QHostAddress &a, const QHostAddress &b) {
static const auto localIpFilter = QStringLiteral("192.168/16");
if (a.isInSubnet(QHostAddress::parseSubnet(localIpFilter)) !=
b.isInSubnet(QHostAddress::parseSubnet(localIpFilter))) {
return true;
}
return a.toIPv4Address() < b.toIPv4Address();
};
std::sort(physicalIPs.begin(), physicalIPs.end(), physicalComparator);
std::sort(virtualIPs.begin(), virtualIPs.end(), [](const QHostAddress &a, const QHostAddress &b) {
return a.toIPv4Address() < b.toIPv4Address();
});
auto result = physicalIPs;
result.append(virtualIPs);
return result;
}
QHostAddress NetworkMonitor::getSuggestedIPv4Address() const
{
const auto addresses = getAvailableIPv4Addresses();
if (addresses.isEmpty())
return QHostAddress();
return addresses.first();
}
void NetworkMonitor::setIpAddresses(const QList<QHostAddress> &newAddresses)
{
if (newAddresses == m_lastAddresses)
return;
m_lastAddresses = newAddresses;
Q_EMIT ipAddressesChanged(m_lastAddresses);
}
void NetworkMonitor::updateNetworkState()
{
setIpAddresses(getAvailableIPv4Addresses());
}
} // namespace deskflow::gui

View File

@ -0,0 +1,88 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QHostAddress>
#include <QList>
#include <QObject>
class QTimer;
namespace deskflow::gui {
/**
* @brief Monitor network activity changes and provide IP address updates
*
* The NetworkMonitor class monitors IP address changes
* It periodically checks network status and emits signals when changes are detected.
*/
class NetworkMonitor : public QObject
{
Q_OBJECT
public:
/**
* @brief Construct a new NetworkMonitor object
* @param parent Parent QObject
*/
explicit NetworkMonitor(QObject *parent = nullptr);
/**
* @brief Destroy the NetworkMonitor object
*/
~NetworkMonitor() override = default;
/**
* @brief Start network monitoring
* @param intervalMs Check interval in milliseconds, default 3000ms (3 seconds)
*/
void startMonitoring(int intervalMs = 3000);
/**
* @brief Stop network monitoring
*/
void stopMonitoring();
/**
* @brief Get list of all available IPv4 addresses (excluding local and link-local addresses)
* @return IPv4 address list
*/
QList<QHostAddress> getAvailableIPv4Addresses() const;
/**
* @brief Get recommended IP address (192.168.x.x preferred)
* @return Recommended IP address, returns null if none available
*/
QHostAddress getSuggestedIPv4Address() const;
Q_SIGNALS:
/**
* @brief Emitted when IP addresses change
* @param addresses New IP address list
*/
void ipAddressesChanged(const QList<QHostAddress> &addresses);
private:
void setIpAddresses(const QList<QHostAddress> &newAddresses);
/**
* @brief Check if a network interface is virtual
* @param interfaceName Network interface name
* @return true if it's a virtual interface
*/
bool isVirtualInterface(const QString &interfaceName) const;
/**
* @brief Update current network status
*/
void updateNetworkState();
QTimer *m_checkTimer; ///< Timer for periodic network checks
QList<QHostAddress> m_lastAddresses; ///< Last known IP addresses
bool m_isMonitoring; ///< Flag indicating if monitoring is active
};
} // namespace deskflow::gui

View File

@ -14,6 +14,7 @@
#include "common/Settings.h"
#include "gui/Messages.h"
#include "gui/TlsUtility.h"
#include "gui/core/NetworkMonitor.h"
#include <QComboBox>
#include <QDir>
@ -49,6 +50,16 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
// the developer was looking at, and it's easy to accidentally save that.
ui->tabWidget->setCurrentIndex(0);
// Populate the list of IP addresses
NetworkMonitor networkMonitor(this);
const auto addresses = networkMonitor.getAvailableIPv4Addresses();
for (const auto &address : addresses) {
QString ipString = address.toString();
if (ui->comboInterface->findText(ipString) == -1) {
ui->comboInterface->addItem(ipString, ipString);
}
}
loadFromConfig();
adjustSize();
@ -160,7 +171,7 @@ void SettingsDialog::updateText()
void SettingsDialog::accept()
{
Settings::setValue(Settings::Core::Port, ui->sbPort->value());
Settings::setValue(Settings::Core::Interface, ui->lineInterface->text());
Settings::setValue(Settings::Core::Interface, ui->comboInterface->currentData());
Settings::setValue(Settings::Log::Level, ui->comboLogLevel->currentIndex());
Settings::setValue(Settings::Log::ToFile, ui->cbLogToFile->isChecked());
Settings::setValue(Settings::Log::File, ui->lineLogFilename->text());
@ -194,7 +205,6 @@ void SettingsDialog::accept()
void SettingsDialog::loadFromConfig()
{
ui->sbPort->setValue(Settings::value(Settings::Core::Port).toInt());
ui->lineInterface->setText(Settings::value(Settings::Core::Interface).toString());
ui->comboLogLevel->setCurrentIndex(Settings::value(Settings::Log::Level).toInt());
ui->cbLogToFile->setChecked(Settings::value(Settings::Log::ToFile).toBool());
ui->lineLogFilename->setText(Settings::value(Settings::Log::File).toString());
@ -222,6 +232,10 @@ void SettingsDialog::loadFromConfig()
ui->lblDebugWarning->setVisible(Settings::value(Settings::Log::Level).toInt() > 4);
ui->comboInterface->setCurrentText(Settings::value(Settings::Core::Interface).toString());
if (ui->comboInterface->currentIndex() < 0)
ui->comboInterface->setCurrentIndex(0);
qDebug() << "load from config done";
updateControls();
}
@ -289,7 +303,7 @@ void SettingsDialog::updateControls()
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(writable);
ui->sbPort->setEnabled(writable);
ui->lineInterface->setEnabled(writable);
ui->comboInterface->setEnabled(writable);
ui->comboLogLevel->setEnabled(writable);
ui->cbLogToFile->setEnabled(writable);
ui->cbAutoHide->setEnabled(writable);

View File

@ -393,10 +393,12 @@
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineInterface">
<property name="enabled">
<bool>true</bool>
</property>
<widget class="QComboBox" name="comboInterface">
<item>
<property name="text">
<string>Automatic</string>
</property>
</item>
</widget>
</item>
</layout>
@ -733,7 +735,7 @@
<tabstop>btnTlsRegenCert</tabstop>
<tabstop>cbRequireClientCert</tabstop>
<tabstop>sbPort</tabstop>
<tabstop>lineInterface</tabstop>
<tabstop>comboInterface</tabstop>
<tabstop>cbLogToFile</tabstop>
<tabstop>lineLogFilename</tabstop>
<tabstop>btnBrowseLog</tabstop>

View File

@ -420,8 +420,16 @@ Do you want to connect to the server?
<translation type="unfinished">No se puede detectar una dirección IP. Compruebe que su conexión de red esté activa.</translation>
</message>
<message>
<source>Suggested IP: %1</source>
<translation type="unfinished">IP sugerida: %1</translation>
<source>Using IP: </source>
<translation type="unfinished">Usando IP: </translation>
</message>
<message>
<source>Selected as the interface in settings.</source>
<translation type="unfinished">Seleccionado como la interfaz en la configuración.</translation>
</message>
<message>
<source>Suggested IP: </source>
<translation type="unfinished">IP sugerida: </translation>
</message>
<message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -435,6 +443,12 @@ Do you want to connect to the server?
<source>one of the following IPs:&lt;br/&gt;%1</source>
<translation type="unfinished">una de las siguientes IP:&lt;br/&gt;%1</translation>
</message>
<message>
<source>
A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished">
La dirección IP asignada ahora no es válida; es posible que deba reiniciar el servidor.</translation>
</message>
<message>
<source>&amp;File</source>
<translation type="unfinished">&amp;Archivo</translation>
@ -459,6 +473,12 @@ Do you want to connect to the server?
<source>invalid certificate, generating a new one</source>
<translation type="unfinished">certificado no válido, generando uno nuevo</translation>
</message>
<message>
<source>
Interface is not active. Unable to start server.</source>
<translation type="unfinished">
La interfaz no está activa. No se puede iniciar el servidor.</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation type="unfinished">%1 lo intentará nuevamente en un momento...</translation>
@ -1287,6 +1307,10 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requiere el paquete wl-clipboard&lt;/p&gt;&lt;p&gt;Al usar wl-clipboard v2.2.1, existe un error que provoca la pérdida del foco y que puede dificultar el uso de Deskflow. Este error se ha corregido al usar la rama principal de wl-clipboard, a menos que su Compositor no sea compatible con el protocolo wlroots-data-control.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Automatic</source>
<translation type="unfinished">Automática</translation>
</message>
</context>
<context>
<name>i18n</name>

View File

@ -408,8 +408,16 @@ Vuoi connetterti al server?
<translation>Impossibile rilevare un indirizzo IP. Controlla che la tua connessione di rete sia attiva.</translation>
</message>
<message>
<source>Suggested IP: %1</source>
<translation>IP suggerito: %1</translation>
<source>Using IP: </source>
<translation type="unfinished">Utilizzo dell&apos;indirizzo IP: </translation>
</message>
<message>
<source>Selected as the interface in settings.</source>
<translation type="unfinished">Selezionata come interfaccia nelle impostazioni.</translation>
</message>
<message>
<source>Suggested IP: </source>
<translation type="unfinished">IP suggerito: </translation>
</message>
<message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -423,6 +431,12 @@ Vuoi connetterti al server?
<source>one of the following IPs:&lt;br/&gt;%1</source>
<translation>uno dei seguenti IP:&lt;br/&gt;%1</translation>
</message>
<message>
<source>
A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished">
L&apos;indirizzo IP associato non è più valido, potrebbe essere necessario riavviare il server.</translation>
</message>
<message>
<source>&amp;File</source>
<translation>&amp;File</translation>
@ -467,6 +481,12 @@ Nomi validi:
<source>invalid certificate, generating a new one</source>
<translation type="unfinished">certificato non valido, ne viene generato uno nuovo</translation>
</message>
<message>
<source>
Interface is not active. Unable to start server.</source>
<translation type="unfinished">
L&apos;interfaccia non è attiva. Impossibile avviare il server.</translation>
</message>
<message>
<source>%1 will retry in a moment...</source>
<translation>%1 riproverà tra un momento...</translation>
@ -1287,6 +1307,10 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Richiede il pacchetto wl-clipboard&lt;/p&gt;&lt;p&gt;Quando si utilizza wl-clipboard v2.2.1, si verifica un bug di furto del focus che potrebbe rendere Deskflow più difficile da usare. Questo problema è stato risolto quando si utilizza il ramo master di wl-clipboard, a meno che il proprio compositore non supporti il protocollo wlroots-data-control.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Automatic</source>
<translation type="unfinished">Automatica</translation>
</message>
</context>
<context>
<name>i18n</name>

View File

@ -372,8 +372,12 @@ Do you want to connect to the server?
<translation>IPアドレスが見つかりません</translation>
</message>
<message>
<source>Suggested IP: %1</source>
<translation>IPアドレス: %1</translation>
<source>Using IP: </source>
<translation type="unfinished">IPアドレスを使用する </translation>
</message>
<message>
<source>Selected as the interface in settings.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -387,6 +391,12 @@ Do you want to connect to the server?
<source>one of the following IPs:&lt;br/&gt;%1</source>
<translation>IPアドレスのいずれか:&lt;br/&gt;%1</translation>
</message>
<message>
<source>
A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished">
IPアドレスが無効になりました</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 </translation>
@ -531,6 +541,16 @@ Valid names:
<translation>:
%1</translation>
</message>
<message>
<source>
Interface is not active. Unable to start server.</source>
<translation type="unfinished">
</translation>
</message>
<message>
<source>Suggested IP: </source>
<translation type="unfinished">IPアドレス: </translation>
</message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation></translation>
@ -1288,6 +1308,10 @@ Enabling this setting will disable the server config GUI.</source>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;wl-clipboard &lt;/p&gt;&lt;p&gt;wl-clipboard v2.2.1 使 Deskflow 使 wl-clipboard 使 wlroots-data-control &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Automatic</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>i18n</name>

View File

@ -374,8 +374,12 @@ Do you want to connect to the server?
<translation>Не получаеться найти Ip адресc. Проверте подключение к сети.</translation>
</message>
<message>
<source>Suggested IP: %1</source>
<translation>Ваш (рекомендованый) IP адресс: %1</translation>
<source>Using IP: </source>
<translation type="unfinished">Использование IP-адреса: </translation>
</message>
<message>
<source>Selected as the interface in settings.</source>
<translation type="unfinished">Выбран в качестве интерфейса в настройках.</translation>
</message>
<message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -389,6 +393,12 @@ Do you want to connect to the server?
<source>one of the following IPs:&lt;br/&gt;%1</source>
<translation>один из следующих IP-адресов:&lt;br/&gt;%1</translation>
</message>
<message>
<source>
A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished">
Привязанный IP-адрес теперь недействителен, возможно, потребуется перезапустить сервер.</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 запускается...</translation>
@ -535,6 +545,16 @@ Valid names:
<translation>Клиент:
%1</translation>
</message>
<message>
<source>
Interface is not active. Unable to start server.</source>
<translation type="unfinished">
Интерфейс неактивен. Невозможно запустить сервер.</translation>
</message>
<message>
<source>Suggested IP: </source>
<translation type="unfinished">Ваш (рекомендованый) IP адресс: </translation>
</message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation type="unfinished">Не удалось запустить исполняемый файл Core, хотя он существует. Проверьте, есть ли у вас достаточные права для запуска этой программы.</translation>
@ -1292,6 +1312,10 @@ Enabling this setting will disable the server config GUI.</source>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Для этого необходим пакет wl-clipboard.&lt;/p&gt;&lt;p&gt;Когда ты используешь wl-clipboard v2.2.1. Возникает ошибка перехвата фокуса, которая мешает использовать deskflowю. Это фиксется если использовать wl-clipboard из ветки master, если только ваш Compositor не поддерживает протокол wlroots-data-control.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Automatic</source>
<translation type="unfinished">Автоматический</translation>
</message>
</context>
<context>
<name>i18n</name>

View File

@ -372,8 +372,12 @@ Do you want to connect to the server?
<translation> IP </translation>
</message>
<message>
<source>Suggested IP: %1</source>
<translation> IP%1</translation>
<source>Using IP: </source>
<translation type="unfinished">使IP地址 </translation>
</message>
<message>
<source>Selected as the interface in settings.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;p&gt;If connecting via the hostname fails, try %1&lt;/p&gt;</source>
@ -387,6 +391,12 @@ Do you want to connect to the server?
<source>one of the following IPs:&lt;br/&gt;%1</source>
<translation> IP &lt;br/&gt;%1</translation>
</message>
<message>
<source>
A bound IP is now invalid, you may need to restart the server.</source>
<translation type="unfinished">
IP地址现在无效</translation>
</message>
<message>
<source>%1 is starting...</source>
<translation>%1 ...</translation>
@ -531,6 +541,16 @@ Valid names:
<translation>
%1</translation>
</message>
<message>
<source>
Interface is not active. Unable to start server.</source>
<translation type="unfinished">
</translation>
</message>
<message>
<source>Suggested IP: </source>
<translation type="unfinished"> IP </translation>
</message>
<message>
<source>The Core executable could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.</source>
<translation type="unfinished">Core </translation>
@ -1288,6 +1308,10 @@ Enabling this setting will disable the server config GUI.</source>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Requires the wl-clipboard package&lt;/p&gt;&lt;p&gt;When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt; wl-clipboard &lt;/p&gt;&lt;p&gt;使 wl-clipboard v2.2.1 Bug Deskflow 使便 wl-clipboard master wlroots-data-control &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Automatic</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>i18n</name>