mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
windows app icon
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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>";
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
screen_retriever
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
win32audio
|
||||
window_manager
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user