Furious plugins
Furious.Plugins is the extension host. It defines contracts, discovery,
lifecycle, and capability indexes; it does not implement a proxy protocol or
runtime itself. Official implementations live in Furious.Backends, while
extensions that are not proxy runtimes live in Furious.Extensions.
Plugin API version 3 is capability-based. A plugin is a metadata-bearing container for any combination of independent capabilities, not a synonym for a proxy core. The currently supported capability kinds are:
ProtocolHandler: recognizes one protocol, parses and exports share links, creates normalized connection documents, and validates them.ProtocolEditorProvider: lazily creates a PySide6 editor for one or more protocol IDs. Protocol parsing remains usable without importing Qt.SubscriptionDecoder: identifies and decodes one subscription wire format into URI or mapping entries. It does not create or persist profiles.KernelFactory: recognizes runtime configuration types and constructs a preparedKernelLaunchfrom aKernelRequest.TrafficStatsProvider: exposes an optional, worker-safe cumulative traffic counter query for runtime kernels that support monitoring.ActionProvider: contributes optional management UI without making the application assume that the owning plugin is a proxy core.PluginCapability: may also be subclassed for future utility extension points identified byCapabilityKind.Utility.
The registry exposes both a generic query API (capabilities, capability,
and pluginsWithCapability) and focused dispatch helpers. Adding a capability
does not require changing an enum of plugin types or a central protocol switch.
Metadata, declaration, and lifecycle
Every plugin declares immutable PluginMetadata and a sequence of capability
objects. Capability identifiers are unique within their kind. The registry also
validates protocol IDs, URI schemes, editor ownership, configuration types, and
kernel types before it mutates its indexes.
from Furious.Plugins import FuriousPlugin, PluginMetadata
class ExamplePlugin(FuriousPlugin):
metadata = PluginMetadata(
id='example.networking',
displayName='Example Networking',
version='1.0',
description='Example protocol and runtime support.',
provider='Example Organization',
)
def __init__(self):
self.capabilities = (
ExampleProtocol(),
ExampleProtocolEditor(),
ExampleKernelFactory(),
)
The host validates and indexes the complete declaration, then calls
plugin.initialize(context). Failed initialization rolls registration back.
During application cleanup, initialized plugins receive shutdown() in reverse
order.
Third-party distributions expose a FuriousPlugin class or instance through
the furious.plugins Python entry-point group:
[project.entry-points."furious.plugins"]
example = "furious_example.plugin:ExamplePlugin"
Plugins execute inside the Furious process and are trusted Python code.
Protocols and editors
A protocol handler owns connection semantics, never UI:
from Furious.Plugins import (
ProtocolDescriptor,
ProtocolHandler,
ProtocolParseResult,
)
class ExampleProtocol(ProtocolHandler):
descriptor = ProtocolDescriptor(
id='example',
displayName='Example',
addActionText='Add Example Server...',
menuOrder=100,
configurationSchema={
'required': ('address', 'port'),
},
# Third-party labels are literal by default. Official translation keys
# opt in explicitly.
translatable=False,
)
schemes = ('example',)
def supports(self, configuration):
return isinstance(configuration, ExampleConfig)
def parse(self, uri, **kwargs):
configuration, display_name = ExampleConfig.fromURI(uri)
return ProtocolParseResult(
configuration,
{'displayName': display_name},
)
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)
An optional editor capability binds Qt UI to the protocol ID. It imports Qt
widgets inside createEditor, because plugin discovery can occur while
Furious.Qt is still initializing:
from Furious.Plugins import ProtocolEditorProvider
class ExampleProtocolEditor(ProtocolEditorProvider):
editorId = 'example.qt-editor'
protocolIds = ('example',)
def createEditor(self, protocolId, parent=None, **kwargs):
from .Editor import ExampleEditor
return ExampleEditor(parent=parent, **kwargs)
The add/edit server UI enumerates registered ProtocolDescriptor values and
asks the registry for an editor. It contains no VMess/VLESS/Trojan-specific
factory switch. Headless protocol plugins may intentionally omit an editor.
Subscriptions
Subscription processing has three stages:
- The source layer downloads bytes and supplies source metadata.
- A
SubscriptionDecoderrecognizes the representation and emits normalizedSubscriptionItemvalues. Plain share links and Base64 share links are separate decoders, so Clash YAML or sing-box JSON can be added independently. SubscriptionImportServicedispatches each URI or mapping to a registered protocol handler, creates aServerProfile, and combines decoder-provided item metadata with source ID and update time. UI code only displays results and updates storage.
Decoder priority controls automatic format detection. A caller may also select a decoder explicitly by ID, which avoids content-guessing for a known source.
Runtime kernels
Runtime selection is based on registered configuration types. The connection manager asks the registry to create a kernel rather than branching on Xray, Hysteria, or another core:
from Furious.Plugins import KernelFactory, KernelLaunch
class ExampleKernelFactory(KernelFactory):
factoryId = 'example.runtime'
configurationTypes = (ExampleConfig,)
kernelTypes = (ExampleProcess,)
def create(self, request):
process = ExampleProcess(
exitCallback=request.exitCallback,
msgCallback=request.messageCallback,
)
return KernelLaunch(
kernel=process,
configuration=request.configuration,
options=request.options,
)
KernelRequest carries host-selected routing, callbacks, proxy-only mode, log
preference, and start options. KernelLaunch binds the constructed process to
its prepared arguments. TUN preparation, routing choices, download-test
configuration, version reporting, log parsing, and post-connect maintenance are
also dispatched through the selected factory.
Management actions are a distinct ActionProvider; a runtime factory therefore
does not import or own Qt UI merely because its plugin also offers settings.
Traffic monitoring is similarly optional. A TrafficStatsProvider associates
one or more kernel types with a TrafficStatsMonitor. The monitor contains a
picklable query callable and backend-specific target, allowing the host service
to run blocking native API calls outside the GUI process. The service exposes
both cumulative usage and calculated rates to Qt widgets, and suspends polling
while its parent widget is hidden or minimized.
Profiles and persistence
ServerProfile composes two different kinds of data:
connectionis aConfigFactorycontaining only protocol/core connection configuration.metadataisProfileMetadata, containing display name, group, tags, subscription source, update time, annotations, favorite state, and transient presentation measurements.
Storage schema version 2 persists these as separate metadata and connection
fields. The repository migrates the previous combined record format while
loading. Subscription synchronization can now replace a connection document
without losing user metadata, and configuration export cannot accidentally
serialize table state. URI parsers return a ProtocolParseResult, so even
fragment-derived display names do not pass through a connection model as
temporary attributes.
Routing labels are literal by default. RoutingOption.translatable should be
set only when displayName is an application translation key; user-provided
labels must remain literal. Protocol add-action labels follow the same rule via
ProtocolDescriptor.translatable. Plugin metadata display names are always
treated as literal names.