Skip to content

Commit

Permalink
Added dock
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Apr 1, 2018
1 parent 172da7d commit e5bb0a3
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 39 deletions.
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,50 @@ time.Sleep(time.Second)
t.SetImage(astilectron.PtrStr("/path/to/image-2.png"))
```

## Dock (MacOSX only)

```go
// Get the dock
var d = a.Dock()

// Hide and show the dock
d.Hide()
d.Show()

// Make the Dock bounce
id, _ := d.Bounce(astilectron.DockBounceTypeCritical)

// Cancel the bounce
d.CancelBounce(id)

// Update badge and icon
d.SetBadge("test")
d.SetIcon("/path/to/icon")

// New dock menu
var m = d.NewMenu([]*astilectron.MenuItemOptions{
{
Label: astilectron.PtrStr("Root 1"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Item 1")},
{Label: astilectron.PtrStr("Item 2")},
{Type: astilectron.MenuItemTypeSeparator},
{Label: astilectron.PtrStr("Item 3")},
},
},
{
Label: astilectron.PtrStr("Root 2"),
SubMenu: []*astilectron.MenuItemOptions{
{Label: astilectron.PtrStr("Item 1")},
{Label: astilectron.PtrStr("Item 2")},
},
},
})

// Create the menu
m.Create()
```

## Dialogs

### Error box
Expand Down Expand Up @@ -411,11 +455,12 @@ document.addEventListener('astilectron-ready', function() {
- [x] bundler
- [x] session
- [x] accelerators (shortcuts)
- [x] dock
- [ ] notifications (macosx)
- [ ] loader
- [ ] file methods (drag & drop, ...)
- [ ] clipboard methods
- [ ] power monitor events (suspend, resume, ...)
- [ ] notifications (macosx)
- [ ] desktop capturer (audio and video)
- [ ] window advanced options (add missing ones)
- [ ] window advanced methods (add missing ones)
Expand Down
33 changes: 21 additions & 12 deletions astilectron.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
// Versions
const (
DefaultAcceptTCPTimeout = 30 * time.Second
VersionAstilectron = "0.17.0"
VersionAstilectron = "0.18.0"
VersionElectron = "1.8.1"
)

Expand Down Expand Up @@ -51,6 +51,7 @@ type Astilectron struct {
closeOnce sync.Once
dispatcher *dispatcher
displayPool *displayPool
dock *Dock
executer Executer
identifier *identifier
listener net.Listener
Expand Down Expand Up @@ -139,7 +140,7 @@ func (a *Astilectron) SetExecuter(e Executer) *Astilectron {

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
a.dispatcher.addListener(mainTargetID, eventName, l)
a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
Expand Down Expand Up @@ -206,8 +207,8 @@ func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan boo
return
case <-t.C:
astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
return
}
}
Expand All @@ -221,17 +222,17 @@ func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
var err error
if conn, err = a.listener.Accept(); err != nil {
astilog.Errorf("%s while TCP accepting", err)
a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
return
}

// We only accept the first connection which should be Astilectron, close the next one and stop
// the app
if i > 0 {
astilog.Errorf("Too many TCP connections")
a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
conn.Close()
return
}
Expand Down Expand Up @@ -276,6 +277,9 @@ func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
if e.Displays != nil {
a.displayPool.update(e.Displays)
}

// Create dock
a.dock = newDock(a.canceller, a.dispatcher, a.identifier, a.writer)
return
}

Expand All @@ -287,12 +291,12 @@ func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
// Check the canceller to check whether it was a crash
if !a.canceller.Cancelled() {
astilog.Debug("App has crashed")
a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
} else {
astilog.Debug("App has closed")
a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
}
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
Expand Down Expand Up @@ -357,14 +361,19 @@ func (a *Astilectron) Displays() []*Display {
return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
return newMenu(nil, mainTargetID, i, a.canceller, a.dispatcher, a.identifier, a.writer)
return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
Expand Down
2 changes: 1 addition & 1 deletion astilectron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func TestAstilectron_NewMenu(t *testing.T) {
a, err := New(Options{})
assert.NoError(t, err)
m := a.NewMenu([]*MenuItemOptions{})
assert.Equal(t, mainTargetID, m.rootID)
assert.Equal(t, targetIDApp, m.rootID)
}

func TestAstilectron_Actions(t *testing.T) {
Expand Down
111 changes: 111 additions & 0 deletions dock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package astilectron

import "github.com/asticode/go-astitools/context"

// Dock event names
const (
eventNameDockCmdBounce = "dock.cmd.bounce"
eventNameDockCmdBounceDownloads = "dock.cmd.bounce.downloads"
eventNameDockCmdCancelBounce = "dock.cmd.cancel.bounce"
eventNameDockCmdHide = "dock.cmd.hide"
eventNameDockCmdSetBadge = "dock.cmd.set.badge"
eventNameDockCmdSetIcon = "dock.cmd.set.icon"
eventNameDockCmdShow = "dock.cmd.show"
eventNameDockEventBadgeSet = "dock.event.badge.set"
eventNameDockEventBouncing = "dock.event.bouncing"
eventNameDockEventBouncingCancelled = "dock.event.bouncing.cancelled"
eventNameDockEventDownloadsBouncing = "dock.event.download.bouncing"
eventNameDockEventHidden = "dock.event.hidden"
eventNameDockEventIconSet = "dock.event.icon.set"
eventNameDockEventShown = "dock.event.shown"
)

// Dock bounce types
const (
DockBounceTypeCritical = "critical"
DockBounceTypeInformational = "informational"
)

// Dock represents a dock
// https://github.com/electron/electron/blob/v1.8.1/docs/api/app.md#appdockbouncetype-macos
type Dock struct {
*object
}

func newDock(c *asticontext.Canceller, d *dispatcher, i *identifier, wrt *writer) *Dock {
return &Dock{object: newObject(nil, c, d, i, wrt, targetIDDock)}
}

// Bounce bounces the dock
func (d *Dock) Bounce(bounceType string) (id int, err error) {
if err = d.isActionable(); err != nil {
return
}
var e Event
if e, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdBounce, TargetID: d.id, BounceType: bounceType}, eventNameDockEventBouncing); err != nil {
return
}
if e.ID != nil {
id = *e.ID
}
return
}

// BounceDownloads bounces the downloads part of the dock
func (d *Dock) BounceDownloads(filePath string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdBounceDownloads, TargetID: d.id, FilePath: filePath}, eventNameDockEventDownloadsBouncing)
return
}

// CancelBounce cancels the dock bounce
func (d *Dock) CancelBounce(id int) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdCancelBounce, TargetID: d.id, ID: PtrInt(id)}, eventNameDockEventBouncingCancelled)
return
}

// Hide hides the dock
func (d *Dock) Hide() (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdHide, TargetID: d.id}, eventNameDockEventHidden)
return
}

// NewMenu creates a new dock menu
func (d *Dock) NewMenu(i []*MenuItemOptions) *Menu {
return newMenu(d.ctx, d.id, i, d.c, d.d, d.i, d.w)
}

// SetBadge sets the badge of the dock
func (d *Dock) SetBadge(badge string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdSetBadge, TargetID: d.id, Badge: badge}, eventNameDockEventBadgeSet)
return
}

// SetIcon sets the icon of the dock
func (d *Dock) SetIcon(image string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdSetIcon, TargetID: d.id, Image: image}, eventNameDockEventIconSet)
return
}

// Show shows the dock
func (d *Dock) Show() (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdShow, TargetID: d.id}, eventNameDockEventShown)
return
}
41 changes: 41 additions & 0 deletions dock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package astilectron

import (
"testing"

"github.com/asticode/go-astitools/context"
"github.com/stretchr/testify/assert"
)

func TestDock_Actions(t *testing.T) {
// Init
var c = asticontext.NewCanceller()
var d = newDispatcher()
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt)
var dck = newDock(c, d, i, w)

// Actions
testObjectAction(t, func() error {
_, err := dck.Bounce(DockBounceTypeCritical)
return err
}, dck.object, wrt, "{\"name\":\""+eventNameDockCmdBounce+"\",\"targetID\":\""+dck.id+"\",\"bounceType\":\"critical\"}\n", eventNameDockEventBouncing)
testObjectAction(t, func() error { return dck.BounceDownloads("/path/to/file") }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdBounceDownloads+"\",\"targetID\":\""+dck.id+"\",\"filePath\":\"/path/to/file\"}\n", eventNameDockEventDownloadsBouncing)
testObjectAction(t, func() error { return dck.CancelBounce(1) }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdCancelBounce+"\",\"targetID\":\""+dck.id+"\",\"id\":1}\n", eventNameDockEventBouncingCancelled)
testObjectAction(t, func() error { return dck.Hide() }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdHide+"\",\"targetID\":\""+dck.id+"\"}\n", eventNameDockEventHidden)
testObjectAction(t, func() error { return dck.SetBadge("badge") }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdSetBadge+"\",\"targetID\":\""+dck.id+"\",\"badge\":\"badge\"}\n", eventNameDockEventBadgeSet)
testObjectAction(t, func() error { return dck.SetIcon("/path/to/icon") }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdSetIcon+"\",\"targetID\":\""+dck.id+"\",\"image\":\"/path/to/icon\"}\n", eventNameDockEventIconSet)
testObjectAction(t, func() error { return dck.Show() }, dck.object, wrt, "{\"name\":\""+eventNameDockCmdShow+"\",\"targetID\":\""+dck.id+"\"}\n", eventNameDockEventShown)
}

func TestDock_NewMenu(t *testing.T) {
var c = asticontext.NewCanceller()
var d = newDispatcher()
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt)
var dck = newDock(c, d, i, w)
m := dck.NewMenu([]*MenuItemOptions{})
assert.Equal(t, dck.id, m.rootID)
}
9 changes: 7 additions & 2 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"errors"
)

// Misc constants
// Target IDs
const (
mainTargetID = "main"
targetIDApp = "app"
targetIDDock = "dock"
)

// Event represents an event
Expand All @@ -19,8 +20,12 @@ type Event struct {
// This is a list of all possible payloads.
// A choice was made not to use interfaces since it's a pain in the ass asserting each an every payload afterwards
// We use pointers so that omitempty works
Badge string `json:"badge,omitempty"`
BounceType string `json:"bounceType,omitempty"`
CallbackID string `json:"callbackId,omitempty"`
Displays *EventDisplays `json:"displays,omitempty"`
FilePath string `json:"filePath,omitempty"`
ID *int `json:"id,omitempty"`
Image string `json:"image,omitempty"`
Menu *EventMenu `json:"menu,omitempty"`
MenuItem *EventMenuItem `json:"menuItem,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion menu_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type MenuItemOptions struct {
func newMenuItem(parentCtx context.Context, rootID string, o *MenuItemOptions, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer) (m *MenuItem) {
m = &MenuItem{
o: o,
object: newObject(parentCtx, c, d, i, w),
object: newObject(parentCtx, c, d, i, w, i.new()),
rootID: rootID,
}
if o.OnClick != nil {
Expand Down
6 changes: 3 additions & 3 deletions menu_item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (

func TestMenuItem_ToEvent(t *testing.T) {
var o = &MenuItemOptions{Label: PtrStr("1"), SubMenu: []*MenuItemOptions{{Label: PtrStr("2")}, {Label: PtrStr("3")}}}
var mi = newMenuItem(context.Background(), "main", o, nil, nil, newIdentifier(), nil)
var mi = newMenuItem(context.Background(), targetIDApp, o, nil, nil, newIdentifier(), nil)
e := mi.toEvent()
assert.Equal(t, &EventMenuItem{ID: "1", RootID: "main", Options: o, SubMenu: &EventSubMenu{ID: "2", Items: []*EventMenuItem{{ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: "main"}, {ID: "4", Options: &MenuItemOptions{Label: PtrStr("3")}, RootID: "main"}}, RootID: "main"}}, e)
assert.Equal(t, &EventMenuItem{ID: "1", RootID: targetIDApp, Options: o, SubMenu: &EventSubMenu{ID: "2", Items: []*EventMenuItem{{ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: targetIDApp}, {ID: "4", Options: &MenuItemOptions{Label: PtrStr("3")}, RootID: targetIDApp}}, RootID: targetIDApp}}, e)
assert.Len(t, mi.SubMenu().items, 2)
}

Expand All @@ -23,7 +23,7 @@ func TestMenuItem_Actions(t *testing.T) {
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt)
var mi = newMenuItem(context.Background(), "main", &MenuItemOptions{Label: PtrStr("label")}, c, d, i, w)
var mi = newMenuItem(context.Background(), targetIDApp, &MenuItemOptions{Label: PtrStr("label")}, c, d, i, w)

// Actions
testObjectAction(t, func() error { return mi.SetChecked(true) }, mi.object, wrt, "{\"name\":\""+EventNameMenuItemCmdSetChecked+"\",\"targetID\":\""+mi.id+"\",\"menuItemOptions\":{\"checked\":true}}\n", EventNameMenuItemEventCheckedSet)
Expand Down
Loading

0 comments on commit e5bb0a3

Please sign in to comment.