290 lines
9.6 KiB
Dart
290 lines
9.6 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:sqlite3/sqlite3.dart';
|
|
import 'package:xp_nix/src/monitors/productivity_monitor.dart';
|
|
import 'package:xp_nix/src/config/config_manager.dart';
|
|
import 'package:xp_nix/src/logging/logger.dart';
|
|
import 'package:xp_nix/src/web/dashboard_server.dart';
|
|
import 'package:xp_nix/src/database/database_manager.dart';
|
|
import 'package:xp_nix/src/providers/system_time_provider.dart';
|
|
import 'package:xp_nix/src/build/dashboard_builder.dart';
|
|
// Window manager detection and factories
|
|
import 'package:xp_nix/src/detectors/window_manager_detector.dart';
|
|
import 'package:xp_nix/src/factories/idle_monitor_factory.dart';
|
|
import 'package:xp_nix/src/factories/activity_detector_factory.dart';
|
|
import 'package:xp_nix/src/factories/desktop_enhancer_factory.dart';
|
|
// Interfaces
|
|
import 'package:xp_nix/src/interfaces/i_idle_monitor.dart';
|
|
import 'package:xp_nix/src/interfaces/i_activity_detector.dart';
|
|
import 'package:xp_nix/src/interfaces/i_desktop_enhancer.dart';
|
|
|
|
late final Database _db;
|
|
late final IIdleMonitor _idleMonitor;
|
|
late final SystemTimeProvider _timeProvider;
|
|
late final IDesktopEnhancer _desktopEnhancer;
|
|
late final IActivityDetector? _activityDetector;
|
|
late final ProductivityMonitor _monitor;
|
|
late final DashboardServer _dashboardServer;
|
|
|
|
// Enhanced main function with interactive commands and one-shot mode
|
|
void main(List<String> args) async {
|
|
// Parse command line arguments
|
|
String dbPath = 'productivity_tracker.db';
|
|
int port = 8080;
|
|
|
|
for (int i = 0; i < args.length; i++) {
|
|
switch (args[i]) {
|
|
case '--db':
|
|
if (i + 1 < args.length) {
|
|
dbPath = args[i + 1];
|
|
i++; // Skip next argument as it's the db path
|
|
} else {
|
|
print('Error: --db flag requires a database path');
|
|
exit(1);
|
|
}
|
|
break;
|
|
case '--port':
|
|
if (i + 1 < args.length) {
|
|
port = int.tryParse(args[i + 1]) ?? 8080;
|
|
i++; // Skip next argument as it's the port number
|
|
} else {
|
|
print('Error: --port flag requires a port number');
|
|
exit(1);
|
|
}
|
|
break;
|
|
case '--help':
|
|
case '-h':
|
|
print('''
|
|
XP Nix Server - Productivity Tracking with Gamification
|
|
|
|
A productivity tracking system that works with both Hyprland and Sway window managers.
|
|
Automatically detects your environment and adapts functionality accordingly.
|
|
|
|
Usage: dart xp_nix.dart [options]
|
|
|
|
Options:
|
|
--db PATH Database file path (default: productivity_tracker.db)
|
|
--port PORT Server port (default: 8080)
|
|
--help, -h Show this help message
|
|
|
|
Supported Window Managers:
|
|
• Hyprland: Full visual effects, idle monitoring, activity detection
|
|
• Sway: Activity detection, idle monitoring (no visual effects)
|
|
• Detection: Automatic via environment variables
|
|
|
|
Examples:
|
|
dart xp_nix.dart
|
|
dart xp_nix.dart --db test_data.db --port 8081
|
|
|
|
Requirements:
|
|
• Hyprland: hypridle, hyprctl
|
|
• Sway: swayidle, swaymsg
|
|
''');
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
print('🗄️ Using database: $dbPath');
|
|
print('🌐 Starting server on port: $port');
|
|
|
|
// Initialize logging system
|
|
await Logger.instance.initialize(level: LogLevel.info, logDirectory: 'logs', maxFileSizeMB: 10, maxFiles: 5);
|
|
|
|
// Initialize configuration manager
|
|
await ConfigManager.instance.initialize();
|
|
|
|
// Detect window manager and display information
|
|
final windowManager = WindowManagerDetector.instance.detect();
|
|
final detectionInfo = WindowManagerDetector.instance.getDetectionInfo();
|
|
|
|
print('🖼️ Detected window manager: ${windowManager.toString()}');
|
|
print(' Detection method: ${detectionInfo['detection_method']}');
|
|
|
|
// Log additional environment info
|
|
Logger.info('Window manager detection: $detectionInfo');
|
|
|
|
_db = sqlite3.open(dbPath);
|
|
|
|
// Create window manager-specific dependencies using factories
|
|
try {
|
|
_idleMonitor = IdleMonitorFactory.create(windowManager);
|
|
_desktopEnhancer = DesktopEnhancerFactory.create(windowManager);
|
|
_activityDetector = ActivityDetectorFactory.create(windowManager);
|
|
_timeProvider = SystemTimeProvider();
|
|
|
|
// Validate requirements for the detected window manager
|
|
final idleSupported = await IdleMonitorFactory.validateRequirements(windowManager);
|
|
final activitySupported = await ActivityDetectorFactory.validateRequirements(windowManager);
|
|
final enhancerSupported = await DesktopEnhancerFactory.validateRequirements(windowManager);
|
|
|
|
print('💡 Component availability:');
|
|
print(' Idle monitoring: ${idleSupported ? '✅' : '❌'}');
|
|
print(' Activity detection: ${activitySupported ? '✅' : '❌'}');
|
|
print(' Visual enhancements: ${enhancerSupported ? '✅' : '❌'}');
|
|
|
|
if (!idleSupported) {
|
|
print(' ⚠️ Idle monitoring may not work properly');
|
|
}
|
|
if (!activitySupported) {
|
|
print(' ⚠️ Activity detection will fall back to legacy polling');
|
|
}
|
|
if (!enhancerSupported) {
|
|
print(' ⚠️ Visual enhancements may be limited');
|
|
}
|
|
|
|
} catch (e) {
|
|
print('❌ Error creating window manager components: $e');
|
|
print(' Falling back to legacy mode...');
|
|
|
|
// Fallback to original Hyprland implementations (import at top of file)
|
|
// Note: This requires adding imports back if needed
|
|
Logger.error('Component creation failed, falling back to legacy mode: $e');
|
|
exit(1); // For now, exit on component creation failure
|
|
}
|
|
|
|
// Create monitor with dependency injection
|
|
_monitor = ProductivityMonitor(
|
|
db: _db,
|
|
idleMonitor: _idleMonitor,
|
|
timeProvider: _timeProvider,
|
|
desktopEnhancer: _desktopEnhancer,
|
|
activityDetector: _activityDetector, // May be null for fallback to legacy polling
|
|
);
|
|
|
|
_dashboardServer = DashboardServer.withDatabase(DatabaseManager(_db));
|
|
|
|
ProcessSignal.sigint.watch().listen((_) async {
|
|
Logger.info('Shutting down XP Nix...');
|
|
print('\nShutting down...');
|
|
_quit();
|
|
});
|
|
|
|
// Start the dashboard server
|
|
try {
|
|
await _dashboardServer.start(port);
|
|
Logger.info('Dashboard available at: ${_dashboardServer.dashboardUrl}');
|
|
print('🌐 Dashboard available at: ${_dashboardServer.dashboardUrl}');
|
|
} catch (e) {
|
|
Logger.error('Failed to start dashboard server: $e');
|
|
print('⚠️ Dashboard server failed to start: $e');
|
|
_quit();
|
|
}
|
|
|
|
_monitor.start();
|
|
_monitor.printDetailedStats();
|
|
|
|
// Add command listener for manual controls
|
|
stdin.transform(utf8.decoder).transform(LineSplitter()).listen((line) {
|
|
final parts = line.trim().split(' ');
|
|
final command = parts[0].toLowerCase();
|
|
|
|
switch (command) {
|
|
case 'stats':
|
|
_monitor.printDetailedStats();
|
|
break;
|
|
case 'test':
|
|
if (parts.length > 1) {
|
|
final levelOrCommand = parts[1].toLowerCase();
|
|
if (levelOrCommand == 'overlay') {
|
|
_testOverlay(parts.length > 2 ? int.tryParse(parts[2]) : null);
|
|
} else {
|
|
final level = int.tryParse(levelOrCommand) ?? 1;
|
|
_monitor.testTheme(level);
|
|
}
|
|
} else {
|
|
print('Usage: test [level|overlay] - Specify a level number or "overlay" to test XP overlay');
|
|
}
|
|
break;
|
|
case 'restore':
|
|
_monitor.restoreDesktop();
|
|
break;
|
|
case 'refresh':
|
|
_monitor.refreshConfig();
|
|
break;
|
|
case 'build':
|
|
_handleBuildCommand();
|
|
break;
|
|
case 'help':
|
|
print('''
|
|
Available commands:
|
|
- stats: Show current productivity stats
|
|
- test [level]: Test theme for specific level
|
|
- test overlay [value]: Test XP overlay at cursor position
|
|
- restore: Restore desktop backup
|
|
- refresh: Refresh base config from current system config
|
|
- build: Build Flutter dashboard and copy to static files
|
|
- help: Show this help
|
|
''');
|
|
break;
|
|
}
|
|
});
|
|
|
|
print('💡 Type "help" for available commands');
|
|
|
|
// Keep running and show stats periodically
|
|
while (true) {
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
if (DateTime.now().second == 0 && DateTime.now().minute % 10 == 0) {
|
|
_monitor.printDetailedStats();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handles the build command to rebuild the Flutter dashboard
|
|
void _handleBuildCommand() async {
|
|
print('🚀 Starting dashboard build process...');
|
|
|
|
// Check if Flutter is available
|
|
if (!await DashboardBuilder.checkFlutterAvailable()) {
|
|
print('❌ Flutter is not available in the system PATH');
|
|
print(' Please ensure Flutter is installed and available in your PATH');
|
|
return;
|
|
}
|
|
|
|
// Build the dashboard
|
|
final success = await DashboardBuilder.buildDashboard();
|
|
|
|
if (success) {
|
|
print('🎉 Dashboard build completed successfully!');
|
|
print(' The server is now serving the updated Flutter dashboard');
|
|
} else {
|
|
print('❌ Dashboard build failed. Check the logs for more details.');
|
|
}
|
|
}
|
|
|
|
/// Test the XP overlay with an optional XP value
|
|
Future<void> _testOverlay(int? xpValue) async {
|
|
print('🧪 Testing XP overlay system...');
|
|
final windowManager = WindowManagerDetector.instance.detect();
|
|
|
|
if (windowManager == WindowManager.unknown) {
|
|
print('❌ No supported window manager detected');
|
|
return;
|
|
}
|
|
|
|
final overlayManager = _monitor.cursorOverlayManager;
|
|
if (overlayManager == null) {
|
|
print('❌ Overlay manager not initialized');
|
|
return;
|
|
}
|
|
|
|
final available = await overlayManager.isAvailable();
|
|
if (!available) {
|
|
print('❌ Overlay system is not available. Check logs for details.');
|
|
return;
|
|
}
|
|
|
|
print('✅ Overlay system is available, showing test overlay...');
|
|
await overlayManager.testOverlay(testXP: xpValue ?? 42);
|
|
}
|
|
|
|
Future<void> _quit() async {
|
|
_monitor.stop();
|
|
await _dashboardServer.stop();
|
|
await Logger.instance.dispose();
|
|
_db.dispose();
|
|
exit(0);
|
|
}
|