mirror of
https://github.com/fumiama/go-web-wrapper.git
synced 2026-06-05 00:32:43 +08:00
init
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,3 +30,6 @@ go.work.sum
|
|||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
||||||
|
/dist.zip
|
||||||
|
.DS_Store
|
||||||
|
|||||||
17
README.md
Normal file
17
README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# go-web-wrapper
|
||||||
|
A simple template that wrap your static website into an executable file.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
1. Place `dist.zip` at project root dir (include all your website files like index.html, etc.).
|
||||||
|
2. Build project.
|
||||||
|
- Windows
|
||||||
|
```bash
|
||||||
|
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -H=windowsgui" -trimpath -o web.exe
|
||||||
|
```
|
||||||
|
- Unix-Like
|
||||||
|
```bash
|
||||||
|
# MacOS ARM
|
||||||
|
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -trimpath -o web_darwin_arm64
|
||||||
|
# Linux AMD64
|
||||||
|
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -trimpath -o web_linux_amd64
|
||||||
|
```
|
||||||
6
config.go
Normal file
6
config.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
Endpoint = "127.0.0.1:8381"
|
||||||
|
WindowTitle = "预览" // for windows only
|
||||||
|
)
|
||||||
31
dist.go
Normal file
31
dist.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist.zip
|
||||||
|
var distzipbytes []byte
|
||||||
|
|
||||||
|
var distribution = func() http.FileSystem {
|
||||||
|
distzip, err := zip.NewReader(bytes.NewReader(distzipbytes), int64(len(distzipbytes)))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return http.FS(fs.FS(distzip))
|
||||||
|
}()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.Handle("/", http.FileServer(distribution)) // frontend
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
fmt.Println("server quit for", http.ListenAndServe(Endpoint, nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Println("正在展示:", Endpoint)
|
||||||
|
}
|
||||||
30
main_unix.go
Normal file
30
main_unix.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//go:build unix
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
var cmd string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
cmd = "open"
|
||||||
|
case "linux":
|
||||||
|
cmd = "xdg-open"
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported platform")
|
||||||
|
}
|
||||||
|
return exec.Command(cmd, url).Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := openBrowser("http://" + Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
select {}
|
||||||
|
}
|
||||||
261
main_windows.go
Normal file
261
main_windows.go
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
// ① 隐藏控制台窗口(如果存在)
|
||||||
|
hConsole, _, _ := procGetConsoleWindow.Call()
|
||||||
|
if hConsole != 0 {
|
||||||
|
procShowWindow.Call(hConsole, SW_HIDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ② 获取当前模块句柄
|
||||||
|
hInstance, _, _ = procGetModuleHandleW.Call(0)
|
||||||
|
|
||||||
|
// ③ 注册窗口类
|
||||||
|
className := syscall.StringToUTF16Ptr("MyWindowClass")
|
||||||
|
wndClass := WNDCLASSEX{
|
||||||
|
CbSize: uint32(unsafe.Sizeof(WNDCLASSEX{})),
|
||||||
|
Style: 0,
|
||||||
|
LpfnWndProc: syscall.NewCallback(WndProc),
|
||||||
|
CbClsExtra: 0,
|
||||||
|
CbWndExtra: 0,
|
||||||
|
HInstance: hInstance,
|
||||||
|
HIcon: 0,
|
||||||
|
HCursor: 0,
|
||||||
|
// 背景色使用 COLOR_WINDOW+1(6)
|
||||||
|
HbrBackground: uintptr(6),
|
||||||
|
LpszMenuName: nil,
|
||||||
|
LpszClassName: className,
|
||||||
|
HIconSm: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, _, err := procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wndClass)))
|
||||||
|
if ret == 0 {
|
||||||
|
panic("RegisterClassExW failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ④ 创建窗口
|
||||||
|
windowTitle := syscall.StringToUTF16Ptr(WindowTitle)
|
||||||
|
hWnd, _, err := procCreateWindowExW.Call(
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(className)),
|
||||||
|
uintptr(unsafe.Pointer(windowTitle)),
|
||||||
|
WS_OVERLAPPEDWINDOW&^WS_THICKFRAME, // 移除 WS_THICKFRAME 样式
|
||||||
|
100, 100, 400, 150, // 窗口位置和大小
|
||||||
|
0, 0,
|
||||||
|
hInstance,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if hWnd == 0 {
|
||||||
|
panic("CreateWindowExW failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⑤ 创建控件:文本框和按钮
|
||||||
|
createControls(hWnd)
|
||||||
|
|
||||||
|
// 显示并更新窗口
|
||||||
|
procShowWindow.Call(hWnd, SW_SHOW)
|
||||||
|
procUpdateWindow.Call(hWnd)
|
||||||
|
|
||||||
|
// ⑥ 消息循环
|
||||||
|
var msg MSG
|
||||||
|
for {
|
||||||
|
ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0)
|
||||||
|
if ret == 0 { // 收到 WM_QUIT 消息
|
||||||
|
break
|
||||||
|
}
|
||||||
|
procDispatchMessageW.Call(uintptr(unsafe.Pointer(&msg)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createControls(parent uintptr) {
|
||||||
|
// 创建宋体字体
|
||||||
|
font := createFont("宋体")
|
||||||
|
|
||||||
|
// 创建一个不可编辑的文本框,显示 "正在展示"
|
||||||
|
editClass := syscall.StringToUTF16Ptr("EDIT")
|
||||||
|
text := syscall.StringToUTF16Ptr("正在展示: " + Endpoint)
|
||||||
|
editHWnd, _, _ := procCreateWindowExW.Call(
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(editClass)),
|
||||||
|
uintptr(unsafe.Pointer(text)),
|
||||||
|
uintptr(WS_CHILD|WS_VISIBLE|ES_CENTER),
|
||||||
|
10, 20, 370, 40,
|
||||||
|
parent,
|
||||||
|
0,
|
||||||
|
hInstance,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
// 设置宋体字体
|
||||||
|
procSendMessageW.Call(editHWnd, WM_SETFONT, font, 1)
|
||||||
|
|
||||||
|
// 创建按钮控件,显示“按钮”,并指定控制 ID 为 ID_BUTTON_OPEN
|
||||||
|
buttonClass := syscall.StringToUTF16Ptr("BUTTON")
|
||||||
|
buttonText := syscall.StringToUTF16Ptr("打开页面")
|
||||||
|
buttonHWnd, _, _ := procCreateWindowExW.Call(
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(buttonClass)),
|
||||||
|
uintptr(unsafe.Pointer(buttonText)),
|
||||||
|
uintptr(WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON),
|
||||||
|
10, 70, 365, 30,
|
||||||
|
parent,
|
||||||
|
uintptr(ID_BUTTON_OPEN), // 这里设置按钮的ID
|
||||||
|
hInstance,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
// 设置宋体字体
|
||||||
|
procSendMessageW.Call(buttonHWnd, WM_SETFONT, font, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口和控件样式、消息常量
|
||||||
|
const (
|
||||||
|
WS_OVERLAPPEDWINDOW = 0x00CF0000 // 标准窗口样式(包含标题栏、系统菜单、可调整大小等)
|
||||||
|
WS_CHILD = 0x40000000 // 子窗口样式
|
||||||
|
WS_VISIBLE = 0x10000000 // 可见样式
|
||||||
|
WS_BORDER = 0x00800000 // 边框样式
|
||||||
|
WS_CAPTION = 0x00C00000 // 标题栏样式
|
||||||
|
WS_SYSMENU = 0x00080000 // 系统菜单样式
|
||||||
|
WS_THICKFRAME = 0x00040000 // 可调整大小的边框样式
|
||||||
|
|
||||||
|
BS_DEFPUSHBUTTON = 0x00000001 // 默认按钮样式
|
||||||
|
|
||||||
|
ES_CENTER = 0x00000001
|
||||||
|
|
||||||
|
SW_HIDE = 0
|
||||||
|
SW_SHOW = 5
|
||||||
|
|
||||||
|
WM_DESTROY = 0x0002
|
||||||
|
WM_COMMAND = 0x0111
|
||||||
|
WM_SETFONT = 0x0030
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 定义一个退出按钮的ID
|
||||||
|
ID_BUTTON_OPEN = 1001
|
||||||
|
)
|
||||||
|
|
||||||
|
// 结构体定义,与 WIN32 相同
|
||||||
|
type POINT struct {
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type MSG struct {
|
||||||
|
Hwnd uintptr
|
||||||
|
Message uint32
|
||||||
|
WParam uintptr
|
||||||
|
LParam uintptr
|
||||||
|
Time uint32
|
||||||
|
Pt POINT
|
||||||
|
}
|
||||||
|
|
||||||
|
type WNDCLASSEX struct {
|
||||||
|
CbSize uint32
|
||||||
|
Style uint32
|
||||||
|
LpfnWndProc uintptr
|
||||||
|
CbClsExtra int32
|
||||||
|
CbWndExtra int32
|
||||||
|
HInstance uintptr
|
||||||
|
HIcon uintptr
|
||||||
|
HCursor uintptr
|
||||||
|
HbrBackground uintptr
|
||||||
|
LpszMenuName *uint16
|
||||||
|
LpszClassName *uint16
|
||||||
|
HIconSm uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 加载 DLL
|
||||||
|
user32 = syscall.NewLazyDLL("user32.dll")
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
shell32 = syscall.NewLazyDLL("shell32.dll")
|
||||||
|
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||||||
|
|
||||||
|
// 接口函数
|
||||||
|
procDefWindowProcW = user32.NewProc("DefWindowProcW")
|
||||||
|
procDispatchMessageW = user32.NewProc("DispatchMessageW")
|
||||||
|
procGetMessageW = user32.NewProc("GetMessageW")
|
||||||
|
procRegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||||
|
procCreateWindowExW = user32.NewProc("CreateWindowExW")
|
||||||
|
procShowWindow = user32.NewProc("ShowWindow")
|
||||||
|
procUpdateWindow = user32.NewProc("UpdateWindow")
|
||||||
|
procPostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||||
|
procSendMessageW = user32.NewProc("SendMessageW")
|
||||||
|
|
||||||
|
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||||
|
procGetConsoleWindow = kernel32.NewProc("GetConsoleWindow")
|
||||||
|
|
||||||
|
shellExecute = shell32.NewProc("ShellExecuteW")
|
||||||
|
|
||||||
|
procCreateFontIndirectW = gdi32.NewProc("CreateFontIndirectW")
|
||||||
|
)
|
||||||
|
|
||||||
|
var hInstance uintptr
|
||||||
|
|
||||||
|
// 窗口过程函数,处理窗口消息
|
||||||
|
func WndProc(hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
|
switch msg {
|
||||||
|
case WM_COMMAND:
|
||||||
|
// 当点击控件时,wParam 的低 16 位为控件 ID
|
||||||
|
if uint16(wParam) == ID_BUTTON_OPEN {
|
||||||
|
// 调用 ShellExecuteW 打开默认浏览器
|
||||||
|
// 第一个参数为 0 表示没有窗口句柄,最后一个参数指定需要打开的 URL
|
||||||
|
url := "http://" + Endpoint
|
||||||
|
shellExecute.Call(0,
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("open"))),
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(url))),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1) // 1 表示普通窗口打开
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
case WM_DESTROY:
|
||||||
|
procPostQuitMessage.Call(0)
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
ret, _, _ := procDefWindowProcW.Call(hWnd, uintptr(msg), wParam, lParam)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建指定字体(如宋体)
|
||||||
|
func createFont(fontName string) uintptr {
|
||||||
|
font := &struct {
|
||||||
|
Height int32
|
||||||
|
Width int32
|
||||||
|
Escapement int32
|
||||||
|
Orientation int32
|
||||||
|
Weight int32
|
||||||
|
Italic byte
|
||||||
|
Underline byte
|
||||||
|
StrikeOut byte
|
||||||
|
CharSet byte
|
||||||
|
OutPrecision byte
|
||||||
|
ClipPrecision byte
|
||||||
|
Quality byte
|
||||||
|
PitchAndFamily byte
|
||||||
|
FaceName [32]uint16
|
||||||
|
}{}
|
||||||
|
|
||||||
|
font.Height = 20 // 字体高度
|
||||||
|
font.Weight = 800 // 字体粗细
|
||||||
|
font.CharSet = 1 // 默认字符集
|
||||||
|
font.OutPrecision = 4 // 输出精度
|
||||||
|
font.ClipPrecision = 2 // 裁剪精度
|
||||||
|
font.Quality = 1 // 质量
|
||||||
|
font.PitchAndFamily = 0x31 // 字体类型
|
||||||
|
copy(font.FaceName[:], syscall.StringToUTF16(fontName))
|
||||||
|
|
||||||
|
hFont, _, _ := procCreateFontIndirectW.Call(uintptr(unsafe.Pointer(font)))
|
||||||
|
return hFont
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user