ONEMORE的诞生

在公司其他事业部有一个工具包bsptools集成了一些常用的工具,咨询了作者不开源。

同时设计的UI界面因为偏重于实用,界面设计也较为复古

受到此软件的启发,有了制作一款高度自定义的集成工具的想法,OneMore由此诞生

下图为软件登录界面

框架设计

OneMore使用了pyqt6+fluent-widgets进行开发,以侧边导航栏为分类,以新建标签页的形式来呈现每个工具的功能。将工具所需要的变量以可视化的方法交由使用人员配置进行。

2025/07/halo_ahwjoha.png

app/common: 存放通用的一些接口

app/resource: 存放资源文件,包括图标,背景图片,语言翻译文件等等

app/main_window.py: 主窗口文件

app/mtk_interface.py: mtk界面设计界面

app/qcom_interface.py: 高通界面设计界面

app/register_window.py: 账号登录界面

app/setting_interface.py: 设置界面

tools: 存放一些通用的工具

plugins: 插件安装目录

release: 存放插件的压缩包目录

已实现功能

  1. 完成登录界面

  2. 设置界面设计完成,包括深浅色模式切换、语言选择等等

  3. 导航栏设计完成,方便后续新增分类

  4. 标签页设计完成,方便后续分类中的插件新增标签页的

  5. 插件执行页面设计完成,以工具简介/工具详细描述/工具执行参数可视化等元素

  6. 上架插件市场

  7. 软件在线更新插件功能

插件开发指南

插件组成元素

一个插件包含5个部分

文件

作用

备注

plugin.py

插件入口函数,插件依次能够自动加载

logo.py

插件logo的图片源资源

metadata.json

描述插件的基本信息

工具目录

存放该插件外挂的工具目录

比如Linux-ramdump-parser-v2,如果不需要外挂,则可以不需要

插件interface

定义插件的使用card interface

如果不需要外挂工具,则可以不需要

plugin.py

from loguru import logger
from app.common.utils import generate_uuid
from plugins.Linux_Ramdump_Parser.LinuxRamdumpParserinterface import LinuxRamdumpParserInterface as LRDP     # 按照实际插件信息更改
from plugins.Linux_Ramdump_Parser.LinuxRamdumpParserinterface import LinuxRamdumpParserCardsInfo     # 按照实际插件信息更改
import os

UNIQUE_NAME = "Linux Ramdump Parser"    # 更改插件的名字
CURRENT_PLUGIN_DIR = os.path.dirname(__file__)

def get_route_key():
    """
    Generate a unique route key for the plugin.
    """
    return f"{UNIQUE_NAME} {generate_uuid()}"


def register(main_window):

    def on_open():

        routekey = get_route_key()
        interface = LRDP(mainWindow=main_window)         # 按照实际插件信息更改
        interface.addTab(routeKey=routekey, text=routekey, icon='logo.png')

        # 将插件的路由键添加到 main_window 的 TabRouteKeys 中, 以便於处理 Tab 切换
        main_window.TabRouteKeys.append(routekey)

    def on_tab_changed(route_key: str):
        state = -1
        for TabRouteKey in main_window.TabRouteKeys:
            if TabRouteKey == route_key:
                logger.info("[TAB CHANGED] Tab change to {}".format(TabRouteKey))
                widget = main_window.showInterface.findChild(LinuxRamdumpParserCardsInfo, TabRouteKey)  # 按照实际插件信息更改
                main_window.showInterface.setCurrentWidget(widget)
                state = 1
                break

        if state != 1:
                logger.warning(f"[TAB WARNING] can not handle Tab change,can not find route_key={route_key}")

    appcard = main_window.qcomInterface.addCard(os.path.join(CURRENT_PLUGIN_DIR, "logo.png"), "Linux Ramdump Parser", '@designed by iliuqi.', "Linux Ramdump Parser")  # 按照实际插件信息更改

    main_window.registerPluginOpener(UNIQUE_NAME, on_open)
    main_window.registerTabChangedHandler(UNIQUE_NAME, on_tab_changed)

上述注释标注的地方,需要按照插件的实际信息更改,其余地方如果不了解,请不要更改!

备注:

插件目前共有三个分类,分别是通用、高通和MTK。

如果需要控制插件注册到特定的分类中,则需要更改上述demo的倒数第三行addCard

分类

interface

通用

generalInterface

高通

qcomInterface

MTK

mtkInterface

比如控制插件注册到通用分类中,则该行改为

appcard = main_window.generalInterface.addCard(os.path.join(CURRENT_PLUGIN_DIR, "logo.png"), "Linux Ramdump Parser", '@designed by iliuqi.', "Linux Ramdump Parser")  # 按照实际插件信息更改

metadata.json

  {
    "name": "DTB2DTS",
    "description": "将设备树二进制文件(DTB)转换为设备树源文件(DTS)的工具",
    "version": "1.0.0",
    "author": "iliuqi",
    "logo": "logo.png",
    "entry": "plugin.py",
    "zip_url": "https://raw.githubusercontent.com/miniLQ/onemore/refs/heads/dev/release/DTB2DTS.zip"
  }

按照实际插件情况,设置插件的metadata.json

备注:目前,插件metadata.json中的zip_url属性无用,只是预留!插件市场下载更新插件是根据源码中的plugins/plugin_index.json中定义的插件下载地址进行处理的

插件interface

interface是描述插件的标签卡,也就是下图显示的内容

2025/07/halo_ahwjoha.png

关于这个interface的py脚本的详细逻辑,不在此详细描述,只描述需要值得注意的地方

AppInfoCard

AppInfoCard也就是上图中最上面的那一个块。

