Files
LorenEteval_Furious/Furious/Plugins

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:

  • ProtocolHandler owns one protocol's URI schemes, profile recognition, blank profile, URI export, and optional editor.
  • CoreBackend owns process startup, routing, TUN, tests, versions, logs, and backend maintenance for one or more configuration types.
  • SubscriptionDecoder converts 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.