gui改为使用python 不再依赖外部的运行时

This commit is contained in:
DevWiki 2025-08-28 17:30:32 +08:00
parent 1f15856a68
commit 2590b47137
25 changed files with 1449 additions and 560 deletions

112
.gitignore vendored
View File

@ -1,4 +1,108 @@
/.idea # Python运行时文件
*/obj __pycache__/
*/bin *.py[cod]
*/pack/output *$py.class
*.so
# 分发/打包
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
*.manifest
*.spec
# 单元测试/覆盖率报告
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# 翻译
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# 项目特定文件
config.json
harmony_dev_tools.log
*.exe
*.spec
# 导出目录(时间戳格式)
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]/
.idea/

View File

@ -1,16 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HarmonyDevTools", "HarmonyDevTools\HarmonyDevTools.csproj", "{BBCBF001-B0B9-4F6D-A6EF-CC85E9D6FFFB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BBCBF001-B0B9-4F6D-A6EF-CC85E9D6FFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBCBF001-B0B9-4F6D-A6EF-CC85E9D6FFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBCBF001-B0B9-4F6D-A6EF-CC85E9D6FFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBCBF001-B0B9-4F6D-A6EF-CC85E9D6FFFB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=D_003A_005CCode_005Cgitea_005CHarmonyDevTools_005Clibs_005Clog4net_002Edll/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String></wpf:ResourceDictionary>

View File

@ -1,9 +0,0 @@
<Application x:Class="HarmonyDevTools.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HarmonyDevTools"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace HarmonyDevTools
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ApplicationIcon>icon.ico</ApplicationIcon>
<Title>HarmonyDevTools</Title>
<Authors>DevWiki</Authors>
<Description>OpenHarmony and HarmonyOS Develop Tools</Description>
<Copyright>DevWiki</Copyright>
<PackageIcon>icon.ico</PackageIcon>
<PackageTags>OpenHarmony,HarmonyOS</PackageTags>
<AssemblyVersion>1.0.2</AssemblyVersion>
<FileVersion>1.0.2</FileVersion>
<Version>1.0.2</Version>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net">
<HintPath>..\libs\log4net.dll</HintPath>
</Reference>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="xcopy &quot;$(ProjectDir)..\toolchains&quot; &quot;$(TargetDir)toolchains&quot; /E /I /Y" />
</Target>
</Project>

View File

@ -1,76 +0,0 @@
<Window x:Class="HarmonyDevTools.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:control="clr-namespace:HarmonyDevTools.control"
mc:Ignorable="d" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Title="HDC Tools" Height="600" Width="740" Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="48"/>
<RowDefinition Height="48"/>
<RowDefinition Height="48"/>
<RowDefinition Height="48"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="ListBtn" Grid.Row="0" Grid.Column="0" Width="100" Height="36" Content="列举设备" Click="ListBtn_OnClick" />
<TextBox x:Name="TargetTb" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,10,0" VerticalContentAlignment="Center"
VerticalAlignment="Center" Height="36" control:TextBoxHelper.Placeholder="请输入connect key"/>
<Button x:Name="ConnectBtn" Grid.Row="0" Grid.Column="3" Width="100" Height="36" Content="连接设备" Click="ConnectBtn_OnClick" />
<Button x:Name="KillBtn" Grid.Row="0" Grid.Column="4" Width="100" Height="36" Content="重启hdc" Click="KillBtn_OnClick" />
<Button x:Name="CheckServerBtn" Grid.Row="0" Grid.Column="5" Width="100" Height="36" Content="版本信息" Click="CheckServerBtn_OnClick" />
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Red" BorderThickness="1" Margin="5,0,5,0">
<StackPanel Orientation="Horizontal" Margin="5,0,5,0">
<Button x:Name="InstallBtn" Width="100" Height="36" Content="安装hap"
Click="InstallOnClick" VerticalAlignment="Center" />
<CheckBox x:Name="ReplaceCb" Content="替换安装" IsChecked="False" VerticalAlignment="Center"
Margin="10,0,0,0"/>
<CheckBox x:Name="DowngradeCb" Content="允许降级" IsChecked="False" VerticalAlignment="Center"
Margin="10,0,0,0"/>
<CheckBox x:Name="DynamicCb" Content="动态授权" IsChecked="False" VerticalAlignment="Center"
Margin="10,0,0,0"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="3" Grid.ColumnSpan="3" Margin="5,0,5,0" BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal" Margin="5,0,5,0">
<TextBox x:Name="PackageNameTb" control:TextBoxHelper.Placeholder="输入包名" VerticalAlignment="Center" VerticalContentAlignment="Center"
Width="228" Height="36"/>
<Button x:Name="UninstallBtn" Width="100" Height="36" Content="卸载应用" Margin="10,0,0,0"
Click="UninstallBtn_OnClick" VerticalAlignment="Center" />
</StackPanel>
</Border>
<Button x:Name="GetPhoto" Grid.Row="3" Grid.Column="0" Width="100" Height="36" Content="导出照片" Click="GetPhoto_OnClick" />
<Button x:Name="GetUDID" Grid.Row="3" Grid.Column="1" Width="100" Height="36" Content="UDID" Click="GetUDID_OnClick" />
<TextBox x:Name="CommandTb" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5" Margin="10,0,10,0"
Height="32" VerticalContentAlignment="Center" control:TextBoxHelper.Placeholder="输入命令不需要以 hdc 开头"/>
<Button x:Name="CommandBtn" Content="执行" Grid.Row="2" Grid.Column="5" Width="100" Height="36" VerticalAlignment="Center"
Click="CommandBtn_OnClick" HorizontalAlignment="Center"/>
<TextBox Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="6" x:Name="ResultTb" Margin="10" TextWrapping="Wrap" IsReadOnly="True"
VerticalScrollBarVisibility="Auto"/>
</Grid>
</Window>

View File

@ -1,136 +0,0 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using HarmonyDevTools.util;
using Microsoft.Win32;
namespace HarmonyDevTools;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void InstallOnClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = "Select a package",
Filter = "HarmonyNEXT(*.hap)|*.hap",
FilterIndex = 1,
RestoreDirectory = true
};
if (dialog.ShowDialog() == true)
{
// 获取所选文件的路径并显示在文本框中
string filePath = dialog.FileName;
AppendHintAndScrollToEnd($"select file: {filePath} \n");
string command = "install";
if (ReplaceCb.IsChecked ?? false)
{
command += " -r";
}
if (DowngradeCb.IsChecked ?? false)
{
command += " -d";
}
if (DynamicCb.IsChecked ?? false)
{
command += " -g";
}
CommandTb.Text = $"{command} {filePath}";
GetCommandAndExecute();
}
}
private void CommandBtn_OnClick(object sender, RoutedEventArgs e)
{
GetCommandAndExecute();
}
private void ListBtn_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = "list targets -v";
GetCommandAndExecute();
}
private async void GetCommandAndExecute()
{
string command = CommandTb.Text;
AppendHintAndScrollToEnd( $"execute command: {command} \n");
string result = await Task.Run(() => HdcUtil.RunCommand(command));
AppendHintAndScrollToEnd($"execute result: {result} \n");
}
private void CheckServerBtn_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = "checkserver";
GetCommandAndExecute();
}
private void ConnectBtn_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = $"-t {TargetTb.Text}";
GetCommandAndExecute();
}
private void KillBtn_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = "kill -r";
GetCommandAndExecute();
}
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
KillHdcExe();
e.Cancel = false;
}
private async void KillHdcExe()
{
string result = await Task.Run(() => CmdUtil.RunCmd("taskkill /IM hdc.exe"));
AppendHintAndScrollToEnd(result + "\n");
}
private void UninstallBtn_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = $"uninstall {PackageNameTb.Text}";
GetCommandAndExecute();
}
private void AppendHintAndScrollToEnd(string hint)
{
ResultTb.Text += hint;
ResultTb.ScrollToEnd();
}
private void GetPhoto_OnClick(object sender, RoutedEventArgs e)
{
ExportFile("/storage/media/100/local/files/Photo");
}
private async void ExportFile(string path)
{
var formattedDate = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
CmdUtil.RunCmd($"mkdir {formattedDate}");
AppendHintAndScrollToEnd($"create dir: {formattedDate} \n");
AppendHintAndScrollToEnd($"export {path} to {formattedDate} \n");
string result = await Task.Run(() => HdcUtil.ExportFile(path));
AppendHintAndScrollToEnd($"export result: {result} \n");
}
private void GetUDID_OnClick(object sender, RoutedEventArgs e)
{
CommandTb.Text = "shell bm get --udid";
GetCommandAndExecute();
}
}

