Replace boost::signals2 with homegrown CSignal<>
Drops the last boost::signals2 dependency from the GUI/wallet/smessage notification path. CSignal<> is a std::function-based fan-out signal with explicit Connection tokens (no equivalent-bind disconnect). Same semantics for the void-returning case; non-void variant returns the last-connected slot's result via std::optional. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -6,8 +6,8 @@
|
||||
#define TRIANGLES_KEYSTORE_H
|
||||
|
||||
#include "crypter.h"
|
||||
#include "signal.h"
|
||||
#include "sync.h"
|
||||
#include <boost/signals2/signal.hpp>
|
||||
|
||||
class CScript;
|
||||
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
/* Wallet status (encrypted, locked) changed.
|
||||
* Note: Called without locks held.
|
||||
*/
|
||||
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;
|
||||
CSignal<void(CCryptoKeyStore*)> NotifyStatusChanged;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -182,14 +182,13 @@ static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConn
|
||||
|
||||
void ClientModel::subscribeToCoreSignals()
|
||||
{
|
||||
// Connect signals to client
|
||||
uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this));
|
||||
uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1));
|
||||
m_core_signal_connections.add(uiInterface.NotifyBlocksChanged.connect(
|
||||
[this]() { NotifyBlocksChanged(this); }));
|
||||
m_core_signal_connections.add(uiInterface.NotifyNumConnectionsChanged.connect(
|
||||
[this](int n) { NotifyNumConnectionsChanged(this, n); }));
|
||||
}
|
||||
|
||||
void ClientModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals from client
|
||||
uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this));
|
||||
uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1));
|
||||
m_core_signal_connections.disconnect_all();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "../signal.h"
|
||||
|
||||
class OptionsModel;
|
||||
class AddressTableModel;
|
||||
class TransactionTableModel;
|
||||
@@ -57,6 +59,8 @@ private:
|
||||
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
signals:
|
||||
void numConnectionsChanged(int count);
|
||||
void numBlocksChanged(int count, int countOfPeers);
|
||||
|
||||
+9
-10
@@ -620,20 +620,19 @@ void MessageModel::subscribeToCoreSignals()
|
||||
{
|
||||
qRegisterMetaType<SecMsgStored>("SecMsgStored");
|
||||
|
||||
// Connect signals
|
||||
NotifySecMsgInboxChanged.connect(boost::bind(NotifySecMsgInbox, this, _1));
|
||||
NotifySecMsgOutboxChanged.connect(boost::bind(NotifySecMsgOutbox, this, _1));
|
||||
NotifySecMsgWalletUnlocked.connect(boost::bind(NotifySecMsgWallet, this));
|
||||
|
||||
m_core_signal_connections.add(NotifySecMsgInboxChanged.connect(
|
||||
[this](SecMsgStored& hdr) { NotifySecMsgInbox(this, hdr); }));
|
||||
m_core_signal_connections.add(NotifySecMsgOutboxChanged.connect(
|
||||
[this](SecMsgStored& hdr) { NotifySecMsgOutbox(this, hdr); }));
|
||||
m_core_signal_connections.add(NotifySecMsgWalletUnlocked.connect(
|
||||
[this]() { NotifySecMsgWallet(this); }));
|
||||
|
||||
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
|
||||
}
|
||||
|
||||
void MessageModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals
|
||||
NotifySecMsgInboxChanged.disconnect(boost::bind(NotifySecMsgInbox, this, _1));
|
||||
NotifySecMsgOutboxChanged.disconnect(boost::bind(NotifySecMsgOutbox, this, _1));
|
||||
NotifySecMsgWalletUnlocked.disconnect(boost::bind(NotifySecMsgWallet, this));
|
||||
|
||||
m_core_signal_connections.disconnect_all();
|
||||
|
||||
disconnect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "allocators.h" /* for SecureString */
|
||||
#include "../signal.h"
|
||||
#include "smessage.h"
|
||||
#include <map>
|
||||
#include <QSortFilterProxyModel>
|
||||
@@ -175,6 +176,8 @@ private:
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
|
||||
public slots:
|
||||
|
||||
/* Check for new messages */
|
||||
|
||||
+11
-8
@@ -545,18 +545,21 @@ static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet,
|
||||
|
||||
void WalletModel::subscribeToCoreSignals()
|
||||
{
|
||||
// Connect signals to wallet
|
||||
wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
|
||||
wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
|
||||
wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
m_core_signal_connections.add(wallet->NotifyStatusChanged.connect(
|
||||
[this](CCryptoKeyStore* w) { NotifyKeyStoreStatusChanged(this, w); }));
|
||||
m_core_signal_connections.add(wallet->NotifyAddressBookChanged.connect(
|
||||
[this](CWallet* w, const CTxDestination& address, const std::string& label, bool isMine, ChangeType status) {
|
||||
NotifyAddressBookChanged(this, w, address, label, isMine, status);
|
||||
}));
|
||||
m_core_signal_connections.add(wallet->NotifyTransactionChanged.connect(
|
||||
[this](CWallet* w, const uint256& hash, ChangeType status) {
|
||||
NotifyTransactionChanged(this, w, hash, status);
|
||||
}));
|
||||
}
|
||||
|
||||
void WalletModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals from wallet
|
||||
wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
|
||||
wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
|
||||
wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
m_core_signal_connections.disconnect_all();
|
||||
}
|
||||
|
||||
// WalletModel::UnlockContext implementation
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "allocators.h" /* for SecureString */
|
||||
#include "../signal.h"
|
||||
|
||||
class OptionsModel;
|
||||
class AddressTableModel;
|
||||
@@ -157,6 +158,8 @@ private:
|
||||
void unsubscribeFromCoreSignals();
|
||||
bool checkBalanceChanged();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
|
||||
|
||||
public slots:
|
||||
/* Wallet status might have changed */
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_SIGNAL_H
|
||||
#define TRIANGLES_SIGNAL_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Lightweight multi-listener signal built on std::function.
|
||||
*
|
||||
* Two variants via partial specialization on a function signature:
|
||||
* CSignal<void(Args...)> fan-out signal, operator() returns void.
|
||||
* CSignal<R(Args...)> returns std::optional<R> from the most-recently
|
||||
* connected slot (mirroring signals2's last_value
|
||||
* policy with optional fallback when no slots).
|
||||
*
|
||||
* connect(slot) returns a Connection token; call .disconnect() to unsubscribe.
|
||||
*
|
||||
* Disconnect-by-bind-identity (signals2's compare-by-equivalent-bind trick) is
|
||||
* intentionally not provided — std::function lacks equality. Callers should
|
||||
* keep the Connection token (or a CSignalConnections bag) and disconnect via it.
|
||||
*/
|
||||
template <typename Signature> class CSignal;
|
||||
|
||||
namespace signal_detail {
|
||||
|
||||
template <typename Slot>
|
||||
struct SignalState
|
||||
{
|
||||
std::map<std::size_t, Slot> slots;
|
||||
std::size_t next_id = 0;
|
||||
};
|
||||
|
||||
template <typename Slot>
|
||||
class ConnectionT
|
||||
{
|
||||
public:
|
||||
ConnectionT() = default;
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (auto sp = m_state.lock()) {
|
||||
sp->slots.erase(m_id);
|
||||
}
|
||||
m_state.reset();
|
||||
}
|
||||
|
||||
template <typename> friend class SignalBase;
|
||||
|
||||
ConnectionT(std::weak_ptr<SignalState<Slot>> state, std::size_t id)
|
||||
: m_state(std::move(state)), m_id(id) {}
|
||||
|
||||
private:
|
||||
std::weak_ptr<SignalState<Slot>> m_state;
|
||||
std::size_t m_id = 0;
|
||||
};
|
||||
|
||||
template <typename Slot>
|
||||
class SignalBase
|
||||
{
|
||||
public:
|
||||
using slot_type = Slot;
|
||||
using Connection = ConnectionT<Slot>;
|
||||
|
||||
SignalBase() : m_state(std::make_shared<SignalState<Slot>>()) {}
|
||||
SignalBase(const SignalBase&) = delete;
|
||||
SignalBase& operator=(const SignalBase&) = delete;
|
||||
|
||||
Connection connect(slot_type slot)
|
||||
{
|
||||
std::size_t id = ++m_state->next_id;
|
||||
m_state->slots.emplace(id, std::move(slot));
|
||||
return Connection(m_state, id);
|
||||
}
|
||||
|
||||
bool empty() const { return m_state->slots.empty(); }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<SignalState<Slot>> m_state;
|
||||
};
|
||||
|
||||
} // namespace signal_detail
|
||||
|
||||
// Void-returning specialization: invoke every connected slot.
|
||||
template <typename... Args>
|
||||
class CSignal<void(Args...)> : public signal_detail::SignalBase<std::function<void(Args...)>>
|
||||
{
|
||||
public:
|
||||
template <typename... CallArgs>
|
||||
void operator()(CallArgs&&... args) const
|
||||
{
|
||||
// Snapshot lets slots mutate connections during invocation.
|
||||
auto snapshot = this->m_state->slots;
|
||||
for (auto& kv : snapshot) {
|
||||
if (kv.second) kv.second(args...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Non-void specialization: invoke every slot, return the most-recent slot's
|
||||
// result wrapped in optional (empty if no slots are connected).
|
||||
template <typename R, typename... Args>
|
||||
class CSignal<R(Args...)> : public signal_detail::SignalBase<std::function<R(Args...)>>
|
||||
{
|
||||
public:
|
||||
template <typename... CallArgs>
|
||||
std::optional<R> operator()(CallArgs&&... args) const
|
||||
{
|
||||
auto snapshot = this->m_state->slots;
|
||||
std::optional<R> result;
|
||||
for (auto& kv : snapshot) {
|
||||
if (kv.second) result = kv.second(args...);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Holder for a heterogeneous list of signal connections that should all be
|
||||
* released together (typical pattern: subscribe in ctor, drop on dtor).
|
||||
*/
|
||||
class CSignalConnections
|
||||
{
|
||||
public:
|
||||
CSignalConnections() = default;
|
||||
CSignalConnections(const CSignalConnections&) = delete;
|
||||
CSignalConnections& operator=(const CSignalConnections&) = delete;
|
||||
|
||||
template <typename Conn>
|
||||
void add(Conn conn)
|
||||
{
|
||||
auto holder = std::make_shared<Conn>(std::move(conn));
|
||||
m_disconnectors.emplace_back([holder]() { holder->disconnect(); });
|
||||
}
|
||||
|
||||
void disconnect_all()
|
||||
{
|
||||
for (auto& d : m_disconnectors) d();
|
||||
m_disconnectors.clear();
|
||||
}
|
||||
|
||||
~CSignalConnections() { disconnect_all(); }
|
||||
|
||||
private:
|
||||
std::vector<std::function<void()>> m_disconnectors;
|
||||
};
|
||||
|
||||
#endif // TRIANGLES_SIGNAL_H
|
||||
+3
-3
@@ -77,9 +77,9 @@ Notes:
|
||||
|
||||
// TODO: For buckets older than current, only need to store no. messages and hash in memory
|
||||
|
||||
boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
|
||||
boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
|
||||
boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
|
||||
CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
|
||||
CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
|
||||
CSignal<void()> NotifySecMsgWalletUnlocked;
|
||||
|
||||
bool fSecMsgEnabled = false;
|
||||
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "net.h"
|
||||
#include "db.h"
|
||||
#include "signal.h"
|
||||
#include "wallet.h"
|
||||
#include "lz4/lz4.h"
|
||||
|
||||
@@ -41,13 +42,13 @@ extern bool fSecMsgEnabled;
|
||||
class SecMsgStored;
|
||||
|
||||
// Inbox db changed, called with lock cs_smsgDB held.
|
||||
extern boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
|
||||
extern CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
|
||||
|
||||
// Outbox db changed, called with lock cs_smsgDB held.
|
||||
extern boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
|
||||
extern CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
|
||||
|
||||
// Wallet Unlocked, called after all messages received while locked have been processed.
|
||||
extern boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
|
||||
extern CSignal<void()> NotifySecMsgWalletUnlocked;
|
||||
|
||||
|
||||
class SecMsgBucket;
|
||||
|
||||
+19
-9
@@ -13,6 +13,7 @@
|
||||
#include "main.h"
|
||||
#include "net.h"
|
||||
#include "notificationqueue.h"
|
||||
#include "signal.h"
|
||||
|
||||
#undef printf
|
||||
#include <boost/asio.hpp>
|
||||
@@ -25,6 +26,7 @@
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <fstream>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/weak_ptr.hpp>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
@@ -886,7 +888,7 @@ void ThreadRPCServer2(void* parg)
|
||||
boost::system::error_code v6_only_error;
|
||||
boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
|
||||
|
||||
boost::signals2::signal<void ()> StopRequests;
|
||||
CSignal<void()> StopRequests;
|
||||
|
||||
bool fListening = false;
|
||||
std::string strerr;
|
||||
@@ -902,10 +904,15 @@ void ThreadRPCServer2(void* parg)
|
||||
acceptor->listen(socket_base::max_listen_connections);
|
||||
|
||||
RPCListen(acceptor, context, fUseSSL);
|
||||
// Cancel outstanding listen-requests for this acceptor when shutting down
|
||||
StopRequests.connect(signals2::slot<void ()>(
|
||||
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
|
||||
.track(acceptor));
|
||||
// Cancel outstanding listen-requests for this acceptor when shutting down.
|
||||
// weak_ptr emulates signals2's .track(): if the acceptor has already been
|
||||
// released by the time StopRequests fires, the slot is a no-op.
|
||||
{
|
||||
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
|
||||
StopRequests.connect([weak_acceptor]() {
|
||||
if (auto a = weak_acceptor.lock()) a->close();
|
||||
});
|
||||
}
|
||||
|
||||
fListening = true;
|
||||
}
|
||||
@@ -928,10 +935,13 @@ void ThreadRPCServer2(void* parg)
|
||||
acceptor->listen(socket_base::max_listen_connections);
|
||||
|
||||
RPCListen(acceptor, context, fUseSSL);
|
||||
// Cancel outstanding listen-requests for this acceptor when shutting down
|
||||
StopRequests.connect(signals2::slot<void ()>(
|
||||
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
|
||||
.track(acceptor));
|
||||
// See note above on weak_ptr-based .track() emulation.
|
||||
{
|
||||
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
|
||||
StopRequests.connect([weak_acceptor]() {
|
||||
if (auto a = weak_acceptor.lock()) a->close();
|
||||
});
|
||||
}
|
||||
|
||||
fListening = true;
|
||||
}
|
||||
|
||||
+15
-16
@@ -6,11 +6,9 @@
|
||||
#ifndef TRIANGLES_UI_INTERFACE_H
|
||||
#define TRIANGLES_UI_INTERFACE_H
|
||||
|
||||
#include <boost/signals2/last_value.hpp>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
using namespace boost::placeholders;
|
||||
#include "signal.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -65,40 +63,41 @@ public:
|
||||
};
|
||||
|
||||
/** Show message box. */
|
||||
boost::signals2::signal<void (const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
|
||||
CSignal<void(const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
|
||||
|
||||
/** Ask the user whether they want to pay a fee or not. */
|
||||
boost::signals2::signal<bool (int64_t nFeeRequired, const std::string& strCaption), boost::signals2::last_value<bool> > ThreadSafeAskFee;
|
||||
CSignal<bool(int64_t nFeeRequired, const std::string& strCaption)> ThreadSafeAskFee;
|
||||
|
||||
/** Handle a URL passed at the command line. */
|
||||
boost::signals2::signal<void (const std::string& strURI)> ThreadSafeHandleURI;
|
||||
CSignal<void(const std::string& strURI)> ThreadSafeHandleURI;
|
||||
|
||||
/** Progress message during initialization. */
|
||||
boost::signals2::signal<void (const std::string &message)> InitMessage;
|
||||
CSignal<void(const std::string& message)> InitMessage;
|
||||
|
||||
/** Initiate client shutdown. */
|
||||
boost::signals2::signal<void ()> QueueShutdown;
|
||||
CSignal<void()> QueueShutdown;
|
||||
|
||||
/** Translate a message to the native language of the user. */
|
||||
boost::signals2::signal<std::string (const char* psz)> Translate;
|
||||
CSignal<std::string(const char* psz)> Translate;
|
||||
|
||||
/** Block chain changed. */
|
||||
boost::signals2::signal<void ()> NotifyBlocksChanged;
|
||||
CSignal<void()> NotifyBlocksChanged;
|
||||
|
||||
/** Number of network connections changed. */
|
||||
boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged;
|
||||
CSignal<void(int newNumConnections)> NotifyNumConnectionsChanged;
|
||||
};
|
||||
|
||||
extern CClientUIInterface uiInterface;
|
||||
|
||||
/**
|
||||
* Translation function: Call Translate signal on UI interface, which returns a boost::optional result.
|
||||
* If no translation slot is registered, nothing is returned, and simply return the input.
|
||||
* Translation function: Call Translate signal on UI interface, which returns
|
||||
* an std::optional. If no translation slot is registered, fall back to the
|
||||
* untranslated input.
|
||||
*/
|
||||
inline std::string _(const char* psz)
|
||||
{
|
||||
boost::optional<std::string> rv = uiInterface.Translate(psz);
|
||||
return rv ? (*rv) : psz;
|
||||
std::optional<std::string> rv = uiInterface.Translate(psz);
|
||||
return rv ? *rv : psz;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -2231,7 +2231,7 @@ string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, std::string& sNa
|
||||
return strError;
|
||||
}
|
||||
|
||||
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")))
|
||||
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")).value_or(false))
|
||||
return "ABORTED";
|
||||
|
||||
if (!CommitTransaction(wtxNew, reservekey))
|
||||
|
||||
+2
-2
@@ -334,12 +334,12 @@ public:
|
||||
/** Address book entry changed.
|
||||
* @note called with lock cs_wallet held.
|
||||
*/
|
||||
boost::signals2::signal<void (CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged;
|
||||
CSignal<void(CWallet*, const CTxDestination&, const std::string&, bool, ChangeType)> NotifyAddressBookChanged;
|
||||
|
||||
/** Wallet transaction added, removed or updated.
|
||||
* @note called with lock cs_wallet held.
|
||||
*/
|
||||
boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, ChangeType status)> NotifyTransactionChanged;
|
||||
CSignal<void(CWallet*, const uint256&, ChangeType)> NotifyTransactionChanged;
|
||||
};
|
||||
|
||||
/** A key allocated from the key pool. */
|
||||
|
||||
Reference in New Issue
Block a user