class AppInfoCard(SimpleCardWidget):
    """ App information card """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent 
        self.iconLabel = ImageLabel(os.path.join(CURRENT_PLUGIN_DIR, "logo.png"), self)   # 设置logo
        self.iconLabel.setBorderRadius(8, 8, 8, 8)
        self.iconLabel.scaledToWidth(120)

        self.nameLabel = TitleLabel('Android Images Editor', self)      # 设置标题

        #self.companyLabel = HyperlinkLabel(
        #    QUrl('https://qfluentwidgets.com'), 'Shokokawaii Inc.', self)
        self.companyLabel = CaptionLabel('@Designed by cfig.', self)     # 更改作者信息
        #self.installButton.setFixedWidth(160)

        #self.scoreWidget = StatisticsWidget('平均', '5.0', self)
        #self.separator = VerticalSeparator(self)
        #self.commentWidget = StatisticsWidget('评论数', '3K', self)

        self.descriptionLabel = BodyLabel(
            '本工具为github上的开源项目,本人只是将此工具直接集成到onemore中\n作者:cfig\ngithub地址:https://github.com/cfig/Android_boot_image_editor', self)     # 更改简要描述
        self.descriptionLabel.setWordWrap(True)

        self.tagButton = PillPushButton('android', self)       # 设置tag
        self.tagButton.setCheckable(False)
        setFont(self.tagButton, 12)
        self.tagButton.setFixedSize(80, 32)

        self.tagButton2 = PillPushButton('unpack', self)       # 设置tag
        self.tagButton2.setCheckable(False)
        setFont(self.tagButton2, 12)
        self.tagButton2.setFixedSize(80, 32)

DescriptionCard

这个选项卡是详细描述部分

class DescriptionCard(HeaderCardWidget):
    """ Description card """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.descriptionLabel = BodyLabel(
            '本工具为github上的开源项目,本人只是将此工具直接集成到onemore中\n作者:cfig\ngithub地址:https://github.com/cfig/Android_boot_image_editor\n'
            '使用本工具前必须先保证安装好JDK11+的java环境!!!', self) 

        self.descriptionLabel.setWordWrap(True)
        self.viewLayout.addWidget(self.descriptionLabel)
        self.setTitle('描述')
        self.setBorderRadius(8)

SettingsCard

这里为自定义可视化参数的选项卡,具体细节参考已开发的插件

runButtonClicked

这个是点击运行按钮后,会跳转的函数,我们需要在这里面封装我们需要执行的操作

    def runButtonClicked(self):
        logger.info("Run Button Clicked")
        logger.info("image file path: {}".format(self.inputfile))
        logger.info("output directory: {}".format(self.output))

        if self.comboBox.currentText() == "始终显示":
            shell = True
        else:
            shell = False

        logger.info("Display terminal: {}".format(shell))

        if self.output == "" or self.inputfile == "":
            self.showNoSelectFileFlyout()
        # 如果vmlinuxfile的文件名不为vmlinux
        elif not self.inputfile.endswith(".img"):
            # 提示vmlinux文件名不为vmlinux
            self.showFileStyleErrorFlyout()
        else:
            self.stateTooltip = StateToolTip('正在解析', '客官请耐心等待哦~~', self)
            self.bottomStateLayout.addWidget(self.stateTooltip, 0, Qt.AlignmentFlag.AlignRight)
            self.vBoxLayout.addLayout(self.bottomStateLayout)
            # 显示状态提示
            self.stateTooltip.show()

            # runbuton按钮设置为不可点击
            self.runButton.setDisabled(True)

            command = "copy /Y {} {} && CD /d {} && {} unpack && xcopy /Y /E /I {} {} && {} clear".format(
                self.inputfile,
                ANDROID_BOOT_IMAGE_EDITOR_PATH,
                ANDROID_BOOT_IMAGE_EDITOR_PATH,
                "gradlew.bat",
                os.path.normpath(os.path.join(ANDROID_BOOT_IMAGE_EDITOR_PATH, "build", "unzip_boot")).replace(os.sep, os.path.normcase(os.sep)),
                os.path.normpath(os.path.join(self.output, "unzip_boot")).replace(os.sep, os.path.normcase(os.sep)),
                "gradlew.bat")
            self.start_task(command, shell=shell)

插件上传

开发好后的插件,需要压缩成zip格式压缩包,上传至master分支的release目录下

同时需要更新plugin_index.json上架市场

上架插件市场

[
  {
    "name": "DTB2DTS",
    "description": "将设备树二进制文件(DTB)转换为设备树源文件(DTS)的工具",
    "version": "1.0.0",
    "author": "iliuqi",
    "logo": "plugin_resources/logo/logo_DTB2DTS.png",
    "entry": "plugin.py",
    "zip_url": "https://raw.githubusercontent.com/miniLQ/onemore/refs/heads/master/release/DTB2DTS.zip"
  },
  {
    "name": "Android_Images_Unpack",
    "description": "高通平台开发提供给研发人员进行TZ log解析的一个工具",
    "version": "v15_r1",
    "author": "iliuqi",
    "logo": "plugin_resources/logo/logo_Android_Images_Unpack.png",
    "entry": "plugin.py",
    "zip_url": "https://raw.githubusercontent.com/miniLQ/onemore/refs/heads/master/release/Android_Images_Unpack.zip"
  },
  ...

]

插入的内容与metadata.json内容保持一致即可

共同开发

本着Linux开源的思想,本项目完全开源!

项目地址:

https://github.com/miniLQ/onemore

  1. 如果您对此项目感兴趣,并且有能力协助一起开发,期待您的PR(https://github.com/miniLQ/onemore/pulls)。

  2. 如果你对此项目有一些idea,也可以在issue(https://github.com/miniLQ/onemore/issues)提出您的建议,谢谢!