fix: icon permissions and GTK single instance

- Use GTK default flags for single instance
- Fix icon path to absolute /usr/share/icons
- Add postinstall chmod 644 for icon
- Remove Dart-level single instance code
This commit is contained in:
Umbrix Developer
2026-01-17 20:10:04 +03:00
parent 9300488d2b
commit 43ab81e8d1
7 changed files with 62 additions and 127 deletions

View File

@@ -50,26 +50,26 @@ GoRouter router(RouterRef ref) {
}
final tabLocations = [
const HomeRoute().location, // 0: Главная
const ProxiesRoute().location, // 1: Локации
const PerAppProxyRoute().location, // 2: Исключения
const SettingsRoute().location, // 3: Настройки
const AboutRoute().location, // 4: О программе
const HomeRoute().location, // 0: Главная
const ProxiesRoute().location, // 1: Локации
const PerAppProxyRoute().location, // 2: Исключения
const SettingsRoute().location, // 3: Настройки
const AboutRoute().location, // 4: О программе
];
int getCurrentIndex(BuildContext context) {
final String location = GoRouterState.of(context).uri.path;
// Проверяем точное совпадение для главной
if (location == const HomeRoute().location) return 0;
// Проверяем остальные маршруты по порядку
// ВАЖНО: более длинные пути проверяем раньше!
if (location.startsWith(const PerAppProxyRoute().location)) return 2; // /settings/per-app-proxy
if (location.startsWith(const ProxiesRoute().location)) return 1; // /proxies
if (location.startsWith(const SettingsRoute().location)) return 3; // /settings
if (location.startsWith(const AboutRoute().location)) return 4; // /about
if (location.startsWith(const ProxiesRoute().location)) return 1; // /proxies
if (location.startsWith(const SettingsRoute().location)) return 3; // /settings
if (location.startsWith(const AboutRoute().location)) return 4; // /about
return 0;
}

View File

@@ -172,49 +172,49 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
// О программе
Builder(
builder: (context) {
final t = ref.watch(translationsProvider);
return _DrawerMenuItem(
icon: FluentIcons.info_24_regular,
selectedIcon: FluentIcons.info_24_filled,
label: t.about.pageTitle,
isSelected: false,
onTap: () {
RootScaffold.stateKey.currentState?.closeDrawer();
const AboutRoute().push(context);
},
);
},
),
// Настройки
Builder(
builder: (context) {
final t = ref.watch(translationsProvider);
return _DrawerMenuItem(
icon: FluentIcons.settings_24_regular,
selectedIcon: FluentIcons.settings_24_filled,
label: t.settings.pageTitle,
isSelected: false,
onTap: () {
RootScaffold.stateKey.currentState?.closeDrawer();
const SettingsRoute().push(context);
},
);
},
),
const SizedBox(height: 16),
const Divider(),
const _DrawerThemeItem(),
const _DrawerLanguageItem(),
const _DrawerLicensesItem(),
],
),
// О программе
Builder(
builder: (context) {
final t = ref.watch(translationsProvider);
return _DrawerMenuItem(
icon: FluentIcons.info_24_regular,
selectedIcon: FluentIcons.info_24_filled,
label: t.about.pageTitle,
isSelected: false,
onTap: () {
RootScaffold.stateKey.currentState?.closeDrawer();
const AboutRoute().push(context);
},
);
},
),
// Настройки
Builder(
builder: (context) {
final t = ref.watch(translationsProvider);
return _DrawerMenuItem(
icon: FluentIcons.settings_24_regular,
selectedIcon: FluentIcons.settings_24_filled,
label: t.settings.pageTitle,
isSelected: false,
onTap: () {
RootScaffold.stateKey.currentState?.closeDrawer();
const SettingsRoute().push(context);
},
);
},
),
const SizedBox(height: 16),
const Divider(),
const _DrawerThemeItem(),
const _DrawerLanguageItem(),
const _DrawerLicensesItem(),
],
),
),
],
),
),
body: AdaptiveLayout(
primaryNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{

View File

@@ -67,16 +67,16 @@ class PerAppProxyPage extends HookConsumerWidget with PresLogger {
tooltip: localizations.searchFieldLabel,
),
],
bottom: PlatformUtils.isDesktop
? null // На Desktop только вкладка "Домены"
: TabBar(
controller: tabController,
onTap: (index) => currentTab.value = index,
tabs: [
Tab(text: t.settings.network.excludedDomains.domainsTab),
Tab(text: t.settings.network.excludedDomains.appsTab),
],
),
bottom: PlatformUtils.isDesktop
? null // На Desktop только вкладка "Домены"
: TabBar(
controller: tabController,
onTap: (index) => currentTab.value = index,
tabs: [
Tab(text: t.settings.network.excludedDomains.domainsTab),
Tab(text: t.settings.network.excludedDomains.appsTab),
],
),
);
final searchAppBar = SliverAppBar(

View File

@@ -19,11 +19,6 @@ class WindowNotifier extends _$WindowNotifier with AppLogger {
Future<void> build() async {
if (!PlatformUtils.isDesktop) return;
// if (Platform.isWindows) {
// loggy.debug("ensuring single instance");
// await WindowsSingleInstance.ensureSingleInstance([], "Hiddify");
// }
await windowManager.ensureInitialized();
await windowManager.setMinimumSize(minimumWindowSize);
await windowManager.setSize(defaultWindowSize);

View File

@@ -1,70 +1,9 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:umbrix/bootstrap.dart';
import 'package:umbrix/core/model/environment.dart';
final lockFile = File('/tmp/umbrix.lock');
Future<void> _activateExistingWindow() async {
// Try to activate existing window via wmctrl
try {
final result = await Process.run('wmctrl', ['-a', 'Umbrix']);
if (result.exitCode == 0) return;
} catch (e) {
// wmctrl not available, try xdotool
try {
await Process.run('xdotool', ['search', '--name', 'Umbrix', 'windowactivate']);
} catch (e) {
// No window activation possible
}
}
}
void _cleanupLockFile() {
try {
if (lockFile.existsSync()) {
lockFile.deleteSync();
}
} catch (e) {
// Ignore cleanup errors
}
}
void main() async {
// Single instance check - BEFORE Flutter initialization
if (Platform.isLinux || Platform.isWindows) {
if (await lockFile.exists()) {
// Check if process is still alive
try {
final pidString = await lockFile.readAsString();
final pid = int.tryParse(pidString.trim());
if (pid != null) {
final result = await Process.run('ps', ['-p', pid.toString()]);
if (result.exitCode != 0) {
// Process dead, remove stale lock
await lockFile.delete();
} else {
// Process alive - activate window and exit
await _activateExistingWindow();
exit(0);
}
}
} catch (e) {
// Error reading lock, remove it
try { await lockFile.delete(); } catch (e) {}
}
}
// Create lock file
try {
await lockFile.create();
await lockFile.writeAsString(pid.toString());
} catch (e) {
// If can't create, continue anyway
}
}
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);