Skip to content

Commit c0568df

Browse files
committed
fix: windows logic service
fix: Linux arm build
1 parent 417e5f4 commit c0568df

File tree

5 files changed

+116
-13
lines changed

5 files changed

+116
-13
lines changed

lib/common/system.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ class System {
7979
}
8080

8181
if (Platform.isWindows) {
82-
final result = await windows?.registerService();
82+
// First, try to start existing service without UAC
83+
final startedWithoutUac = await windows?.tryStartExistingService();
84+
if (startedWithoutUac == true) {
85+
return AuthorizeCode.success;
86+
}
87+
88+
// Service not installed or couldn't start - need to install with UAC
89+
final result = await windows?.installService();
8390
if (result == true) {
8491
return AuthorizeCode.success;
8592
}

lib/common/windows.dart

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,21 @@ class Windows {
246246
return WindowsHelperServiceStatus.presence;
247247
}
248248

249-
Future<bool> registerService() async {
249+
// SDDL string that grants Interactive Users (IU) the following rights:
250+
// RP = SERVICE_START (start the service)
251+
// WP = SERVICE_STOP (stop the service)
252+
// LC = SERVICE_QUERY_STATUS (query service status)
253+
// LO = SERVICE_INTERROGATE (interrogate the service)
254+
// RC = READ_CONTROL (read security descriptor)
255+
// This allows non-admin users to start/stop/query the service without UAC
256+
static const String _serviceSecurityDescriptor =
257+
'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;RPWPLCLORC;;;IU)';
258+
259+
/// Install the helper service (requires UAC elevation).
260+
/// This should only be called when the service is not installed.
261+
/// After installation, sets security descriptor to allow non-admin users
262+
/// to start/stop the service without UAC.
263+
Future<bool> installService() async {
250264
final status = await checkService();
251265

252266
if (status == WindowsHelperServiceStatus.running) {
@@ -261,7 +275,6 @@ class Windows {
261275
"sc",
262276
"delete",
263277
appHelperService,
264-
"/force",
265278
"&&",
266279
],
267280
"sc",
@@ -270,6 +283,12 @@ class Windows {
270283
'binPath= "${appPath.helperPath}"',
271284
'start= auto',
272285
"&&",
286+
// Set security descriptor to allow non-admin users to start/stop the service
287+
"sc",
288+
"sdset",
289+
appHelperService,
290+
_serviceSecurityDescriptor,
291+
"&&",
273292
"sc",
274293
"start",
275294
appHelperService,
@@ -284,6 +303,46 @@ class Windows {
284303
return res;
285304
}
286305

306+
/// Try to start an existing service without UAC.
307+
/// Returns true if the service was started successfully or is already running.
308+
/// Returns false if the service is not installed or failed to start.
309+
Future<bool> tryStartExistingService() async {
310+
final status = await checkService();
311+
312+
if (status == WindowsHelperServiceStatus.running) {
313+
return true;
314+
}
315+
316+
if (status == WindowsHelperServiceStatus.none) {
317+
return false;
318+
}
319+
320+
// Service exists but not running - try to start it without elevation
321+
final result = await Process.run('sc', ['start', appHelperService]);
322+
323+
if (result.exitCode == 0) {
324+
// Wait for service to fully start
325+
await Future.delayed(const Duration(milliseconds: 500));
326+
// Verify it's actually running and responding
327+
final newStatus = await checkService();
328+
return newStatus == WindowsHelperServiceStatus.running;
329+
}
330+
331+
return false;
332+
}
333+
334+
/// Register the service - will request UAC only if service is not installed.
335+
/// If the service is already installed, it will try to start it without UAC.
336+
Future<bool> registerService() async {
337+
// First, try to start existing service without UAC
338+
if (await tryStartExistingService()) {
339+
return true;
340+
}
341+
342+
// Service not installed or couldn't start - need to install with UAC
343+
return installService();
344+
}
345+
287346
Future<bool> startService() async {
288347
final status = await checkService();
289348

lib/main.dart

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,25 @@ Future<void> _service(List<String> flags) async {
142142
if (res.isNotEmpty) {
143143
commonPrint.log("TileService: Start failed with error: $res");
144144
unawaited(app?.tip("Start failed: $res"));
145-
await vpn?.stop();
145+
try {
146+
await vpn?.stop();
147+
} catch (e) {
148+
debugPrint("Tile vpn.stop() error (ignored): $e");
149+
}
146150
exit(0);
147151
}
148152

149153
commonPrint.log("TileService: Starting VPN service");
150-
await vpn?.start(
151-
clashLibHandler.getAndroidVpnOptions(),
152-
);
153-
commonPrint.log("TileService: VPN service started");
154+
try {
155+
await vpn?.start(
156+
clashLibHandler.getAndroidVpnOptions(),
157+
);
158+
commonPrint.log("TileService: VPN service started");
159+
} catch (e) {
160+
// MissingPluginException may occur if VpnPlugin not yet attached
161+
// VPN is started by native side via VpnPlugin.handleStart()
162+
commonPrint.log("TileService: vpn.start() error (may be handled by native): $e");
163+
}
154164

155165
commonPrint.log("TileService: Starting listener");
156166
clashLibHandler.startListener();
@@ -160,17 +170,27 @@ Future<void> _service(List<String> flags) async {
160170
commonPrint.log("Error: $e");
161171
commonPrint.log("StackTrace: $stackTrace");
162172
unawaited(app?.tip("Start error: $e"));
163-
await vpn?.stop();
173+
try {
174+
await vpn?.stop();
175+
} catch (stopError) {
176+
debugPrint("Tile vpn.stop() error (ignored): $stopError");
177+
}
164178
exit(0);
165179
}
166180
},
167181
onStop: () async {
168182
try {
169183
unawaited(app?.tip(appLocalizations.stopVpn));
170184
clashLibHandler.stopListener();
185+
} catch (e) {
186+
debugPrint("Tile stop listener error: $e");
187+
}
188+
try {
171189
await vpn?.stop();
172190
} catch (e) {
173-
debugPrint("Tile stop error: $e");
191+
// MissingPluginException may occur if VpnPlugin not yet attached
192+
// VPN will be stopped by native side via VpnPlugin.handleStop()
193+
debugPrint("Tile vpn.stop() error (ignored): $e");
174194
}
175195
exit(0);
176196
},

linux/my_application.cc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,18 @@ static void my_application_activate(GApplication* application) {
8989
}
9090

9191
gtk_window_set_default_size(window, 1280, 720);
92-
gtk_widget_show(GTK_WIDGET(window));
9392

9493
g_autoptr(FlDartProject) project = fl_dart_project_new();
9594
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
9695

9796
FlView* view = fl_view_new(project);
98-
gtk_widget_show(GTK_WIDGET(view));
9997
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
10098

99+
// Show widgets after the view has been added to the window to avoid
100+
// early exposes without a ready EGL/GL surface on low-power GPUs.
101+
gtk_widget_show(GTK_WIDGET(view));
102+
gtk_widget_show(GTK_WIDGET(window));
103+
101104
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
102105

103106
gtk_widget_grab_focus(GTK_WIDGET(view));
@@ -124,6 +127,20 @@ static gboolean my_application_local_command_line(GApplication* application, gch
124127

125128
// Implements GApplication::startup.
126129
static void my_application_startup(GApplication* application) {
130+
// On some Raspberry Pi environments, setting safer defaults helps the
131+
// Flutter engine initialize EGL/GL correctly. Only set if not already
132+
// provided by the user environment.
133+
#if defined(__arm__) || defined(__aarch64__)
134+
if (!g_getenv("GDK_BACKEND")) {
135+
g_setenv("GDK_BACKEND", "x11", FALSE);
136+
}
137+
if (!g_getenv("GDK_GL")) {
138+
g_setenv("GDK_GL", "gles", FALSE);
139+
}
140+
if (!g_getenv("LIBGL_DRI3_DISABLE")) {
141+
g_setenv("LIBGL_DRI3_DISABLE", "1", FALSE);
142+
}
143+
#endif
127144
//MyApplication* self = MY_APPLICATION(object);
128145

129146
// Perform any actions required at application startup.

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: flclashx
22
description: Fork multi-platform proxy client FlClash based on ClashMeta, simple and easy to use, open-source and ad-free.
33
publish_to: "none"
4-
version: 0.3.0+2026012001
4+
version: 0.3.1+2026012101
55

66
environment:
77
sdk: ">=3.5.0 <4.0.0"

0 commit comments

Comments
 (0)