WIP: mini-window approach for scroll capture

Tentativa de capturar scroll via mini janela sempre-no-topo.
Inclui: MoveWindow/GetMonitorWorkArea via Win32, park consistente
em wx+150/wy+50, ShowCursor loop no goroutine pós-resize.
Revertível: git checkout HEAD~1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo Carneiro 2026-04-24 16:51:00 -03:00
parent 6bdf5fcdbd
commit 5d016d7087
9 changed files with 336 additions and 118 deletions

View File

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(go build:*)"
]
}
}

48
app.go
View File

@ -8,12 +8,19 @@ import (
"kvmote/internal/input" "kvmote/internal/input"
"kvmote/internal/kvm" "kvmote/internal/kvm"
"kvmote/internal/transport" "kvmote/internal/transport"
"github.com/wailsapp/wails/v2/pkg/runtime"
) )
// App struct // App struct
type App struct { type App struct {
ctx context.Context ctx context.Context
engine *kvm.Engine engine *kvm.Engine
// Estado da janela (pixels físicos Win32)
origX, origY int32
origW, origH int32
isMini bool
} }
// NewApp creates a new App application struct // NewApp creates a new App application struct
@ -21,9 +28,48 @@ func NewApp() *App {
t := transport.NewBleTransport() t := transport.NewBleTransport()
h := input.NewInputHandler() h := input.NewInputHandler()
e := kvm.NewEngine(t, h) e := kvm.NewEngine(t, h)
return &App{ app := &App{
engine: e, engine: e,
} }
e.SetWindowManager(app) // Vincula o app como gerenciador de janela da engine
return app
}
const miniW, miniH = int32(300), int32(100)
func (a *App) SetMiniMode() {
if a.isMini {
return
}
// Salva posição/tamanho via Win32 (pixels físicos — mesma base do MoveWindow)
wx, wy := a.engine.GetWindowPos()
ww, wh := a.engine.GetWindowSize()
if ww > 200 {
a.origX, a.origY = wx, wy
a.origW, a.origH = ww, wh
}
a.isMini = true
runtime.EventsEmit(a.ctx, "window-mode", "mini")
// Posiciona usando Win32 SetWindowPos (evita mistura de coordenadas lógicas/físicas)
// MonitorFromWindow garante monitor correto em setups multi-monitor
mx, my, mw, mh := a.engine.GetMonitorWorkArea()
tx := mx + mw - miniW - 20
ty := my + mh - miniH - 50
a.engine.MoveWindow(tx, ty, miniW, miniH, true) // topmost=true
}
func (a *App) RestoreNormalMode() {
if !a.isMini {
return
}
a.isMini = false
runtime.EventsEmit(a.ctx, "window-mode", "normal")
// Restaura via Win32 (mesma base de coordenadas do SetMiniMode)
a.engine.MoveWindow(a.origX, a.origY, a.origW, a.origH, false)
} }
// startup is called when the app starts. The context is saved // startup is called when the app starts. The context is saved

View File

@ -115,72 +115,97 @@
font-weight: bold; font-weight: bold;
} }
.btn-cad { margin-top: 10px; background: #2a2a2a; color: #ccc; } .btn-cad { margin-top: 10px; background: #2a2a2a; color: #ccc; }
/* Estilo Neon para Modo Mini */
.neon-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background-color: black;
cursor: default !important; /* Força cursor visível */
}
.neon-text {
font-size: 1.6rem;
font-weight: bold;
color: #39FF14;
text-shadow: 0 0 5px #39FF14, 0 0 10px #39FF14;
letter-spacing: 1px;
}
</style> </style>
</head> </head>
<body x-data="kvmApp()"> <body x-data="kvmApp()" style="cursor: default !important;">
<div class="app-container"> <template x-if="mode === 'normal'">
<div class="section-title">Posição do PC Cliente:</div> <div class="app-container">
<div class="section-title">Posição do PC Cliente:</div>
<div class="monitor-grid">
<!-- Top -->
<button class="monitor-btn" style="grid-column: 2; grid-row: 1;"
:class="pos === 'above' ? 'active' : ''" @click="setPos('above')">Acima</button>
<!-- Left --> <div class="monitor-grid">
<button class="monitor-btn" style="grid-column: 1; grid-row: 2;" <!-- Top -->
:class="pos === 'left' ? 'active' : ''" @click="setPos('left')">Esquerda</button> <button class="monitor-btn" style="grid-column: 2; grid-row: 1;"
:class="pos === 'above' ? 'active' : ''" @click="setPos('above')">Acima</button>
<!-- Center -->
<div class="host-pc">[HOST PC]</div> <!-- Left -->
<button class="monitor-btn" style="grid-column: 1; grid-row: 2;"
<!-- Right --> :class="pos === 'left' ? 'active' : ''" @click="setPos('left')">Esquerda</button>
<button class="monitor-btn" style="grid-column: 3; grid-row: 2;"
:class="pos === 'right' ? 'active' : ''" @click="setPos('right')">Direita</button> <!-- Center -->
<div class="host-pc">[HOST PC]</div>
<!-- Bottom -->
<button class="monitor-btn" style="grid-column: 2; grid-row: 3;" <!-- Right -->
:class="pos === 'below' ? 'active' : ''" @click="setPos('below')">Abaixo</button> <button class="monitor-btn" style="grid-column: 3; grid-row: 2;"
</div> :class="pos === 'right' ? 'active' : ''" @click="setPos('right')">Direita</button>
<div class="is-flex is-align-items-center mb-4" style="font-size: 0.85rem; color: #888;"> <!-- Bottom -->
<span class="status-dot" :style="{ backgroundColor: detected ? '#3498db' : '#555' }"></span> <button class="monitor-btn" style="grid-column: 2; grid-row: 3;"
<span x-text="detected ? 'KVMote (BLE) detectado' : 'Buscando dispositivo...'"></span> :class="pos === 'below' ? 'active' : ''" @click="setPos('below')">Abaixo</button>
</div>
<div class="columns is-mobile is-gapless mb-2">
<div class="column pr-1">
<button class="btn-action" @click="detect()">Detectar</button>
</div> </div>
<div class="column pl-1">
<button class="btn-action btn-connect" @click="toggleConnect()" x-text="connected ? 'Desconectar' : 'Conectar'"></button> <div class="is-flex is-align-items-center mb-4" style="font-size: 0.85rem; color: #888;">
<span class="status-dot" :style="{ backgroundColor: detected ? '#3498db' : '#555' }"></span>
<span x-text="detected ? 'KVMote (BLE) detectado' : 'Buscando dispositivo...'"></span>
</div>
<div class="columns is-mobile is-gapless mb-2">
<div class="column pr-1">
<button class="btn-action" @click="detect()">Detectar</button>
</div>
<div class="column pl-1">
<button class="btn-action btn-connect" @click="toggleConnect()" x-text="connected ? 'Desconectar' : 'Conectar'"></button>
</div>
</div>
<div class="field mb-4">
<div class="is-flex is-align-items-center is-justify-content-space-between">
<label class="field-label">Layout do cliente:</label>
<select class="custom-select" style="width: 60%;" x-model="layout" @change="setLayout()">
<option value="0">US / EN</option>
<option value="1">PT-BR ABNT2</option>
<option value="2">US International</option>
</select>
</div>
</div>
<button class="btn-action btn-cad" @click="sendCAD()">Enviar Ctrl+Alt+Del</button>
<div class="field mt-4">
<div class="is-flex is-align-items-center is-justify-content-space-between">
<label class="field-label">Ao fechar:</label>
<select class="custom-select" style="width: 60%;">
<option value="tray">Minimizar para Tray</option>
<option value="exit">Fechar aplicação</option>
</select>
</div>
</div> </div>
</div> </div>
</template>
<div class="field mb-4"> <template x-if="mode === 'mini'">
<div class="is-flex is-align-items-center is-justify-content-space-between"> <div class="neon-container" id="mini-bg">
<label class="field-label">Layout do cliente:</label> <div class="neon-text" x-text="'KVMote [' + scrollCount + ']'"></div>
<select class="custom-select" style="width: 60%;" x-model="layout" @change="setLayout()">
<option value="0">US / EN</option>
<option value="1">PT-BR ABNT2</option>
<option value="2">US International</option>
</select>
</div>
</div> </div>
</template>
<button class="btn-action btn-cad" @click="sendCAD()">Enviar Ctrl+Alt+Del</button> <div class="status-bar" x-show="mode === 'normal'">
<div class="field mt-4">
<div class="is-flex is-align-items-center is-justify-content-space-between">
<label class="field-label">Ao fechar:</label>
<select class="custom-select" style="width: 60%;">
<option value="tray">Minimizar para Tray</option>
<option value="exit">Fechar aplicação</option>
</select>
</div>
</div>
</div>
<div class="status-bar">
<span x-text="statusText"></span> <span x-text="statusText"></span>
</div> </div>
@ -197,6 +222,31 @@
statusText: 'Desconectado', statusText: 'Desconectado',
pos: 'right', pos: 'right',
layout: 1, layout: 1,
mode: 'normal',
scrollCount: 0,
init() {
if (window.runtime) {
window.runtime.EventsOn("window-mode", (m) => {
this.mode = m;
});
}
// Listener de Scroll interno ao kvmApp para acessar scrollCount
window.addEventListener('wheel', (event) => {
if (this.mode === 'mini') {
this.scrollCount++;
// Feedback visual rápido
const bg = document.getElementById('mini-bg');
if (bg) {
bg.style.backgroundColor = '#1a3300';
setTimeout(() => bg.style.backgroundColor = 'black', 50);
}
}
if (window.go && window.go.main && window.go.main.App) {
window.go.main.App.HandleScroll(Math.round(event.deltaY));
}
}, { passive: false });
},
toggleConnect() { toggleConnect() {
if (this.connected) { if (this.connected) {
window.go.main.App.Disconnect().then(res => { window.go.main.App.Disconnect().then(res => {
@ -229,9 +279,7 @@
// Captura o scroll globalmente na janela do KVMote // Captura o scroll globalmente na janela do KVMote
window.addEventListener('wheel', (event) => { window.addEventListener('wheel', (event) => {
// Log no console do navegador para você ver se disparar (inspecionar elemento)
console.log("Wheel event:", event.deltaY); console.log("Wheel event:", event.deltaY);
if (window.go && window.go.main && window.go.main.App) { if (window.go && window.go.main && window.go.main.App) {
window.go.main.App.HandleScroll(Math.round(event.deltaY)); window.go.main.App.HandleScroll(Math.round(event.deltaY));
} }

View File

@ -9,6 +9,10 @@ export function Disconnect():Promise<string>;
export function HandleScroll(arg1:number):Promise<void>; export function HandleScroll(arg1:number):Promise<void>;
export function RestoreNormalMode():Promise<void>;
export function SendCtrlAltDel():Promise<void>; export function SendCtrlAltDel():Promise<void>;
export function SetMiniMode():Promise<void>;
export function SetPosition(arg1:number):Promise<void>; export function SetPosition(arg1:number):Promise<void>;

View File

@ -18,10 +18,18 @@ export function HandleScroll(arg1) {
return window['go']['main']['App']['HandleScroll'](arg1); return window['go']['main']['App']['HandleScroll'](arg1);
} }
export function RestoreNormalMode() {
return window['go']['main']['App']['RestoreNormalMode']();
}
export function SendCtrlAltDel() { export function SendCtrlAltDel() {
return window['go']['main']['App']['SendCtrlAltDel'](); return window['go']['main']['App']['SendCtrlAltDel']();
} }
export function SetMiniMode() {
return window['go']['main']['App']['SetMiniMode']();
}
export function SetPosition(arg1) { export function SetPosition(arg1) {
return window['go']['main']['App']['SetPosition'](arg1); return window['go']['main']['App']['SetPosition'](arg1);
} }

View File

@ -27,4 +27,8 @@ type InputHandler interface {
GetScreenResolution() (int32, int32) GetScreenResolution() (int32, int32)
RequestFocus() RequestFocus()
SetCursorClip(clip bool) SetCursorClip(clip bool)
GetWindowPos() (int32, int32)
GetWindowSize() (int32, int32)
GetMonitorWorkArea() (x, y, w, h int32)
MoveWindow(x, y, w, h int32, topmost bool)
} }

View File

@ -29,12 +29,22 @@ var (
procSetForegroundWindow = user32.NewProc("SetForegroundWindow") procSetForegroundWindow = user32.NewProc("SetForegroundWindow")
procGetWindowRect = user32.NewProc("GetWindowRect") procGetWindowRect = user32.NewProc("GetWindowRect")
procClipCursor = user32.NewProc("ClipCursor") procClipCursor = user32.NewProc("ClipCursor")
procSetWindowPos = user32.NewProc("SetWindowPos")
procMonitorFromWindow = user32.NewProc("MonitorFromWindow")
procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW")
) )
type RECT struct { type RECT struct {
Left, Top, Right, Bottom int32 Left, Top, Right, Bottom int32
} }
type MONITORINFO struct {
CbSize uint32
RcMonitor RECT
RcWork RECT
DwFlags uint32
}
const ( const (
WH_KEYBOARD_LL = 13 WH_KEYBOARD_LL = 13
WH_MOUSE_LL = 14 WH_MOUSE_LL = 14
@ -195,3 +205,54 @@ func (h *windowsInputHandler) SetCursorClip(clip bool) {
procClipCursor.Call(0) procClipCursor.Call(0)
} }
} }
func (h *windowsInputHandler) GetWindowPos() (int32, int32) {
if h.hwnd != 0 {
var rect RECT
procGetWindowRect.Call(h.hwnd, uintptr(unsafe.Pointer(&rect)))
return rect.Left, rect.Top
}
return 0, 0
}
func (h *windowsInputHandler) GetWindowSize() (int32, int32) {
if h.hwnd != 0 {
var rect RECT
procGetWindowRect.Call(h.hwnd, uintptr(unsafe.Pointer(&rect)))
return rect.Right - rect.Left, rect.Bottom - rect.Top
}
return 0, 0
}
func (h *windowsInputHandler) GetMonitorWorkArea() (x, y, w, ht int32) {
if h.hwnd == 0 {
return 0, 0, 1920, 1080
}
monitor, _, _ := procMonitorFromWindow.Call(h.hwnd, 2) // MONITOR_DEFAULTTONEAREST
if monitor == 0 {
return 0, 0, 1920, 1080
}
var mi MONITORINFO
mi.CbSize = uint32(unsafe.Sizeof(mi))
procGetMonitorInfoW.Call(monitor, uintptr(unsafe.Pointer(&mi)))
return mi.RcWork.Left, mi.RcWork.Top,
mi.RcWork.Right - mi.RcWork.Left,
mi.RcWork.Bottom - mi.RcWork.Top
}
func (h *windowsInputHandler) MoveWindow(x, y, w, ht int32, topmost bool) {
if h.hwnd == 0 {
return
}
// HWND_TOPMOST = -1, HWND_NOTOPMOST = -2
zorder := ^uintptr(1) // HWND_NOTOPMOST
if topmost {
zorder = ^uintptr(0) // HWND_TOPMOST
}
const SWP_NOACTIVATE = 0x0010
procSetWindowPos.Call(
h.hwnd, zorder,
uintptr(x), uintptr(y), uintptr(w), uintptr(ht),
SWP_NOACTIVATE,
)
}

View File

@ -40,10 +40,16 @@ const (
LayoutUsIntl LayoutUsIntl
) )
type WindowManager interface {
SetMiniMode()
RestoreNormalMode()
}
type Engine struct { type Engine struct {
mu sync.Mutex mu sync.Mutex
transport transport.Transport transport transport.Transport
inputHandler input.InputHandler inputHandler input.InputHandler
winManager WindowManager
clientMode bool clientMode bool
clientPos ClientPos clientPos ClientPos
@ -76,35 +82,40 @@ func NewEngine(t transport.Transport, h input.InputHandler) *Engine {
} }
} }
func (e *Engine) SetWindowManager(wm WindowManager) {
e.winManager = wm
}
func (e *Engine) Transport() transport.Transport { func (e *Engine) Transport() transport.Transport {
return e.transport return e.transport
} }
func (e *Engine) GetScreenResolution() (int32, int32) {
return e.inputHandler.GetScreenResolution()
}
func (e *Engine) GetWindowSize() (int32, int32) {
return e.inputHandler.GetWindowSize()
}
func (e *Engine) GetWindowPos() (int32, int32) {
return e.inputHandler.GetWindowPos()
}
func (e *Engine) GetMonitorWorkArea() (x, y, w, h int32) {
return e.inputHandler.GetMonitorWorkArea()
}
func (e *Engine) MoveWindow(x, y, w, h int32, topmost bool) {
e.inputHandler.MoveWindow(x, y, w, h, topmost)
}
func (e *Engine) Start(ctx context.Context) error { func (e *Engine) Start(ctx context.Context) error {
w, h := e.inputHandler.GetScreenResolution() w, h := e.inputHandler.GetScreenResolution()
LogDebug(fmt.Sprintf("Engine Iniciada. Tela: %dx%d. Pos: %v", w, h, e.clientPos)) LogDebug(fmt.Sprintf("Engine Iniciada. Tela: %dx%d. Pos: %v", w, h, e.clientPos))
return e.inputHandler.Install(ctx, e.onMouse, e.onKey) return e.inputHandler.Install(ctx, e.onMouse, e.onKey)
} }
func (e *Engine) processarScroll(msg uint32, data uint32) {
e.scrollActive = true
e.scrollTimer = time.Now()
deltaRaw := int16(data >> 16)
// LOG TOTAL PARA DESCOBRIR O QUE O TOUCHPAD MANDA
go LogDebug(fmt.Sprintf("SCROLL RAW: msg=0x%X data=0x%X delta=%d", msg, data, deltaRaw))
if deltaRaw > 0 {
e.transport.Send([]byte{'P', 0xDA}) // Up
time.Sleep(5 * time.Millisecond)
e.transport.Send([]byte{'U', 0xDA})
} else if deltaRaw < 0 {
e.transport.Send([]byte{'P', 0xD9}) // Down
time.Sleep(5 * time.Millisecond)
e.transport.Send([]byte{'U', 0xD9})
}
}
func (e *Engine) onMouse(ev input.MouseEvent) bool { func (e *Engine) onMouse(ev input.MouseEvent) bool {
e.mu.Lock() e.mu.Lock()
@ -131,50 +142,48 @@ func (e *Engine) onMouse(ev input.MouseEvent) bool {
switch ev.Message { switch ev.Message {
case 0x020A, 0x020E: // Roda Vertical ou Horizontal case 0x020A, 0x020E: // Roda Vertical ou Horizontal
e.processarScroll(ev.Message, ev.Data) // Não bloquear: evento passa para o webview → JS wheel → HandleManualScroll
return true // (WH_MOUSE_LL não recebe scroll de touchpad precision; mini janela captura via JS)
e.scrollActive = true
e.scrollTimer = time.Now()
return false
case 0x0200: // Move case 0x0200: // Move
if e.isWarping { e.isWarping = false; return true } if e.isWarping { e.isWarping = false; return true }
wx, wy := e.inputHandler.GetWindowPos()
if e.scrollActive { if e.scrollActive {
// Se estiver scrollando, ignoramos movimentos por um tempo curto (touchpads) // Durante scroll: decai após 250ms, mas sempre parka cursor na janela
if time.Since(e.scrollTimer) > 250*time.Millisecond { if time.Since(e.scrollTimer) > 250*time.Millisecond {
e.scrollActive = false e.scrollActive = false
e.virtualX, e.virtualY = 0, 0 e.virtualX, e.virtualY = 0, 0
} }
e.lastRawPos = ev.Point } else {
return true dx, dy := ev.Point.X-e.lastRawPos.X, ev.Point.Y-e.lastRawPos.Y
} e.virtualX += dx
e.virtualY += dy
e.pendingDX += dx
e.pendingDY += dy
dx, dy := ev.Point.X - e.lastRawPos.X, ev.Point.Y - e.lastRawPos.Y if e.shouldReturnToHost() {
e.virtualX += dx if time.Since(e.lastModeChange) > 800*time.Millisecond {
e.virtualY += dy e.exitClientMode()
e.pendingDX += dx return true
e.pendingDY += dy }
e.virtualX, e.virtualY = 0, 0
if e.shouldReturnToHost() { } else if time.Since(e.mouseThrottle) >= 40*time.Millisecond {
if time.Since(e.lastModeChange) > 800*time.Millisecond { e.mouseThrottle = time.Now()
e.exitClientMode() sdx, sdy := int8(clamp(int(e.pendingDX), -127, 127)), int8(clamp(int(e.pendingDY), -127, 127))
return true e.pendingDX, e.pendingDY = 0, 0
e.transport.SendLossy([]byte{'M', byte(sdx), byte(sdy)})
} }
e.virtualX, e.virtualY = 0, 0
return true
} }
// Park no centro para manter o mouse sobre a janela do App // Sempre parka cursor no centro da mini janela (garante alvo para scroll)
// permitindo a captura do scroll.
w, h := e.inputHandler.GetScreenResolution()
e.isWarping = true e.isWarping = true
e.inputHandler.SetCursorPos(w/2, h/2) e.inputHandler.SetCursorPos(wx+150, wy+50)
e.lastRawPos = input.Point{X: w / 2, Y: h / 2} e.lastRawPos = input.Point{X: wx + 150, Y: wy + 50}
if time.Since(e.mouseThrottle) >= 40*time.Millisecond {
e.mouseThrottle = time.Now()
sdx, sdy := int8(clamp(int(e.pendingDX), -127, 127)), int8(clamp(int(e.pendingDY), -127, 127))
e.pendingDX, e.pendingDY = 0, 0
e.transport.SendLossy([]byte{'M', byte(sdx), byte(sdy)})
}
return true return true
case 0x0201: e.transport.Send([]byte{'D', 'L'}); return true case 0x0201: e.transport.Send([]byte{'D', 'L'}); return true
@ -218,17 +227,31 @@ func (e *Engine) enterClientMode(p input.Point) {
e.wheelAccum = 0 e.wheelAccum = 0
e.mouseThrottle = time.Now() e.mouseThrottle = time.Now()
// Foco para receber scroll da interface
e.inputHandler.RequestFocus() e.inputHandler.RequestFocus()
w, h := e.inputHandler.GetScreenResolution() if e.winManager != nil {
e.isWarping = true e.winManager.SetMiniMode()
// Park no centro como no início }
e.inputHandler.SetCursorPos(w/2, h/2)
e.lastRawPos = input.Point{X: w / 2, Y: h / 2} // Wails move janela de forma assíncrona; aguardar antes de parkear
// ShowCursor(true) DEVE ser após resize/move: Wails/webview pode chamar ShowCursor(false) internamente
// Sem esconder cursor para teste de scroll puro go func() {
e.inputHandler.ShowCursor(true) time.Sleep(250 * time.Millisecond)
e.mu.Lock()
defer e.mu.Unlock()
if !e.clientMode {
return
}
// Força cursor visível (combate ShowCursor(false) interno do Wails/webview)
for i := 0; i < 10; i++ {
e.inputHandler.ShowCursor(true)
}
wx, wy := e.inputHandler.GetWindowPos()
e.isWarping = true
e.inputHandler.SetCursorPos(wx+150, wy+50)
e.lastRawPos = input.Point{X: wx + 150, Y: wy + 50}
LogDebug(fmt.Sprintf("Cursor parkado em mini: (%d,%d)", wx+150, wy+50))
}()
// 'A' (ReleaseAll) limpa estados presos no firmware, 'O' sinaliza LED Magenta // 'A' (ReleaseAll) limpa estados presos no firmware, 'O' sinaliza LED Magenta
e.transport.Send([]byte{'A'}) e.transport.Send([]byte{'A'})
@ -239,6 +262,12 @@ func (e *Engine) exitClientMode() {
LogDebug("Saindo Modo Cliente.") LogDebug("Saindo Modo Cliente.")
e.clientMode = false e.clientMode = false
e.lastModeChange = time.Now() e.lastModeChange = time.Now()
// Restaura a janela (Normal Mode)
if e.winManager != nil {
e.winManager.RestoreNormalMode()
}
e.inputHandler.ShowCursor(true) e.inputHandler.ShowCursor(true)
w, h := e.inputHandler.GetScreenResolution() w, h := e.inputHandler.GetScreenResolution()
var ret input.Point var ret input.Point

11
main.go
View File

@ -6,6 +6,7 @@ import (
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
) )
//go:embed all:frontend/dist //go:embed all:frontend/dist
@ -20,6 +21,11 @@ func main() {
Title: "KVMote", Title: "KVMote",
Width: 400, Width: 400,
Height: 550, Height: 550,
DisableResize: false,
Fullscreen: false,
Frameless: false,
MinWidth: 200,
MinHeight: 50,
AssetServer: &assetserver.Options{ AssetServer: &assetserver.Options{
Assets: assets, Assets: assets,
}, },
@ -28,6 +34,11 @@ func main() {
Bind: []interface{}{ Bind: []interface{}{
app, app,
}, },
Windows: &windows.Options{
DisableWindowIcon: false,
WebviewIsTransparent: false,
WindowIsTranslucent: false,
},
}) })
if err != nil { if err != nil {