diff --git a/lib/process_finder.dart b/lib/process_finder.dart index d8308b0..2224ae0 100644 --- a/lib/process_finder.dart +++ b/lib/process_finder.dart @@ -123,7 +123,7 @@ class ProcessFinder { } /// Returns a list of running processes on the current system. - static List listRunningProcesses() { + static List listRunningProcesses({int priority = 8}) { final processes = []; final connected = _connect(); @@ -138,7 +138,7 @@ class ProcessFinder { // For example, query for all the running processes var hr = wbemServices.execQuery( TEXT('WQL'), - TEXT('SELECT * FROM Win32_Process WHERE Priority = 8'), + TEXT('SELECT * FROM Win32_Process WHERE Priority = $priority'), // ExecutablePath IS NOT NULL removed because apex legends WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_FORWARD_ONLY | WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_RETURN_IMMEDIATELY, diff --git a/lib/rewards/crash_process_widget.dart b/lib/rewards/crash_process_widget.dart index bb505df..2b3a2b8 100644 --- a/lib/rewards/crash_process_widget.dart +++ b/lib/rewards/crash_process_widget.dart @@ -1,5 +1,9 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:twitch_listener/process_finder.dart'; import 'package:twitch_listener/reward.dart'; import 'package:twitch_listener/reward_widget.dart'; import 'package:twitch_listener/themes.dart'; @@ -18,19 +22,19 @@ class CrashProcessWidget extends StatefulWidget { class _State extends State { @override void initState() { - _sceneNameController = TextEditingController(text: widget.action.target); + _processNameController = TextEditingController(text: widget.action.target); widget.saveHook.addHandler(_handleSave); super.initState(); } @override void dispose() { - _sceneNameController.dispose(); + _processNameController.dispose(); widget.saveHook.removeHandler(_handleSave); super.dispose(); } - late final TextEditingController _sceneNameController; + late final TextEditingController _processNameController; @override Widget build(BuildContext context) { @@ -44,17 +48,37 @@ class _State extends State { Expanded( child: TextFormField( maxLines: 1, - controller: _sceneNameController, + controller: _processNameController, style: const TextStyle( fontSize: 14, ), decoration: const DefaultInputDecoration(hintText: 'Process name'), ), - ) + ), + const Gap(8), + ElevatedButton(onPressed: _selectProcess, child: const Text('Select')) ]) ]); } + Future _selectProcess() async { + final processName = await showModalBottomSheet( + context: context, + constraints: const BoxConstraints(maxWidth: 512), + backgroundColor: const Color(0xFF404450), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(8), topLeft: Radius.circular(8)), + ), + builder: (context) { + return _ProcessListWidget(); + }); + + if (processName != null) { + _processNameController.text = processName; + } + } + @override void didUpdateWidget(covariant CrashProcessWidget oldWidget) { if (widget.saveHook != oldWidget.saveHook) { @@ -65,6 +89,110 @@ class _State extends State { } void _handleSave() { - widget.action.target = _sceneNameController.text.trim(); + widget.action.target = _processNameController.text.trim(); + } +} + +class _ProcessListWidget extends StatefulWidget { + @override + State createState() => _ProcessListState(); +} + +class _ProcessListState extends State<_ProcessListWidget> { + @override + void initState() { + _searchController.addListener(_handleSearchQuery); + _runTickerLoop(); + super.initState(); + } + + bool _disposed = false; + List _processes = []; + + void _runTickerLoop() async { + while (true) { + if (_disposed) break; + + final processes = await compute(_getRunningProcesses, 8); + if (_disposed) break; + + setState(() { + _processes = processes.map((e) => e.name).toSet().toList(); + _processes.sort((a, b) => a.compareTo(b)); + }); + } + } + + @override + void dispose() { + _searchController.removeListener(_handleSearchQuery); + _searchController.dispose(); + _disposed = true; + super.dispose(); + } + + static List _getRunningProcesses(int priority) { + ProcessFinder.initialize(); + + final data = ProcessFinder.listRunningProcesses(priority: priority); + ProcessFinder.uninitialize(); + + return data; + } + + final _searchController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final filtered = _processes + .where((p) => _q.isEmpty || p.toLowerCase().contains(_q)) + .toList(); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + child: TextField( + maxLines: 1, + controller: _searchController, + style: const TextStyle( + fontSize: 14, + ), + decoration: + const DefaultInputDecoration(hintText: 'Type to search...'), + ), + ), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(top: 8), + itemCount: filtered.length, + itemBuilder: (context, index) { + final info = filtered[index]; + return InkWell( + onTap: () => _selectProcess(context, info), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + child: Text( + info, + style: const TextStyle(color: Colors.white), + ), + ), + ); + })) + ], + ); + } + + String _q = ''; + + void _handleSearchQuery() { + setState(() { + _q = _searchController.text.toLowerCase().trim(); + }); + } + + _selectProcess(BuildContext contex, String info) { + Navigator.pop(context, info); } }