windows app icon

This commit is contained in:
wanghongenpin
2024-03-27 02:00:58 +08:00
parent 9b4c9e0e43
commit 075341f2c7
13 changed files with 122 additions and 52 deletions

View File

@@ -204,6 +204,10 @@ class ChannelContext {
set currentRequest(HttpRequest? request) => putAttribute(AttributeKeys.request, request);
set processInfo(ProcessInfo? processInfo) => putAttribute(AttributeKeys.processInfo, processInfo);
ProcessInfo? get processInfo => getAttribute(AttributeKeys.processInfo);
StreamSetting? setting;
HttpRequest? putStreamRequest(int streamId, HttpRequest request) {

View File

@@ -24,6 +24,7 @@ import 'package:network_proxy/network/components/host_filter.dart';
import 'package:network_proxy/network/handler.dart';
import 'package:network_proxy/network/util/attribute_keys.dart';
import 'package:network_proxy/network/util/crts.dart';
import 'package:network_proxy/network/util/process_info.dart';
import 'package:network_proxy/network/util/tls.dart';
import 'host_port.dart';
@@ -131,10 +132,11 @@ class Server extends Network {
void ssl(ChannelContext channelContext, Channel channel, Uint8List data) async {
var hostAndPort = channelContext.host;
try {
hostAndPort?.scheme = HostAndPort.httpsScheme;
if (hostAndPort == null && TLS.getDomain(data) != null) {
hostAndPort = HostAndPort.host(TLS.getDomain(data)!, 443);
}
hostAndPort?.scheme = HostAndPort.httpsScheme;
channelContext.putAttribute(AttributeKeys.domain, hostAndPort?.host);
Channel? remoteChannel = channelContext.serverChannel;
@@ -162,6 +164,13 @@ class Server extends Network {
var secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data);
channel.secureSocket(secureSocket, channelContext);
} catch (error, trace) {
try {
channelContext.processInfo =
await ProcessInfoUtils.getProcessByPort(channel.remoteSocketAddress, hostAndPort?.domain ?? 'unknown');
} catch (ignore) {
/*ignore*/
}
if (error is HandshakeException) {
channelContext.host = hostAndPort;
}

View File

@@ -67,7 +67,8 @@ class ProxyHelper {
static exceptionHandler(
ChannelContext channelContext, Channel channel, EventListener? listener, HttpRequest? request, error) async {
HostAndPort? hostAndPort = channelContext.host;
hostAndPort ??= HostAndPort.host(scheme: HostAndPort.httpScheme, channel.remoteSocketAddress.host, channel.remoteSocketAddress.port);
hostAndPort ??= HostAndPort.host(
scheme: HostAndPort.httpScheme, channel.remoteSocketAddress.host, channel.remoteSocketAddress.port);
String message = error.toString();
HttpStatus status = HttpStatus(-1, message);
if (error is HandshakeException) {
@@ -84,13 +85,7 @@ class ProxyHelper {
..body = message.codeUnits
..headers.contentLength = message.codeUnits.length
..hostAndPort = hostAndPort;
try {
request.processInfo ??=
await ProcessInfoUtils.getProcessByPort(channel.remoteSocketAddress, request.remoteDomain()!);
} catch (ignore) {
/*ignore*/
}
request.processInfo ??= channelContext.processInfo;
request.response ??= HttpResponse(status)
..headers.contentType = 'text/plain'

View File

@@ -7,4 +7,5 @@ interface class AttributeKeys {
static const String request = "REQUEST";
static const String remote = "REMOTE";
static const String proxyInfo = "PROXY_INFO";
static const String processInfo = "PROCESS_INFO";
}

View File

@@ -5,15 +5,15 @@ import 'dart:typed_data';
import 'package:network_proxy/native/installed_apps.dart';
import 'package:network_proxy/native/process_info.dart';
import 'package:network_proxy/network/util/socket_address.dart';
import 'package:win32audio/win32audio.dart';
import 'cache.dart';
void main() async {
var processInfo = await ProcessInfoUtils.getProcess(512);
print(await processInfo!._getIconPath());
// await ProcessInfoUtils.getMacIcon(processInfo!.path);
// print(await ProcessInfoUtils.getProcessByPort(63194));
print((await ProcessInfoUtils.getProcess(30025))?._getIconPath());
print(processInfo);
}
class ProcessInfoUtils {
@@ -29,29 +29,49 @@ class ProcessInfoUtils {
return null;
}
var pid = await _getPid(socketAddress);
if (pid == null) return null;
String cacheKey = "$cacheKeyPre:$pid";
var processInfo = processInfoCache.get(cacheKey);
if (processInfo != null) return processInfo;
processInfo = await getProcess(pid);
processInfoCache.set(cacheKey, processInfo!);
return processInfo;
}
// 获取进程 ID
static Future<int?> _getPid(InetSocketAddress socketAddress) async {
if (Platform.isWindows) {
var result = await Process.run('cmd', ['/c', 'netstat -ano | findstr :${socketAddress.port}']);
var lines = LineSplitter.split(result.stdout);
for (var line in lines) {
var parts = line.trim().split(RegExp(r'\s+'));
if (parts.length < 5) {
continue;
}
if (parts[1].trim().contains("${socketAddress.host}:${socketAddress.port}")) {
return int.tryParse(parts[4]);
}
}
return null;
}
if (Platform.isMacOS) {
var results = await Process.run('bash', [
'-c',
_concatCommands(['lsof -nP -iTCP:${socketAddress.port} |grep "${socketAddress.port}->"'])
]);
var results =
await Process.run('bash', ['-c', 'lsof -nP -iTCP:${socketAddress.port} |grep "${socketAddress.port}->"']);
if (results.exitCode == 0) {
var lines = LineSplitter.split(results.stdout);
if (results.exitCode != 0) {
return null;
}
for (var line in lines) {
var parts = line.trim().split(RegExp(r'\s+'));
if (parts.length >= 9) {
var pid = int.tryParse(parts[1]);
if (pid != null) {
String cacheKey = "$cacheKeyPre:$pid";
var processInfo = processInfoCache.get(cacheKey);
if (processInfo != null) return processInfo;
var lines = LineSplitter.split(results.stdout);
processInfo = await getProcess(pid);
processInfoCache.set(cacheKey, processInfo!);
return processInfo;
}
}
for (var line in lines) {
var parts = line.trim().split(RegExp(r'\s+'));
if (parts.length >= 9) {
return int.tryParse(parts[1]);
}
}
}
@@ -59,11 +79,18 @@ class ProcessInfoUtils {
}
static Future<ProcessInfo?> getProcess(int pid) async {
if (Platform.isWindows) {
// 获取应用路径
var result = await Process.run('cmd', ['/c', 'wmic process where processid=$pid get ExecutablePath']);
var output = result.stdout.toString();
var path = output.split('\n')[1].trim();
print(output);
String name = path.substring(path.lastIndexOf('\\') + 1);
return ProcessInfo(name, name.split(".")[0], path);
}
if (Platform.isMacOS) {
var results = await Process.run('bash', [
'-c',
_concatCommands(['ps -p $pid -o pid= -o comm='])
]);
var results = await Process.run('bash', ['-c', 'ps -p $pid -o pid= -o comm=']);
if (results.exitCode == 0) {
var lines = LineSplitter.split(results.stdout);
for (var line in lines) {
@@ -80,10 +107,6 @@ class ProcessInfoUtils {
return null;
}
static _concatCommands(List<String> commands) {
return commands.where((element) => element.isNotEmpty).join(' && ');
}
}
class ProcessInfo {
@@ -101,10 +124,6 @@ class ProcessInfo {
return ProcessInfo(json['id'], json['name'], json['path']);
}
Future<String> _getIconPath() async {
return _getMacIcon(path);
}
Future<Uint8List> getIcon() async {
if (icon != null) return icon!;
if (_iconCache.get(id) != null) return _iconCache.get(id)!;
@@ -114,10 +133,15 @@ class ProcessInfo {
icon = (await InstalledApps.getAppInfo(id)).icon;
}
if (Platform.isWindows) {
icon = await _getWindowsIcon(path);
}
if (Platform.isMacOS) {
var macIcon = await _getIconPath();
var macIcon = await _getMacIcon(path);
icon = await File(macIcon).readAsBytes();
}
icon = icon ?? Uint8List(0);
_iconCache.set(id, icon);
} catch (e) {
@@ -126,6 +150,10 @@ class ProcessInfo {
return icon!;
}
Future<Uint8List?> _getWindowsIcon(String path) async {
return await WinIcons().extractFileIcon(path);
}
static Future<String> _getMacIcon(String path) async {
var xml = await File('$path/Contents/Info.plist').readAsString();
var key = "<key>CFBundleIconFile</key>";

View File

@@ -197,7 +197,7 @@ class NetworkTabState extends State<NetworkTabController> with SingleTickerProvi
const SizedBox(height: 20),
rowWidget("Remote Address", response?.remoteAddress),
const SizedBox(height: 20),
rowWidget("Request Time", request.requestTime.toString()),
rowWidget("Request Time", request.requestTime.format()),
const SizedBox(height: 20),
rowWidget("Duration", response?.costTime()),
const SizedBox(height: 20),
@@ -209,6 +209,10 @@ class NetworkTabState extends State<NetworkTabController> with SingleTickerProvi
const SizedBox(height: 20),
rowWidget("Response Package", getPackage(response)),
];
if (request.processInfo != null) {
content.add(const SizedBox(height: 20));
content.add(rowWidget("App", request.processInfo!.name));
}
return ListView(children: [expansionTile("General", content)]);
}

View File

@@ -126,6 +126,7 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
//按照域名分类
DomainRequests domainRequests = getDomainRequests(request);
var isNew = domainRequests.body.isEmpty;
domainRequests.addRequest(request.requestId, request);
//搜索视图
if (searchModel?.isNotEmpty == true && searchModel?.filter(request, null) == true) {
@@ -142,14 +143,10 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
DomainRequests getDomainRequests(HttpRequest request) {
var host = request.remoteDomain()!;
DomainRequests? domainRequests = containerMap[host];
var processInfo = request.processInfo;
if (domainRequests == null) {
domainRequests = DomainRequests(host,
proxyServer: widget.panel.proxyServer,
trailing: processInfo == null
? null
: futureWidget(processInfo.getIcon(),
(data) => SizedBox(width: 25, child: data.isEmpty ? null : Image.memory(data))),
trailing: appIcon(request),
onDelete: deleteHost,
onRequestRemove: (req) => container.remove(req));
containerMap[host] = domainRequests;
@@ -158,6 +155,18 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
return domainRequests;
}
Widget? appIcon(HttpRequest request) {
var processInfo = request.processInfo;
if (processInfo == null) {
return null;
}
return futureWidget(
processInfo.getIcon(),
(data) =>
data.isEmpty ? const SizedBox() : Image.memory(data, width: 23, height: Platform.isWindows ? 16 : null));
}
///移除域名
deleteHost(String host) {
setState(() {
@@ -250,6 +259,11 @@ class DomainRequests extends StatefulWidget {
return requestMap[response.request?.requestId ?? response.requestId];
}
setTrailing(Widget? trailing) {
var state = key as GlobalKey<_DomainRequestsState>;
state.currentState?.trailing = trailing;
}
_remove(RequestWidget requestWidget) {
if (body.remove(requestWidget)) {
onRequestRemove?.call(requestWidget.request);
@@ -298,6 +312,7 @@ class _DomainRequestsState extends State<DomainRequests> {
final GlobalKey<ColorTransitionState> transitionState = GlobalKey<ColorTransitionState>();
late Configuration configuration;
late bool selected;
Widget? trailing;
AppLocalizations get localizations => AppLocalizations.of(context)!;
@@ -306,6 +321,7 @@ class _DomainRequestsState extends State<DomainRequests> {
super.initState();
configuration = widget.proxyServer.configuration;
selected = widget.selected;
trailing = widget.trailing;
}
changeState() {
@@ -327,7 +343,7 @@ class _DomainRequestsState extends State<DomainRequests> {
child: ListTile(
minLeadingWidth: 25,
leading: Icon(selected ? Icons.arrow_drop_down : Icons.arrow_right, size: 18),
trailing: widget.trailing,
trailing: trailing,
dense: true,
horizontalTitleGap: 0,
contentPadding: const EdgeInsets.only(left: 3, right: 8),

View File

@@ -69,7 +69,7 @@ class _ToolbarState extends State<Toolbar> {
return Row(
children: [
Padding(padding: EdgeInsets.only(left: Platform.isMacOS ? 80 : 30)),
SocketLaunch(proxyServer: widget.proxyServer),
SocketLaunch(proxyServer: widget.proxyServer, startup: widget.proxyServer.configuration.startup),
const Padding(padding: EdgeInsets.only(left: 20)),
IconButton(
tooltip: localizations.clear,

View File

@@ -233,7 +233,7 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
'4. The script supports log viewing, output through console.log()\n'
'5. Setting Auto Start Recording Traffic\n'
'6. Android certificate download optimization; \n';
showAlertDialog(isCN ? '更新内容V1.0.9-Beta' : "Update content V1.0.9-Beta", content, () {
showAlertDialog(isCN ? '更新内容V1.0.9' : "Update content V1.0.9", content, () {
widget.appConfiguration.upgradeNoticeV9 = false;
widget.appConfiguration.flushConfig();
});

View File

@@ -811,6 +811,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.3.0"
win32audio:
dependency: "direct main"
description:
name: win32audio
sha256: "30d663f393c41c0dc6de956f56f4b98c3942b8b1002943c75fac1a6861a6d368"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
window_manager:
dependency: "direct main"
description:

View File

@@ -38,6 +38,7 @@ dependencies:
flutter_code_editor:
file_picker: ^8.0.0
flutter_desktop_context_menu: ^0.2.0
win32audio: ^1.3.1
# video_player:
# git:
# url: https://github.com/icapps/plugins.git

View File

@@ -14,6 +14,7 @@
#include <screen_retriever/screen_retriever_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <win32audio/win32audio_plugin_c_api.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -33,6 +34,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
Win32audioPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Win32audioPluginCApi"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
}

View File

@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
share_plus
url_launcher_windows
win32audio
window_manager
)