Furious plugins
Furious.Plugins is the extension host. It contains contracts, lifecycle, and
capability indexes, but no proxy-core implementation. Bundled proxy cores live
under Furious.Backends; bundled non-core extensions live under
Furious.Extensions.
The API separates three capabilities that can evolve independently:
ProtocolHandlerowns one protocol's URI schemes, profile recognition, blank profile, URI export, and optional editor.CoreBackendowns process startup, routing, TUN, tests, versions, logs, and backend maintenance for one or more configuration types.SubscriptionDecoderconverts one subscription representation into URI or normalized mapping entries. Importing those entries remains a host concern.
A plugin is a small container for any combination of these capabilities. A subscription-format plugin therefore does not appear as a proxy core, and a protocol handler can target an existing backend without inheriting its process implementation.
Dispatch and ownership
Protocol IDs, declared URI schemes, backend IDs, decoder IDs, configuration types, and core types are validated at registration. A mapping/editor-only protocol may declare no URI scheme. Declared URI schemes are indexed directly; the importer does not scan plugins until one happens to accept a string. Duplicate schemes fail during registration, avoiding order-dependent imports.
Mapping recognition is capability-driven because a mapping has no required URI scheme. The host rejects an ambiguous mapping when multiple handlers claim it. Configuration export and editor creation use the handler that explicitly owns the profile. Runtime operations use the independently resolved core backend.
Lifecycle and discovery
Third-party packages expose a FuriousPlugin through the
furious.plugins Python entry-point group:
[project.entry-points."furious.plugins"]
example = "furious_example.plugin:ExamplePlugin"
The host validates and indexes all capabilities, then calls
plugin.initialize(context). If initialization fails, registration is rolled
back. During application cleanup, initialized plugins receive shutdown() in
reverse order. Plugins execute in the Furious process and must be treated as
trusted Python code.
from Furious.Plugins import (
FuriousPlugin,
ProtocolDescriptor,
ProtocolHandler,
)
class ExampleProtocol(ProtocolHandler):
descriptor = ProtocolDescriptor(
id='example',
displayName='Example',
addActionText='Add Example Server...',
menuOrder=100,
)
schemes = ('example',)
def supports(self, configuration):
return isinstance(configuration, ExampleConfig)
def parse(self, uri, **kwargs):
return ExampleConfig.fromUri(uri, **kwargs)
def fromMapping(self, configuration, **kwargs):
if configuration.get('type') == 'example':
return ExampleConfig(configuration, **kwargs)
def blank(self, **kwargs):
return ExampleConfig.blank(**kwargs)
def export(self, configuration, remark=''):
return configuration.toUri(remark)
class ExamplePlugin(FuriousPlugin):
pluginId = 'example.protocol'
displayName = 'Example Protocol'
protocolHandlers = (ExampleProtocol(),)
Qt-dependent editors and actions should be imported only inside capability methods. Discovery may happen while the host's Qt package is still being initialized; metadata, protocol parsing, and backend configuration must remain usable without importing Qt widgets.
Routing labels are literal by default. A RoutingOption should set
translatable=True only when its display name is an application translation
key. User-provided labels must remain literal.