wails 和 gopsutil 构建系统工具应用(二)

691 阅读4分钟


在上一篇文章中,我们介绍了如何使用 wails 与第三方扩展包 getlantern/systray 配合开发带有托盘功能的系统工具。由于 wails v2 版本并不原生支持 systray 类似的托盘功能,因此我们通过将 getlantern/systray 作为子程序运行,并且通过进程间通信来解决这一问题。

这篇文章将详细讲解如何实现两个进程之间的通信,并完善相关功能。

进程间通信

在这个项目中,使用管道(pipe)来在主程序和子进程之间进行数据通信。管道是一种非常轻量的通信方式,适用于本地的进程间通信场景。

wails 的启动与关闭

wails 的应用中,需要在程序启动和关闭时处理与子进程的通信。为此,在 OnStartupOnShutdown 回调中设置相应的逻辑:

OnStartup:        app.startup,  OnShutdown:       app.closeup,

管道的创建与管理

app.gostartup 方法中,创建了两个管道,一个用于主程序向子进程发送数据,另一个用于接收子进程发送的数据。

func (a *App) startup(ctx context.Context) {      a.ctx = ctx      // 创建两个管道:一个用于发送数据,一个用于接收数据      sendPipeR, sendPipeW, err := os.Pipe() // 用于主程序向子进程发送数据      if err != nil {         fmt.Println("创建发送管道失败:", err)         return      }      receivePipeR, receivePipeW, err := os.Pipe() // 用于子进程向主程序发送数据      if err != nil {         fmt.Println("创建接收管道失败:", err)         return      }      a.sendPipeW = sendPipeW       // 主程序向子进程写      a.sendPipeR = sendPipeR       // 主程序向子进程写      a.receivePipeR = receivePipeR // 主程序从子进程读      a.receivePipeW = receivePipeW // 主程序从子进程读        go func() {         // 启动 systray_run.go 程序         cmd := exec.Command("go", "run", "./pak/sys_run/systray_run.go")         cmd.Stdin = a.sendPipeR         cmd.Stdout = a.receivePipeW         cmd.Stderr = os.Stderr         if err := cmd.Start(); err != nil {            fmt.Println("启动 systray_run.go 失败:", err)            return         }         if err := cmd.Wait(); err != nil {            fmt.Println("systray_run.go 执行失败:", err)         }    }()      a.monitorPipe()  }

monitorPipe 方法监听子进程向主程序发送的数据,并根据接收到的指令进行操作,例如退出程序、显示或隐藏主窗口:

func (a *App) monitorPipe() {      reader := bufio.NewReader(a.receivePipeR)      for {         line, err := reader.ReadString('\n') // 读取一行直到换行符         if err != nil {            if err == io.EOF {               fmt.Println("管道关闭")               break // 管道关闭,结束读取            }            fmt.Println("读取管道数据失败:", err)            continue         }         // 处理从 systray_run.go 中接收到的输出         fmt.Printf("从 systray_run.go 接收到: %s", line)         switch line {         case "systray_run: quit\n":            fmt.Println("收到退出请求")            closeType = 1            runtime.Quit(a.ctx)            break         case "systray_run: panel show\n":            fmt.Println("收到控制面板请求")            runtime.WindowShow(a.ctx)    // 窗口显示          break         case "systray_run: panel hide\n":            fmt.Println("收到控制面板请求")            runtime.WindowHide(a.ctx)   // 窗口隐藏          break         }      }}

子程序 systray 的实现

systray_run.go 中,托盘菜单设置了“控制面板”和“退出”选项。点击这些选项时,发送相应的指令给主程序。以下是主要的实现逻辑:

package main    import (      "bufio"      "fmt"    "github.com/getlantern/systray"    "github.com/getlantern/systray/example/icon"    "io"    "os")    var show = true    func onReady() {      systray.SetIcon(icon.Data)      systray.SetTitle("CPU usage: 0%")      systray.SetTooltip("PFinal南丞")      Panel := systray.AddMenuItem("控制面板", "Panel")      mQuit := systray.AddMenuItem("退出", "Quit the whole app")      go func() {         for {            select {            case <-Panel.ClickedCh:               toggerPanel()            case <-mQuit.ClickedCh:               sendQuitMessage()            }       }    }()    go ListenToMain()      mQuit.SetIcon(icon.Data)      Panel.SetIcon(icon.Data)      // 启动 TCP 服务器      systray.Run(onReady, nil)  }    func ListenToMain() {      reader := bufio.NewReader(os.Stdin)      for {         line, err := reader.ReadString('\n')         if err != nil {            if err == io.EOF {               fmt.Println("管道关闭")               break // 管道关闭,结束读取            }            fmt.Println("读取标准输入失败:", err)            continue         }         fmt.Println("从标准输入读取到:" + line)         if line == "quit\n" {            fmt.Println("收到退出请求")            // 可以添加其他处理逻辑            systray.Quit() // 根据需要执行退出操作         }      }}    func sendQuitMessage() {      // 发送退出消息给主程序      _, _ = fmt.Fprintln(os.Stdout, "systray_run: quit") // 发送特定的退出消息      systray.Quit()  }    var toggerPanel = func() {      if show {         show = false         // 发送退出消息给主程序         _, _ = fmt.Fprintln(os.Stdout, "systray_run: panel hide") // 发送特定的退出消息      } else {         show = true         // 发送退出消息给主程序         _, _ = fmt.Fprintln(os.Stdout, "systray_run: panel show") // 发送特定的退出消息      }  }    func main() {      onReady()  }

通过 ListenToMain 方法,监听来自 wails 主程序的消息,做出相应的操作,比如当接收到 quit 消息时,托盘程序将退出。

通过上面, 实现了 wailssystray 的协同工作,并通过管道实现了两个进程之间的通信。此方法不仅解决了 wails 原生不支持托盘功能的问题,还为今后多进程应用的开发提供了很好的参考

动态调整窗口大小

在之前的项目中,遇到了一个挑战:即如何在不同游戏之间保持窗口大小的一致性。这个问题最终通过查阅 wails 的文档得到了解决。发现可以动态地调整窗口大小,因此在代码中添加了对 runtime.WindowSetSizeruntime.WindowReload 的调用,以实现在游戏间切换时自动调整窗口尺寸的功能。

具体来说,Greet 方法不仅返回问候信息,还在每次调用时动态地设置了窗口大小,并重新加载了窗口,确保了用户体验的一致性和流畅性。

func (a *App) Greet(name string) string {      runtime.WindowSetSize(a.ctx, 1000, 500)    // 动态设置窗口大小    runtime.WindowReload(a.ctx)                // 重新加载窗口    return fmt.Sprintf("Hello %s, It's show time!", name)  }

总结

通过上述功能的开发,实现了 窗口托盘程序的联动, 接下来就可以动手开发, 监听系统的功能了.

最后

🌟 更多精彩内容等你发现!关注【PFinalClub】 公众号,成为我们的一员,让我们一起在编程的海洋中探索、学习、成长!