How FlClash's Proxy Manager Configures System-Wide Proxy Settings

FlClash's proxy manager configures system-wide proxy settings by listening to Riverpod state changes in ProxyState and invoking platform-specific commands via the Proxy service to set OS-level HTTP/HTTPS proxies when isStart and systemProxy flags are both true.

FlClash is a cross-platform GUI client for the Clash rule-based proxy engine. To route all system traffic through the local Clash instance, the application must bridge the gap between the Dart-based Flutter UI and the underlying operating system's network configuration. This is accomplished through a reactive proxy management layer that translates user preferences into native network stack modifications.

Architecture Overview

The system-wide proxy configuration flow follows a reactive pattern from UI to kernel. When a user toggles the System Proxy option in the network settings, the change propagates through Riverpod state management to the ProxyManager class. This manager acts as the orchestration layer, deciding whether to activate or deactivate OS-level proxy settings based on two critical conditions: whether the Clash core is running (isStart) and whether the user has enabled system proxy exposure (systemProxy).

The actual platform-specific implementation resides in the Proxy service located in the plugins/proxy package. This service abstracts the differences between macOS, Linux, and Windows into a unified interface while delegating to native command-line tools or platform channels for the actual network stack manipulation.

State Management and Listening

The ProxyManager widget registers a manual listener on the proxyStateProvider to react immediately to any changes in proxy configuration. This listener triggers the update scheduling mechanism whenever the state transitions.

In lib/manager/proxy_manager.dart, the listener is initialized as follows:

ref.listenManual(proxyStateProvider, (prev, next) {
  if (prev != next) {
    _scheduleUpdateProxy(next);
  }
}, fireImmediately: true);

This ensures that the system proxy configuration is applied immediately when the app starts (if conditions are met) and whenever the user modifies proxy settings through the UI. The _scheduleUpdateProxy method eventually delegates to _updateProxy, which contains the core decision logic for applying or removing system-wide settings.

Decision Logic for Proxy Activation

