mirror of
https://github.com/kataras/iris.git
synced 2025-01-24 03:01:03 +01:00
273 lines
5.6 KiB
Go
273 lines
5.6 KiB
Go
|
// +build !linux
|
||
|
|
||
|
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package gui
|
||
|
|
||
|
import (
|
||
|
"sync/atomic"
|
||
|
|
||
|
"github.com/getlantern/systray"
|
||
|
|
||
|
"github.com/kataras/iris/core/errors"
|
||
|
"github.com/kataras/iris/core/gui/icon"
|
||
|
)
|
||
|
|
||
|
var trayRunning int32 // != 0 means a system tray is running
|
||
|
|
||
|
type TrayItem struct {
|
||
|
*systray.MenuItem
|
||
|
title string
|
||
|
checked bool
|
||
|
disabled bool
|
||
|
clickEvents []TrayItemClickEvent
|
||
|
}
|
||
|
|
||
|
type TrayItemClickEvent func(*TrayItem)
|
||
|
|
||
|
func newTrayItem(title string) *TrayItem {
|
||
|
// MenuItem's fields are not exported,
|
||
|
// I could modify the source code to completes my needs
|
||
|
// but I will not because the sys tram is only one
|
||
|
// and its items are not shown until .Show()
|
||
|
// So I will use the AddMenuItem because it adds them with a specific order
|
||
|
// and I want to control the order, so this TrayItem
|
||
|
// will be a wrapper of the *systray.MenuItem and it will be shown as independed when tray host.Show called.
|
||
|
return &TrayItem{title: title}
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Check() {
|
||
|
i.checked = true
|
||
|
if i.MenuItem != nil {
|
||
|
i.MenuItem.Check()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Uncheck() {
|
||
|
i.checked = false
|
||
|
if i.MenuItem != nil {
|
||
|
i.MenuItem.Uncheck()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Checked() bool {
|
||
|
return i.checked
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Disable() {
|
||
|
i.disabled = true
|
||
|
if i.MenuItem != nil {
|
||
|
i.MenuItem.Disable()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Enable() {
|
||
|
i.disabled = false
|
||
|
if i.MenuItem != nil {
|
||
|
i.MenuItem.Enable()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) Disabled() bool {
|
||
|
return i.disabled
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) SetTitle(title string) *TrayItem {
|
||
|
i.title = title
|
||
|
if i.MenuItem != nil {
|
||
|
i.MenuItem.SetTitle(title)
|
||
|
i.MenuItem.SetTooltip(title)
|
||
|
}
|
||
|
return i
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) SetToolTip(tooltip string) *TrayItem {
|
||
|
return i.SetTitle(tooltip)
|
||
|
}
|
||
|
|
||
|
// always checked by-default
|
||
|
func newTrayItemCheckBox(checkedTitle string, onCheck TrayItemClickEvent,
|
||
|
unCheckedTitle string, onUnCheck TrayItemClickEvent) *TrayItem {
|
||
|
|
||
|
item := newTrayItem(checkedTitle)
|
||
|
item.Check()
|
||
|
|
||
|
item.OnClick(func(i *TrayItem) {
|
||
|
if item.Checked() {
|
||
|
item.SetTitle(unCheckedTitle)
|
||
|
item.Uncheck()
|
||
|
onUnCheck(i)
|
||
|
} else {
|
||
|
item.SetTitle(checkedTitle)
|
||
|
item.Check()
|
||
|
onCheck(i)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
sepLine = "────────────────"
|
||
|
)
|
||
|
|
||
|
func newTrayItemSeparator() *TrayItem {
|
||
|
item := newTrayItem(sepLine)
|
||
|
item.Disable()
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) show() {
|
||
|
activeItem := systray.AddMenuItem(i.title, i.title)
|
||
|
if i.disabled {
|
||
|
activeItem.Disable()
|
||
|
}
|
||
|
|
||
|
if i.checked {
|
||
|
activeItem.Check()
|
||
|
}
|
||
|
|
||
|
i.MenuItem = activeItem
|
||
|
go func() {
|
||
|
for {
|
||
|
<-activeItem.ClickedCh
|
||
|
i.fireClick()
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) OnClick(callback TrayItemClickEvent) *TrayItem {
|
||
|
i.clickEvents = append(i.clickEvents, callback)
|
||
|
return i
|
||
|
}
|
||
|
|
||
|
func (i *TrayItem) fireClick() {
|
||
|
for _, cb := range i.clickEvents {
|
||
|
cb(i)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type TrayHost struct {
|
||
|
items []*TrayItem // useless
|
||
|
version string
|
||
|
shutdownServerCb func()
|
||
|
startServerCb func()
|
||
|
hideCb func()
|
||
|
}
|
||
|
|
||
|
var Tray = defaultTrayHost()
|
||
|
|
||
|
func defaultTrayHost() *TrayHost {
|
||
|
t := new(TrayHost)
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) putItem(item *TrayItem) {
|
||
|
t.items = append(t.items, item)
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) PutItem(title string) *TrayItem {
|
||
|
item := newTrayItem(title)
|
||
|
t.putItem(item)
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) PutCheckBox(checkedTitle string, onCheck TrayItemClickEvent,
|
||
|
unCheckedTitle string, onUnCheck TrayItemClickEvent) *TrayItem {
|
||
|
item := newTrayItemCheckBox(checkedTitle, onCheck, unCheckedTitle, onUnCheck)
|
||
|
t.putItem(item)
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) PutSeparator() *TrayItem {
|
||
|
item := newTrayItemSeparator()
|
||
|
t.putItem(item)
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) SetVersion(v string) *TrayHost {
|
||
|
t.version = v
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) OnServerStatusChange(start func(), shutdown func()) *TrayHost {
|
||
|
t.startServerCb = start
|
||
|
t.shutdownServerCb = shutdown
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) OnHide(cb func()) *TrayHost {
|
||
|
t.hideCb = cb
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) Hide() {
|
||
|
atomic.StoreInt32(&trayRunning, -1)
|
||
|
systray.Quit()
|
||
|
if t.hideCb != nil {
|
||
|
t.hideCb()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) Show() error {
|
||
|
if running := atomic.LoadInt32(&trayRunning); running != 0 {
|
||
|
return errors.New("A system tray is already running, please close that first")
|
||
|
}
|
||
|
|
||
|
topItems := make([]*TrayItem, 0) // dynamic because we don't know if status btn will be shown
|
||
|
|
||
|
versionBtn := newTrayItem("Version " + t.version)
|
||
|
versionBtn.Disable()
|
||
|
|
||
|
topItems = append(topItems, versionBtn)
|
||
|
// if server status listeners have been registered, then show the online/offline button
|
||
|
// otherwise not.
|
||
|
if t.startServerCb != nil && t.shutdownServerCb != nil {
|
||
|
statusBtn := newTrayItemCheckBox("Online", t.startServerClicked, "Offline", t.shutdownServerClicked)
|
||
|
topItems = append(topItems, statusBtn)
|
||
|
}
|
||
|
topItems = append(topItems, newTrayItemSeparator())
|
||
|
|
||
|
t.items = append(topItems, t.items...)
|
||
|
t.PutItem("Hide").OnClick(t.hideClicked)
|
||
|
|
||
|
systray.Run(t.onTrayReady)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) onTrayReady() {
|
||
|
systray.SetIcon(icon.Data)
|
||
|
systray.SetTitle("Iris web server")
|
||
|
systray.SetTooltip("Iris")
|
||
|
|
||
|
for _, item := range t.items {
|
||
|
item.show()
|
||
|
}
|
||
|
|
||
|
atomic.StoreInt32(&trayRunning, 1)
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) DisableItem(index int) {
|
||
|
if len(t.items)-1 > index {
|
||
|
t.items[index].Disable()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) startServerClicked(item *TrayItem) {
|
||
|
if t.startServerCb != nil {
|
||
|
t.startServerCb()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) shutdownServerClicked(item *TrayItem) {
|
||
|
if t.shutdownServerCb != nil {
|
||
|
t.shutdownServerCb()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *TrayHost) hideClicked(item *TrayItem) {
|
||
|
t.Hide()
|
||
|
}
|