View File

@ -1,57 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace HarmonyDevTools.control;
public static class TextBoxHelper
{
public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.RegisterAttached("Placeholder", typeof(string), typeof(TextBoxHelper),
new PropertyMetadata(default(string), OnPlaceholderChanged));
public static void SetPlaceholder(UIElement element, string value)
{
element.SetValue(PlaceholderProperty, value);
}
public static string GetPlaceholder(UIElement element)
{
return (string)element.GetValue(PlaceholderProperty);
}
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
textBox.GotFocus -= RemovePlaceholder;
textBox.LostFocus -= ShowPlaceholder;
if (!string.IsNullOrEmpty((string)e.NewValue))
{
textBox.GotFocus += RemovePlaceholder;
textBox.LostFocus += ShowPlaceholder;
ShowPlaceholder(textBox, null);
}
}
}
private static void RemovePlaceholder(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBox && textBox.Text == GetPlaceholder(textBox))
{
textBox.Text = "";
textBox.Foreground = Brushes.Black;
}
}
private static void ShowPlaceholder(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBox && string.IsNullOrEmpty(textBox.Text))
{
textBox.Text = GetPlaceholder(textBox);
textBox.Foreground = Brushes.Gray;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

View File

@ -1,31 +0,0 @@
set version=1.0.1
if not "%~1"=="" (
set version=%1
)
cd ../ && rmdir /s /q bin && rmdir /s /q obj
cd pack
if not exist "output" (
mkdir output
)
cd output
if exist "HarmonyTools-%version%.exe" (
del /q /s HarmonyDevTools-%version%.exe
)
cd ../
dotnet publish ../../HarmonyDevTools/HarmonyDevTools.csproj -c Release -r win-x86 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:SelfContained=false -o ./output
if NOT %errorlevel%==0 @goto :FailureOnBuild
cd output && move HarmonyDevTools.exe HarmonyDevTools-%version%.exe && move HarmonyDevTools.pdb HarmonyDevTools-%version%.pdb && cd ../
xcopy ..\..\toolchains .\output\toolchains /E /I /Y
cd ../bin && del /q /s Release && cd ../pack
goto :eof
:FailureOnBuild
echo build project failure

View File

@ -1,119 +0,0 @@
using System;
using System.Diagnostics;
using System.Text;
using log4net;
namespace HarmonyDevTools.util;
public static class CmdUtil
{
private static readonly ILog Log = LogManager.GetLogger(nameof(CmdUtil));
private const string CmdExe = "cmd.exe";
/**
* 使 cmd.exe
*/
public static string RunCmd(string parameter = "", bool withExit = true)
{
var output = new StringBuilder();
try
{
var pro = new Process
{
StartInfo =
{
FileName = CmdExe,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
if (parameter.Length > 0)
{
pro.StartInfo.Arguments = parameter;
}
pro.Start();
if (parameter.Length > 0)
{
pro.StandardInput.WriteLine(parameter + (withExit ? " &exit" : ""));
pro.StandardInput.AutoFlush = true;
}
while (!pro.StandardOutput.EndOfStream)
{
string? line = pro.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(line) && !line.StartsWith("(c) Microsoft") && !line.StartsWith("Microsoft Windows"))
{
output.AppendLine(line);
}
}
if (withExit)
{
pro.WaitForExit();
pro.Close();
}
}
catch (Exception e)
{
Log.Error(e);
}
return output.ToString();
}
/**
* 使 exe
*/
public static string RunExe(string exePath, string arguments = "")
{
var output = new StringBuilder();
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = exePath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.OutputDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
output.AppendLine(args.Data);
}
};
process.ErrorDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
output.AppendLine($"ERROR: {args.Data}");
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
process.Close();
}
catch (Exception e)
{
Log.Error(e);
}
return output.ToString();
}
}

View File

@ -1,19 +0,0 @@
using System;
namespace HarmonyDevTools.util;
public static class HdcUtil
{
public static string ExportFile(string originPath)
{
var formattedDate = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
CmdUtil.RunCmd($"mkdir {formattedDate}");
var command = $"file recv {originPath} ./{formattedDate}";
return CmdUtil.RunExe("toolchains\\hdc.exe", command);
}
public static string RunCommand(string command)
{
return CmdUtil.RunExe("toolchains\\hdc.exe", command);
}
}

BIN
HarmonyDevTools_v1.4.0.zip Normal file

Binary file not shown.

323
build.py Normal file
View File

@ -0,0 +1,323 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HarmonyDevTools Python版本打包脚本
用于将Python项目打包为exe文件
"""
import os
import sys
import subprocess
import shutil
import re
import zipfile
from pathlib import Path
def check_pyinstaller():
"""检查PyInstaller是否已安装"""
try:
import PyInstaller
return True
except ImportError:
return False
def install_pyinstaller():
"""安装PyInstaller"""
print("正在安装PyInstaller...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyinstaller"])
print("PyInstaller安装成功")
return True
except subprocess.CalledProcessError:
print("PyInstaller安装失败")
return False
def get_version_from_version_info():
"""从version_info.txt文件中获取版本号"""
if not os.path.exists("version_info.txt"):
return "1.0.0"
try:
with open("version_info.txt", 'r', encoding='utf-8') as f:
content = f.read()
match = re.search(r"StringStruct\(u'FileVersion', u'([^']+)'\)", content)
if match:
return match.group(1)
except Exception as e:
print(f"读取version_info.txt版本信息失败: {e}")
return "1.0.0"
def build_with_version(main_file, exe_name, version):
"""使用指定版本号构建exe文件"""
# 构建命令
cmd = [
"pyinstaller",
"--onefile", # 打包为单个文件
"--windowed", # 无控制台窗口
f"--name={exe_name}", # 输出文件名
"--icon=icon.ico", # 图标文件(如果存在)
"--version-file=version_info.txt", # 版本信息文件
"--add-data=toolchains;toolchains", # 包含toolchains目录
main_file
]
# 如果图标文件不存在,移除图标参数
if not os.path.exists("icon.ico"):
cmd = [arg for arg in cmd if not arg.startswith("--icon")]
# 如果版本信息文件不存在,移除版本信息参数
if not os.path.exists("version_info.txt"):
cmd = [arg for arg in cmd if not arg.startswith("--version-file")]
return cmd
def get_version_for_filename(version):
"""将版本号转换为文件名格式(只取前三位)"""
parts = version.split('.')
if len(parts) >= 3:
return f"{parts[0]}.{parts[1]}.{parts[2]}"
return version
def build_exe():
"""构建exe文件"""
print("开始构建exe文件...")
# 检查主程序文件是否存在
if not os.path.exists("main.py"):
print("错误找不到main.py文件")
return False
# 从version_info.txt获取版本号
version = get_version_from_version_info()
version_for_filename = get_version_for_filename(version)
main_file = "main.py"
exe_name = f"HarmonyDevTools_v{version_for_filename}"
print(f"构建HarmonyDevTools v{version}...")
# 检查toolchains目录是否存在
if not os.path.exists("toolchains"):
print("警告找不到toolchains目录请确保包含hdc.exe文件")
# 构建命令
cmd = build_with_version(main_file, exe_name, version_for_filename)
try:
subprocess.check_call(cmd)
print("构建成功!")
return exe_name
except subprocess.CalledProcessError as e:
print(f"构建失败:{e}")
return False
def create_distribution(exe_name="HarmonyDevTools"):
"""创建发布包"""
print("创建发布包...")
# 直接使用dist目录作为发布目录
dist_dir = "dist"
if not os.path.exists(dist_dir):
os.makedirs(dist_dir)
# 复制toolchains目录到dist
if os.path.exists("toolchains"):
toolchains_dest = os.path.join(dist_dir, "toolchains")
if os.path.exists(toolchains_dest):
shutil.rmtree(toolchains_dest)
shutil.copytree("toolchains", toolchains_dest)
print("已复制toolchains目录到dist")
# 创建适合exe发布包的README
create_release_readme(dist_dir)
print(f"发布包已创建:{dist_dir}")
print("发布包包含:")
print(f"- {exe_name}.exe")
print("- toolchains/目录")
print("- README.md")
return True
def create_release_readme(dist_dir):
"""创建发布版README文件"""
# 获取当前版本号
version = get_version_from_version_info()
version_for_filename = get_version_for_filename(version)
version_underscore = version_for_filename.replace('.', '_')
readme_content = f"""# HarmonyDevTools
HarmonyOS/OpenHarmony开发工具提供图形化界面操作HDC工具
## 功能特性
- **设备管理**: 列举设备连接设备重启HDC服务
- **应用管理**: 安装/卸载HarmonyOS应用包
- **文件操作**: 导出设备照片导出应用日志
- **系统信息**: 获取设备UDID查看版本信息
- **命令执行**: 支持执行任意HDC命令
## 使用方法
1. **启动程序**: 双击 `HarmonyDevTools_v{version_underscore}.exe` 启动程序
2. **连接设备**: 点击"列举设备"查看可用设备输入connect key后点击"连接设备"
3. **安装应用**: 选择hap文件设置安装选项后点击"安装hap"
4. **导出文件**: 点击"导出照片""导出日志"从设备导出文件
## 系统要求
- Windows 10/11
- 无需安装Python或其他依赖
- 需要HarmonyOS/OpenHarmony设备
## 注意事项
- 首次使用前请确保设备已开启开发者模式
- 确保toolchains目录包含hdc.exe文件
- 导出文件会自动创建时间戳目录并打开文件夹
## 故障排除
1. **无法连接设备**: 检查设备是否开启USB调试尝试重启HDC服务
2. **安装失败**: 检查hap文件是否有效确认设备存储空间充足
3. **导出失败**: 确认设备路径正确检查文件权限
---
基于原C# WPF项目转换的Python Tkinter版本
版本: {version}
"""
readme_path = os.path.join(dist_dir, "README.md")
with open(readme_path, "w", encoding="utf-8") as f:
f.write(readme_content)
print("已创建发布版README.md")
def create_zip_package(exe_name, dist_dir):
"""创建zip压缩包"""
print("创建zip压缩包...")
# 获取版本号用于zip文件名
version = get_version_from_version_info()
version_for_filename = get_version_for_filename(version)
zip_filename = f"HarmonyDevTools_v{version_for_filename}.zip"
zip_path = os.path.join(dist_dir, zip_filename)
# 创建zip文件
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# 添加exe文件到HarmonyDevTools_v1.4.0目录下
exe_path = os.path.join(dist_dir, f"{exe_name}.exe")
if os.path.exists(exe_path):
zipf.write(exe_path, f"HarmonyDevTools_v{version_for_filename}/{exe_name}.exe")
print(f"已添加 {exe_name}.exe 到压缩包")
# 添加toolchains目录下的文件到HarmonyDevTools_v1.4.0/toolchains目录
toolchains_dir = os.path.join(dist_dir, "toolchains")
if os.path.exists(toolchains_dir):
for root, dirs, files in os.walk(toolchains_dir):
for file in files:
file_path = os.path.join(root, file)
# 计算相对路径去掉dist前缀
rel_path = os.path.relpath(file_path, dist_dir)
# 在zip中创建toolchains子目录
arcname = f"HarmonyDevTools_v{version_for_filename}/{rel_path}"
zipf.write(file_path, arcname)
print(f"已添加 {arcname} 到压缩包")
# 添加README.md到HarmonyDevTools_v1.4.0目录下
readme_path = os.path.join(dist_dir, "README.md")
if os.path.exists(readme_path):
zipf.write(readme_path, f"HarmonyDevTools_v{version_for_filename}/README.md")
print("已添加 README.md 到压缩包")
print(f"zip压缩包创建成功{zip_path}")
return zip_filename
def clean_build_files():
"""清理构建文件"""
print("清理构建文件...")
# 清理所有构建相关的临时文件
dirs_to_clean = ["build", "__pycache__"]
files_to_clean = ["HarmonyDevTools.spec", "HarmonyDevTools_Enhanced.spec"]
for dir_name in dirs_to_clean:
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
print(f"已删除目录:{dir_name}")
for file_name in files_to_clean:
if os.path.exists(file_name):
os.remove(file_name)
print(f"已删除文件:{file_name}")
print("清理完成")
def clean_after_build(exe_name):
"""构建完成后清理临时文件"""
print("清理构建临时文件...")
# 清理build目录
if os.path.exists("build"):
shutil.rmtree("build")
print("已删除build目录")
# 清理spec文件
spec_files = [f"{exe_name}.spec"]
for spec_file in spec_files:
if os.path.exists(spec_file):
os.remove(spec_file)
print(f"已删除文件:{spec_file}")
print("构建后清理完成")
def main():
"""主函数"""
print("=" * 50)
print("HarmonyDevTools Python版本打包工具")
print("=" * 50)
# 检查Python版本
if sys.version_info < (3, 7):
print("错误需要Python 3.7或更高版本!")
return
print(f"Python版本{sys.version}")
# 检查PyInstaller
if not check_pyinstaller():
print("PyInstaller未安装")
choice = input("是否自动安装PyInstaller(y/n): ").lower()
if choice == 'y':
if not install_pyinstaller():
return
else:
print("请手动安装PyInstallerpip install pyinstaller")
return
# 自动清理之前的构建文件
if os.path.exists("build") or os.path.exists("dist"):
print("清理之前的构建文件...")
clean_build_files()
# 构建exe文件
exe_name = build_exe()
if exe_name:
# 自动创建发布包
create_distribution(exe_name)
# 创建zip压缩包
zip_filename = create_zip_package(exe_name, "dist")
# 构建完成后清理临时文件
clean_after_build(exe_name)
print("\n构建完成!")
print(f"exe文件位置dist/{exe_name}.exe")
print(f"zip压缩包{zip_filename}")
print("发布包位置dist/目录")
print("可以直接分发dist目录或zip压缩包作为完整的发布包")
else:
print("构建失败!")
if __name__ == "__main__":
main()

View File

@ -1,7 +0,0 @@
{
"sdk": {
"version": "6.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

664
main.py Normal file
View File

@ -0,0 +1,664 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HarmonyDevTools
提供更丰富的功能和更好的用户体验
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext, Menu
import subprocess
import os
import threading
import datetime
import logging
import json
import ctypes
import webbrowser
import re
from pathlib import Path
from typing import Optional, Dict, Any
# 版本信息
VERSION = "1.4.0"
# --- UI 界面 ---
# 启用 DPI 感知(避免高分屏模糊)
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
except Exception:
try:
ctypes.windll.user32.SetProcessDPIAware()
except Exception:
pass
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('harmony_dev_tools.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def center_window(win, width, height):
"""让窗口在屏幕中央显示"""
screen_width = win.winfo_screenwidth()
screen_height = win.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2
win.geometry(f"{width}x{height}+{x}+{y}")
def get_version():
"""获取版本号"""
import sys
# 检查是否有命令行参数传入版本号
if len(sys.argv) > 1 and sys.argv[1].startswith('--version='):
return sys.argv[1].split('=', 1)[1]
return VERSION
class Config:
"""配置管理类"""
def __init__(self, config_file="config.json"):
self.config_file = config_file
self.config = self.load_config()
def load_config(self) -> Dict[str, Any]:
"""加载配置"""
default_config = {
"window_size": "970x600",
"theme": "default",
"auto_connect": False,
"default_target": "",
"recent_commands": [],
"max_recent_commands": 10,
"log_level": "INFO"
}
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
# 合并默认配置
for key, value in default_config.items():
if key not in config:
config[key] = value
return config
except Exception as e:
logger.error(f"加载配置失败: {e}")
return default_config
def save_config(self):
"""保存配置"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"保存配置失败: {e}")
def get(self, key: str, default=None):
"""获取配置值"""
return self.config.get(key, default)
def set(self, key: str, value):
"""设置配置值"""
self.config[key] = value
self.save_config()
class HdcUtil:
"""HDC工具类用于执行hdc命令"""
def __init__(self):
self.hdc_path = "toolchains/hdc.exe"
self.check_hdc_path()
def check_hdc_path(self):
"""检查hdc.exe路径"""
if not os.path.exists(self.hdc_path):
logger.warning(f"HDC路径不存在: {self.hdc_path}")
# 尝试查找hdc.exe
possible_paths = [
"hdc.exe",
"toolchains/hdc.exe",
"toolchains\\hdc.exe",
"../toolchains/hdc.exe"
]
for path in possible_paths:
if os.path.exists(path):
self.hdc_path = path
logger.info(f"找到HDC路径: {self.hdc_path}")
break
@staticmethod
def run_cmd(parameter="", with_exit=True):
"""使用cmd.exe执行命令"""
output = []
try:
if parameter:
cmd = ["cmd.exe", "/c", parameter]
if with_exit:
cmd.append("&exit")
else:
cmd = ["cmd.exe"]
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW
)
stdout, stderr = process.communicate()
# 过滤输出
for line in stdout.split('\n'):
if line and not line.startswith("(c) Microsoft") and not line.startswith("Microsoft Windows"):
output.append(line)
if stderr:
output.append(f"ERROR: {stderr}")
except Exception as e:
logger.error(f"执行命令失败: {e}")
output.append(f"ERROR: {e}")
return '\n'.join(output)
def run_exe(self, exe_path, arguments=""):
"""使用指定的exe文件执行命令"""
output = []
try:
process = subprocess.Popen(
[exe_path] + arguments.split() if arguments else [exe_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
creationflags=subprocess.CREATE_NO_WINDOW
)
stdout, stderr = process.communicate()
if stdout:
output.extend(stdout.split('\n'))
if stderr:
output.append(f"ERROR: {stderr}")
except Exception as e:
logger.error(f"执行exe失败: {e}")
output.append(f"ERROR: {e}")
return '\n'.join(output)
def export_file(self, origin_path):
"""导出文件"""
formatted_date = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
HdcUtil.run_cmd(f"mkdir {formatted_date}")
command = f"file recv {origin_path} ./{formatted_date}"
return self.run_exe(self.hdc_path, command)
def run_command(self, command):
"""运行hdc命令"""
return self.run_exe(self.hdc_path, command)
class CommandHistory:
"""命令历史管理"""
def __init__(self, max_history=50):
self.max_history = max_history
self.history = []
self.current_index = -1
def add_command(self, command):
"""添加命令到历史"""
if command and command not in self.history:
self.history.append(command)
if len(self.history) > self.max_history:
self.history.pop(0)
self.current_index = len(self.history)
def get_previous(self):
"""获取上一条命令"""
if self.history and self.current_index > 0:
self.current_index -= 1
return self.history[self.current_index]
return ""
def get_next(self):
"""获取下一条命令"""
if self.history and self.current_index < len(self.history) - 1:
self.current_index += 1
return self.history[self.current_index]
return ""
def get_recent(self, count=10):
"""获取最近的命令"""
return self.history[-count:] if self.history else []
class HarmonyDevTools:
"""Harmony开发工具主界面"""
def __init__(self, root):
self.root = root
self.config = Config()
self.hdc_util = HdcUtil()
self.command_history = CommandHistory()
# 设置窗口
self.setup_window()
self.setup_menu()
self.setup_ui()
self.setup_bindings()
# 加载配置
self.load_config()
def setup_window(self):
"""设置窗口"""
self.root.title("HDC Tools")
self.root.minsize(970, 600) # 最小大小限制
self.root.resizable(False, True) # 允许水平 & 垂直拉伸
center_window(self.root, 970, 600) # 初始化居中
# 设置图标(如果存在)
try:
if os.path.exists("icon.ico"):
self.root.iconbitmap("icon.ico")
except:
pass
def setup_menu(self):
"""设置菜单栏"""
menubar = Menu(self.root)
self.root.config(menu=menubar)
# 文件菜单
file_menu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="文件", menu=file_menu)
file_menu.add_command(label="保存日志", command=self.save_log)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.on_closing)
# 工具菜单
tools_menu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="工具", menu=tools_menu)
tools_menu.add_command(label="清理HDC进程", command=self.kill_hdc_processes)
tools_menu.add_command(label="检查HDC状态", command=self.check_hdc_status)
tools_menu.add_separator()
tools_menu.add_command(label="打开日志文件", command=self.open_log_file)
# 帮助菜单
help_menu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="关于", command=self.show_about)
help_menu.add_command(label="查看帮助", command=self.show_help)
def setup_ui(self):
"""设置用户界面"""
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.columnconfigure(2, weight=1)
# 第一行:设备操作
row = 0
ttk.Button(main_frame, text="列举设备", width=12, command=self.list_devices).grid(
row=row, column=0, padx=(0, 5), pady=5)
self.target_var = tk.StringVar()
target_entry = ttk.Entry(main_frame, textvariable=self.target_var, width=30)
target_entry.grid(row=row, column=1, columnspan=2, padx=5, pady=5, sticky=(tk.W, tk.E))
ttk.Button(main_frame, text="连接设备", width=12, command=self.connect_device).grid(
row=row, column=3, padx=5, pady=5)
ttk.Button(main_frame, text="重启hdc", width=12, command=self.restart_hdc).grid(
row=row, column=4, padx=5, pady=5)
ttk.Button(main_frame, text="版本信息", width=12, command=self.check_version).grid(
row=row, column=5, padx=(5, 0), pady=5)
# 第二行:安装操作
row += 1
install_frame = ttk.LabelFrame(main_frame, text="安装操作", padding="5")
install_frame.grid(row=row, column=0, columnspan=3, padx=(0, 5), pady=5, sticky=(tk.W, tk.E))
ttk.Button(install_frame, text="安装hap", width=12, command=self.install_hap).pack(side=tk.LEFT)
self.replace_var = tk.BooleanVar()
ttk.Checkbutton(install_frame, text="替换安装", variable=self.replace_var).pack(side=tk.LEFT, padx=(10, 0))
self.downgrade_var = tk.BooleanVar()
ttk.Checkbutton(install_frame, text="允许降级", variable=self.downgrade_var).pack(side=tk.LEFT, padx=(10, 0))
self.dynamic_var = tk.BooleanVar()
ttk.Checkbutton(install_frame, text="动态授权", variable=self.dynamic_var).pack(side=tk.LEFT, padx=(10, 0))
# 卸载操作
uninstall_frame = ttk.LabelFrame(main_frame, text="卸载操作", padding="5")
uninstall_frame.grid(row=row, column=3, columnspan=3, padx=(5, 0), pady=5, sticky=(tk.W, tk.E))
self.package_name_var = tk.StringVar()
ttk.Entry(uninstall_frame, textvariable=self.package_name_var, width=25).pack(side=tk.LEFT)
ttk.Button(uninstall_frame, text="卸载应用", width=12, command=self.uninstall_app).pack(side=tk.LEFT, padx=(10, 0))
# 第三行:其他操作
row += 1
ttk.Button(main_frame, text="导出照片", width=12, command=self.export_photo).grid(
row=row, column=0, padx=(0, 5), pady=5)
ttk.Button(main_frame, text="UDID", width=12, command=self.get_udid).grid(
row=row, column=1, padx=5, pady=5)
# 第四行:命令执行
row += 1
self.command_var = tk.StringVar()
command_entry = ttk.Entry(main_frame, textvariable=self.command_var, width=50)
command_entry.grid(row=row, column=0, columnspan=5, padx=(0, 5), pady=5, sticky=(tk.W, tk.E))
ttk.Button(main_frame, text="执行", width=12, command=self.execute_command).grid(
row=row, column=5, padx=(5, 0), pady=5)
# 第五行:结果显示
row += 1
result_frame = ttk.LabelFrame(main_frame, text="执行结果", padding="5")
result_frame.grid(row=row, column=0, columnspan=6, padx=0, pady=5, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建结果显示区域
self.result_text = scrolledtext.ScrolledText(result_frame, height=15, width=80)
self.result_text.pack(fill=tk.BOTH, expand=True)
# 配置结果区域网格权重
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
main_frame.rowconfigure(row, weight=1)
# 保存控件引用
self.target_entry = target_entry
self.command_entry = command_entry
def setup_bindings(self):
"""设置事件绑定"""
# 命令输入框绑定回车键
self.command_entry.bind('<Return>', lambda e: self.execute_command())
# 命令输入框绑定上下箭头键(历史记录)
self.command_entry.bind('<Up>', lambda e: self.show_command_history('up'))
self.command_entry.bind('<Down>', lambda e: self.show_command_history('down'))
# 窗口大小改变事件
self.root.bind('<Configure>', self.on_window_resize)
def show_command_history(self, direction):
"""显示命令历史"""
if direction == 'up':
command = self.command_history.get_previous()
else:
command = self.command_history.get_next()
if command:
self.command_var.set(command)
# 选中所有文本
self.command_entry.select_range(0, tk.END)
def on_window_resize(self, event):
"""窗口大小改变事件"""
if event.widget == self.root:
# 保存窗口大小到配置
self.config.set("window_size", f"{event.width}x{event.height}")
def load_config(self):
"""加载配置"""
# 加载默认目标
default_target = self.config.get("default_target", "")
if default_target:
self.target_var.set(default_target)
def append_result(self, text):
"""添加结果到显示区域"""
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
self.result_text.insert(tk.END, f"[{timestamp}] {text}\n")
self.result_text.see(tk.END)
self.root.update_idletasks()
def execute_command_async(self, command):
"""异步执行命令"""
def run():
self.append_result(f"执行命令: {command}")
result = self.hdc_util.run_command(command)
self.append_result(f"执行结果: {result}")
# 添加到命令历史
self.command_history.add_command(command)
thread = threading.Thread(target=run)
thread.daemon = True
thread.start()
def list_devices(self):
"""列举设备"""
self.command_var.set("list targets -v")
self.execute_command_async("list targets -v")
def connect_device(self):
"""连接设备"""
target = self.target_var.get().strip()
if not target:
messagebox.showwarning("警告", "请输入connect key")
return
command = f"-t {target}"
self.command_var.set(command)
self.execute_command_async(command)
# 保存到配置
self.config.set("default_target", target)
def restart_hdc(self):
"""重启hdc"""
self.command_var.set("kill -r")
self.execute_command_async("kill -r")
def check_version(self):
"""检查版本信息"""
self.command_var.set("checkserver")
self.execute_command_async("checkserver")
def install_hap(self):
"""安装hap文件"""
file_path = filedialog.askopenfilename(
title="选择hap文件",
filetypes=[("HarmonyNEXT", "*.hap"), ("所有文件", "*.*")]
)
if file_path:
# 统一路径格式,确保使用正确的路径分隔符
normalized_path = os.path.normpath(file_path)
self.append_result(f"选择文件: {normalized_path}")
command = "install"
if self.replace_var.get():
command += " -r"
if self.downgrade_var.get():
command += " -d"
if self.dynamic_var.get():
command += " -g"
command += f" \"{normalized_path}\""
self.command_var.set(command)
self.execute_command_async(command)
def uninstall_app(self):
"""卸载应用"""
package_name = self.package_name_var.get().strip()
if not package_name:
messagebox.showwarning("警告", "请输入包名")
return
command = f"uninstall {package_name}"
self.command_var.set(command)
self.execute_command_async(command)
def export_photo(self):
"""导出照片"""
self.export_file("/storage/media/100/local/files/Photo")
def export_file(self, path):
"""导出文件"""
def run():
formatted_date = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
HdcUtil.run_cmd(f"mkdir {formatted_date}")
self.append_result(f"创建目录: {formatted_date}")
self.append_result(f"导出 {path}{formatted_date}")
result = self.hdc_util.export_file(path)
self.append_result(f"导出结果: {result}")
# 导出完成后打开文件夹
try:
if os.path.exists(formatted_date):
os.startfile(formatted_date)
self.append_result(f"已打开导出目录: {formatted_date}")
except Exception as e:
self.append_result(f"打开文件夹失败: {e}")
thread = threading.Thread(target=run)
thread.daemon = True
thread.start()
def get_udid(self):
"""获取UDID"""
self.command_var.set("shell bm get --udid")
self.execute_command_async("shell bm get --udid")
def execute_command(self):
"""执行命令"""
command = self.command_var.get().strip()
if not command:
messagebox.showwarning("警告", "请输入命令")
return
self.execute_command_async(command)
def save_log(self):
"""保存日志"""
file_path = filedialog.asksaveasfilename(
title="保存日志",
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.result_text.get(1.0, tk.END))
messagebox.showinfo("成功", f"日志已保存到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")
def kill_hdc_processes(self):
"""清理HDC进程"""
def run():
result = HdcUtil.run_cmd("taskkill /IM hdc.exe")
self.append_result(f"清理HDC进程: {result}")
thread = threading.Thread(target=run)
thread.daemon = True
thread.start()
def check_hdc_status(self):
"""检查HDC状态"""
self.execute_command_async("checkserver")
def open_log_file(self):
"""打开日志文件"""
log_file = "harmony_dev_tools.log"
if os.path.exists(log_file):
try:
os.startfile(log_file)
except:
messagebox.showinfo("信息", f"日志文件位置: {os.path.abspath(log_file)}")
else:
messagebox.showinfo("信息", "暂无日志文件")
def show_about(self):
"""显示关于信息"""
version = get_version()
about_text = f"""HarmonyDevTools
版本: {version}
作者: DevWiki
功能: HarmonyOS/OpenHarmony开发工具
基于原C# WPF项目转换而来
使用Python Tkinter重新实现
支持功能:
- 设备管理
- 应用安装/卸载
- 文件导出
- 命令执行
"""
messagebox.showinfo("关于", about_text)
def show_help(self):
"""显示帮助"""
help_text = """使用说明:
1. 设备连接:
- 点击"列举设备"查看可用设备
- 输入connect key后点击"连接设备"
2. 应用管理:
- 选择hap文件进行安装
- 输入包名进行卸载
3. 文件操作:
- 点击"导出照片"导出设备照片
- 点击"导出日志"导出指定应用的日志文件
- 使用自定义命令执行其他操作
4. 快捷键:
- 命令输入框支持上下箭头键浏览历史
- 回车键执行命令
5. 日志管理:
- 可通过菜单保存执行日志
- 日志文件自动保存在程序目录
"""
messagebox.showinfo("帮助", help_text)
def on_closing(self):
"""窗口关闭时的处理"""
try:
# 保存配置
self.config.save_config()
# 清理HDC进程
result = HdcUtil.run_cmd("taskkill /IM hdc.exe")
self.append_result(result)
except:
pass
self.root.destroy()
def main():
"""主函数"""
root = tk.Tk()
app = HarmonyDevTools(root)
# 绑定关闭事件
root.protocol("WM_DELETE_WINDOW", app.on_closing)
# 启动应用
root.mainloop()
if __name__ == "__main__":
main()

198
readme.md
View File

@ -1,14 +1,15 @@
# HarmonyDevTools - Python版本
![](https://img.shields.io/badge/状态-稳定-red.svg) ![](https://img.shields.io/badge/状态-稳定-red.svg)
![](https://img.shields.io/badge/启动时间-2024/06/20-green.svg) ![](https://img.shields.io/badge/启动时间-2024/06/20-green.svg)
![](https://img.shields.io/badge/优先级-NORMAL-blue.svg) ![](https://img.shields.io/badge/优先级-NORMAL-blue.svg)
![GitHub Release](https://img.shields.io/github/v/release/dev-wiki/HarmonyDevTools?color=yellow&label=版本) ![](https://img.shields.io/badge/语言-Python-blue.svg)
本项目为HarmonyOS/OpenHarmony的 toolchains 工具提供UI操作, 方便使用. 本项目为HarmonyOS/OpenHarmony的 toolchains 工具提供Python Tkinter UI操作界面方便使用。
## 1. 版本说明 ## 1. 版本说明
目前支持 hdc 工具的使用,后续会陆续增加其他工具, 这是原C# WPF项目的Python Tkinter版本保持了相同的功能特性
hdc功能支持: hdc功能支持:
- 枚举设备/连接设备 - 枚举设备/连接设备
@ -19,34 +20,183 @@ hdc功能支持:
- 获取手机的UDID - 获取手机的UDID
- 其他命令的执行(手动输入执行) - 其他命令的执行(手动输入执行)
下载地址: **Release** 页面下载最新版本。 ## 2. 环境要求
![主界面](image/主界面.png) - Python 3.7+
- Windows操作系统
- HarmonyOS/OpenHarmony SDK包含toolchains目录
## 2. 使用方式 ## 3. 安装和运行
下载最新版本, 解压如下目录, 双击 `HarmonyDevTools.exe` 运行。 ### 3.1 直接运行
![解压](image/解压后文件.png) 1. 确保已安装Python 3.7或更高版本
2. 下载项目文件
3. 确保`toolchains`目录包含`hdc.exe`文件
4. 运行主程序:
**方式一:使用启动脚本(推荐)**
```bash
run.bat
```
运行环境要求: netcore6.0, 如果双击无法运行,请下载安装 netcore6.0 runtime: **方式二直接运行Python文件**
- x64版本https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.31-windows-x64-installer ```bash
- x86版本https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.31-windows-x86-installer python main.py
```
## 3. 更新hdc ### 3.2 安装依赖(可选)
根据OpenHarmony官方的文档: [hdc使用指导](https://docs.openharmony.cn/pages/v4.1/zh-cn/device-dev/subsystems/subsys-toolchain-hdc-guide.md), 如果需要额外的功能,可以安装可选依赖:
安装DevEco以后, 设置SDK路径, 下载最新的SDK, 里面包含 toolchains
> ## 环境准备 ```bash
> hdc 工具获取方式: pip install -r requirements.txt
> ```
> 通过OpenHarmony sdk获取hdc在sdk的toolchains目录下。
>
> **使用举例:**
>
> 下面以windows侧使用方式举例
>
> 获取windows的sdk将hdc.exe放到磁盘某个位置即可使用。
## 4. 打包为exe文件
### 4.1 使用项目自带的构建脚本(推荐)
```bash
# 运行构建脚本
python build.py
```
构建脚本会:
- 自动检查PyInstaller是否安装
- 自动清理之前的构建文件
- 构建HarmonyDevTools_v版本号.exe包含版本信息
- 自动创建完整的发布包
- 生成说明文档
- 构建完成后自动清理临时文件
### 4.2 使用PyInstaller手动打包
```bash
# 安装PyInstaller
pip install pyinstaller
# 打包为exe文件
pyinstaller --onefile --windowed --name HarmonyDevTools_v版本号 main.py
```
### 4.3 版本管理
```bash
# 更新版本号
python update_version.py
# 查看当前版本
# 运行 update_version.py 会显示当前版本
```
**版本号格式**: x.y.z (例如: 1.4.0)
- 文件名格式: HarmonyDevTools_v1_4_0.exe
- 程序内显示: 1.4.0(构建时通过命令行参数传递)
- 版本号来源: version_info.txtmain.py中的VERSION变量用于开发调试
### 4.4 使用auto-py-to-exeGUI工具
```bash
# 安装auto-py-to-exe
pip install auto-py-to-exe
# 启动GUI打包工具
auto-py-to-exe
```
## 5. 项目结构
```
HarmonyDevTools_Python/
├── main.py # 主程序文件
├── build.py # 构建脚本
├── version_info.txt # 版本信息文件
├── update_version.py # 版本更新脚本
├── requirements.txt # Python依赖文件
├── README_Python.md # 说明文档
├── toolchains/ # 工具链目录
│ ├── hdc.exe # HDC工具
│ └── libusb_shared.dll
└── build/ # 打包输出目录(可选)
```
## 6. 功能说明
### 6.1 设备管理
- **列举设备**: 显示所有可用的HarmonyOS设备
- **连接设备**: 通过connect key连接指定设备
- **重启hdc**: 重启HDC服务
### 6.2 应用管理
- **安装hap**: 安装HarmonyOS应用包
- 替换安装: 覆盖已存在的应用
- 允许降级: 允许安装较低版本
- 动态授权: 动态授权安装
- **卸载应用**: 根据包名卸载应用
### 6.3 文件操作
- **导出照片**: 从设备导出照片到本地
- **自定义命令**: 执行任意hdc命令
### 6.4 系统信息
- **版本信息**: 显示HDC版本信息
- **UDID**: 获取设备唯一标识符
## 7. 与原C#版本的对比
| 特性 | C# WPF版本 | Python Tkinter版本 |
|------|------------|-------------------|
| 运行环境 | .NET Core 6.0 | Python 3.7+ |
| UI框架 | WPF | Tkinter |
| 打包大小 | 较大 | 较小 |
| 跨平台 | Windows | Windows/Linux/macOS |
| 开发难度 | 中等 | 简单 |
| 维护成本 | 中等 | 低 |
## 8. 故障排除
### 8.1 常见问题
1. **找不到hdc.exe**
- 确保`toolchains`目录存在且包含`hdc.exe`
- 检查文件路径是否正确
2. **Python未安装**
- 从[Python官网](https://www.python.org/downloads/)下载并安装Python
3. **tkinter模块错误**
- 大多数Python安装都包含tkinter
- 如果缺失请重新安装Python并确保选中tkinter选项
### 8.2 日志查看
程序运行时会输出日志信息,可以通过查看控制台输出来诊断问题。
## 9. 开发说明
### 9.1 代码结构
- `HdcUtil`: HDC工具类负责执行命令
- `HarmonyDevTools`: 主界面类负责UI交互
- `main()`: 程序入口函数
### 9.2 扩展功能
如需添加新功能,可以:
1. 在`HdcUtil`类中添加新的静态方法
2. 在`HarmonyDevTools`类中添加对应的UI元素和事件处理
3. 更新UI布局
## 10. 许可证
本项目遵循原项目的许可证条款。
## 11. 贡献
欢迎提交Issue和Pull Request来改进这个项目。
---
**注意**: 使用前请确保已正确安装HarmonyOS/OpenHarmony SDK并且`toolchains`目录包含所需的工具文件。

14
requirements.txt Normal file
View File

@ -0,0 +1,14 @@
# Python Tkinter项目依赖
# 注意tkinter是Python标准库的一部分通常不需要额外安装
# 但为了确保项目完整性,这里列出所有可能的依赖
# 如果需要额外的GUI增强功能可以添加以下依赖
# tkinter-tooltip==2.0.0 # 工具提示功能
# tkinterdnd2==0.3.0 # 拖拽功能
# 日志相关(可选)
# colorlog==6.7.0 # 彩色日志输出
# 打包相关用于生成exe文件
# pyinstaller==5.13.0 # 打包为exe文件
# auto-py-to-exe==2.32.0 # GUI打包工具

37
run.bat Normal file
View File

@ -0,0 +1,37 @@
@echo off
chcp 65001 >nul
echo ========================================
echo HarmonyDevTools Python版本启动脚本
echo ========================================
REM 检查Python是否安装
python --version >nul 2>&1
if errorlevel 1 (
echo 错误未找到Python请先安装Python 3.7或更高版本
echo 下载地址https://www.python.org/downloads/
pause
exit /b 1
)
REM 检查主程序文件是否存在
if not exist "main.py" (
echo 错误找不到main.py文件
pause
exit /b 1
)
REM 检查toolchains目录
if not exist "toolchains" (
echo 警告找不到toolchains目录请确保包含hdc.exe文件
echo.
)
echo 正在启动HarmonyDevTools...
echo.
python main.py
echo.
echo 程序已退出
pause

86
update_version.py Normal file
View File

@ -0,0 +1,86 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
版本信息更新脚本
用于快速更新版本号
"""
import re
import os
def update_version_info(new_version):
"""更新版本信息文件"""
version_file = "version_info.txt"
if not os.path.exists(version_file):
print(f"错误:找不到 {version_file} 文件")
return False
# 解析版本号
version_parts = new_version.split('.')
if len(version_parts) < 3:
print("错误:版本号格式应为 x.y.z (例如: 1.4.0)")
return False
try:
major, minor, patch = map(int, version_parts[:3])
build = 0 # 第四位默认为0
except ValueError:
print("错误:版本号必须为数字")
return False
# 读取文件内容
with open(version_file, 'r', encoding='utf-8') as f:
content = f.read()
# 更新版本号
content = re.sub(r'filevers=\(\d+, \d+, \d+, \d+\)',
f'filevers=({major}, {minor}, {patch}, {build})', content)
content = re.sub(r'prodvers=\(\d+, \d+, \d+, \d+\)',
f'prodvers=({major}, {minor}, {patch}, {build})', content)
content = re.sub(r"StringStruct\(u'FileVersion', u'[^']+'\)",
f"StringStruct(u'FileVersion', u'{new_version}')", content)
content = re.sub(r"StringStruct\(u'ProductVersion', u'[^']+'\)",
f"StringStruct(u'ProductVersion', u'{new_version}')", content)
# 写回文件
with open(version_file, 'w', encoding='utf-8') as f:
f.write(content)
print(f"版本信息已更新为: {new_version}")
print(f"下次构建将生成: HarmonyDevTools_v{new_version.replace('.', '_')}.exe")
print("注意构建时会通过命令行参数传递版本号main.py中的VERSION变量保持不变")
return True
def main():
"""主函数"""
print("=" * 50)
print("HarmonyDevTools 版本更新工具")
print("=" * 50)
# 显示当前版本
if os.path.exists("version_info.txt"):
with open("version_info.txt", 'r', encoding='utf-8') as f:
content = f.read()
match = re.search(r"StringStruct\(u'FileVersion', u'([^']+)'\)", content)
if match:
current_version = match.group(1)
print(f"当前版本: {current_version}")
# 获取新版本号
print("\n请输入新版本号 (格式: x.y.z例如: 1.4.0)")
new_version = input("新版本号: ").strip()
if not new_version:
print("未输入版本号,操作取消")
return
# 更新版本信息
if update_version_info(new_version):
print("\n版本更新完成!")
print("现在可以运行 python build.py 来构建新版本")
else:
print("\n版本更新失败!")
if __name__ == "__main__":
main()

43
version_info.txt Normal file
View File

@ -0,0 +1,43 @@
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(1, 4, 0, 0),
prodvers=(1, 4, 0, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x40004,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'HarmonyDevTools'),
StringStruct(u'FileDescription', u'HarmonyOS/OpenHarmony开发工具'),
StringStruct(u'FileVersion', u'1.4.0'),
StringStruct(u'InternalName', u'HarmonyDevTools'),
StringStruct(u'LegalCopyright', u'Copyright (C) 2025 DevWiki'),
StringStruct(u'OriginalFilename', u'HarmonyDevTools.exe'),
StringStruct(u'ProductName', u'HarmonyDevTools'),
StringStruct(u'ProductVersion', u'1.4.0')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)