The _updateProxy method evaluates the current ProxyState to determine whether to enable or disable the system proxy. It checks two boolean fields: isStart (indicating the Clash core is active) and systemProxy (indicating the user's preference to expose the proxy to the OS).

From lib/manager/proxy_manager.dart, lines 21-30:

final isStart = proxyState.isStart;
final systemProxy = proxyState.systemProxy;
if (isStart && systemProxy) {
  result = await proxy?.startProxy(port, proxyState.bassDomain);
} else {
  result = await proxy?.stopProxy();
}

Only when both conditions are true does the manager invoke startProxy with the current port and bypass domain list. If either condition fails—such as when the user stops the Clash core or disables the system proxy toggle—the manager immediately calls stopProxy to restore the system's original network configuration.

Platform-Specific Implementations

The Proxy service in plugins/proxy/lib/proxy.dart implements distinct strategies for each supported operating system, ensuring compatibility with native network configuration mechanisms.

macOS Implementation

On macOS, FlClash utilizes the networksetup command-line utility to configure system-wide proxies. The implementation iterates over all detected network services and applies HTTP and HTTPS proxy settings pointing to 127.0.0.1:<port>.

final devices = await _getNetworkDeviceListWithMacos();
final commands = devices.expand(
  (dev) => _buildMacosStartCommands(dev, port, bypassDomain),
);
return _runCommands(commands);

This approach modifies the network settings for all active interfaces, ensuring that applications using the system's network stack route traffic through the local Clash instance.

Linux Implementation

For Linux, the service detects the current desktop environment via the XDG_CURRENT_DESKTOP environment variable and selects the appropriate configuration backend. Supported environments include GNOME, MATE, and KDE Plasma.

The implementation generates specific commands for each backend:

  • GNOME: Uses gsettings to modify org.gnome.system.proxy schemas
  • KDE: Uses dconf or direct configuration file manipulation
final commands = await _resolveLinuxStartCommands(
  port, bypassDomain,
  desktop: Platform.environment['XDG_CURRENT_DESKTOP'],
  homeDir: homeDir,
);
return _runCommands(commands);

This desktop-aware approach ensures compatibility across different Linux distributions and windowing systems without requiring elevated privileges beyond standard user session capabilities.

Windows Implementation

On Windows, the implementation delegates to a native platform plugin implemented in C++/Rust. The Dart layer calls the platform interface methods ProxyPlatform.instance.startProxy and stopProxy, which manipulate the Windows registry and WinINET settings to configure the system-wide proxy.

'windows' => await ProxyPlatform.instance.startProxy(port, bypassDomain),

This native implementation handles the complexity of Windows proxy configuration, including the system-level registry keys required for global traffic routing.

Command Execution and Error Handling

All platform-specific implementations converge on the _runCommands method, which sequentially executes each ProxyCommand using Dart's Process.run API. The method validates exit codes to ensure successful application of settings.

From plugins/proxy/lib/proxy.dart, lines 33-46:

Future<bool> _runCommands(Iterable<ProxyCommand> commands) async {
  for (final command in commands) {
    final result = await Process.run(
      command.executable,
      command.arguments,
      runInShell: true,
    );
    if (result.exitCode != 0) {
      return false;
    }
  }
  return true;
}

If any command returns a non-zero exit code, the entire operation is reported as failed, allowing the UI to display appropriate error messages and revert state if necessary.

Enabling System Proxy via UI

Users can toggle system-wide proxying through the Network configuration screen. The UI binds to the networkSettingProvider and updates the systemProxy field when the user interacts with the toggle switch.

In lib/views/config/network.dart:

final systemProxy = ref.watch(networkSettingProvider.select((s) => s.systemProxy));

SwitchListTile.adaptive(
  title: Text(appLocalizations.systemProxy),
  subtitle: Text(appLocalizations.systemProxyDesc),
  value: systemProxy,
  onChanged: (value) => ref
      .read(networkSettingProvider.notifier)
      .update((state) => state.copyWith(systemProxy: value)),
);

Programmatic control is also available through direct state mutation:

ref.read(proxyStateProvider.notifier).update((state) {
  return state.copyWith(
    isStart: true,
    systemProxy: true,
    port: 7890,
    bassDomain: [],
  );
});

This state change triggers the listener chain described above, resulting in immediate application of system-wide proxy settings.

Summary

  • FlClash uses a reactive architecture where ProxyManager listens to proxyStateProvider for changes in isStart and systemProxy flags.
  • The decision to enable system-wide proxying occurs in _updateProxy within lib/manager/proxy_manager.dart, requiring both the Clash core to be running and the user to have enabled the system proxy option.
  • Platform-specific implementations reside in plugins/proxy/lib/proxy.dart, using networksetup for macOS, desktop-environment-specific commands for Linux, and native platform channels for Windows.
  • The _runCommands utility executes native CLI commands sequentially, validating exit codes to ensure successful configuration changes.
  • UI controls in lib/views/config/network.dart allow users to toggle the systemProxy setting, which propagates through Riverpod to trigger OS-level network configuration.

Frequently Asked Questions

How does FlClash know when to apply system proxy settings?

FlClash monitors the proxyStateProvider Riverpod stream via ref.listenManual in the ProxyManager class. Whenever the ProxyState object changes—such as when the user toggles the system proxy switch or starts the Clash core—the listener triggers _scheduleUpdateProxy, which evaluates the current state and calls the appropriate platform-specific methods to apply or remove proxy settings.

What happens if I enable system proxy but Clash isn't running?

The ProxyManager performs a safety check in its _updateProxy method, requiring both isStart (Clash core running) and systemProxy (user preference) to be true before calling startProxy. If the core is not running, the manager invokes stopProxy instead, ensuring that the system does not attempt to route traffic through a non-existent local proxy endpoint, which would result in connection failures.

Which Linux desktop environments does FlClash support for system proxy configuration?

According to the source code in plugins/proxy/lib/proxy.dart, FlClash detects the XDG_CURRENT_DESKTOP environment variable to determine the active session and supports GNOME, MATE, and KDE Plasma. Each environment receives specific command generation logic—for example, gsettings for GNOME-based environments—ensuring compatibility across major Linux distributions without requiring manual configuration.

How are proxy bypass domains handled in the system-wide configuration?

The startProxy method accepts a bypassDomain parameter (referenced as bassDomain in the state object) that specifies domains which should bypass the proxy. These domains are converted into platform-specific exclusion rules—for instance, passed to networksetup on macOS or incorporated into GNOME proxy exceptions on Linux—ensuring that traffic to specified domains routes directly rather than through the Clash tunnel.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →