Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Implement global shortcuts #393

Merged
merged 10 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,27 @@ var m = d.NewMenu([]*astilectron.MenuItemOptions{
m.Create()
```

## Global Shortcuts
asticode marked this conversation as resolved.
Show resolved Hide resolved

Registering a global shortcut.

```go
// Register a new global shortcut
isRegistered, _ := astilectron.GlobalShortcutRegister("CmdOrCtrl+x", func() {
fmt.Println("CmdOrCtrl+x is pressed")
})
fmt.Println("CmdOrCtrl+x is registered:", isRegistered) // true

// Check if a global shortcut is registered
isRegistered, _ = astilectron.GlobalShortcutIsRegistered("Shift+Y") // false

// Unregister a global shortcut
astilectron.GlobalShortcutUnregister("CmdOrCtrl+x")

// Unregister all global shortcuts
astilectron.GlobalShortcutUnregisterAll()
```

## Dialogs

Add the following line at the top of your javascript file :
Expand Down
4 changes: 4 additions & 0 deletions astilectron.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ func (a *Astilectron) Start() (err error) {
} else {
synchronousFunc(a.worker.Context(), a, nil, "app.event.ready")
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could your remove this empty line ?

// Initialize global Shortcuts
InitGlobalShortcuts(a.worker.Context(), a.dispatcher, a.identifier, a.writer)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making the Global Shortcut methods/variables global to the package, could you:

  • create a GlobalShortcuts struct that will contain the *object and registered callbacks
  • add a globalShortcuts *GlobalShortcuts variable to the Astilectron struct
  • add a newGlobalShortcuts() method that creates a *GlobalShortcuts (and does the other instantiating stuff that your InitGlobalShortcuts() does) and call this method after creating the dock
  • add a GlobalShortcuts() method to the Astilectron struct that will return a.globalShortcuts
  • add Register(), IsRegistered(), Unregister() and UnregisterAll() methods to the GlobalShortcuts struct


return nil
}

Expand Down
1 change: 1 addition & 0 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Event struct {
Displays *EventDisplays `json:"displays,omitempty"`
Enable *bool `json:"enable,omitempty"`
FilePath string `json:"filePath,omitempty"`
GlobalShortcut *GlobalShortcut `json:"globalShortcut,omitempty"`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using GlobalShortcut directly here, could you:

  • create a specific EventGlobalShortcuts struct containing json fields only?
  • remove json fields from GlobalShortcuts
  • make sure you add an s at the end of all global shortcut occurences

ID *int `json:"id,omitempty"`
Image string `json:"image,omitempty"`
Index *int `json:"index,omitempty"`
Expand Down
132 changes: 132 additions & 0 deletions global_shortcut.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package astilectron
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you rename the file to global_shortcuts.go?


import (
"context"
)

const (
EventNameGlobalShortcutCmdRegister = "global.shortcut.cmd.register"
EventNameGlobalShortcutCmdIsRegistered = "global.shortcut.cmd.is.register"
EventNameGlobalShortcutCmdUnregister = "global.shortcut.cmd.unregister"
EventNameGlobalShortcutCmdUnregisterAll = "global.shortcut.cmd.unregister.all"
EventNameGlobalShortcutEventProcessFinished = "global.shortcut.event.process.finished" // Register or Unregister process is finished
EventNameGlobalShortcutEventTriggered = "global.shortcut.event.triggered"
)

// GlobalShortcut represents a global shortcut
type GlobalShortcut struct {
*object
Accelerator string `json:"accelerator,omitempty"` // Accelerator of the global globalShortcuts
IsRegistered bool `json:"isRegistered,omitempty"` // Whether the global shortcut is registered
}
type callback func()

var gss = make(map[string]*callback) // Store all registered Global Shortcuts
var obj *object

// InitGlobalShortcuts initializes the globalShortcuts
func InitGlobalShortcuts(ctx context.Context, d *dispatcher, i *identifier, w *writer) {
obj = newObject(ctx, d, i, w, i.new())
obj.On(EventNameGlobalShortcutEventTriggered, func(e Event) (deleteListener bool) { // Register the listener for the triggered event
globalShortcutHandler(e.GlobalShortcut.Accelerator)
return
})
}

// GlobalShortcutRegister Register global shortcuts
func GlobalShortcutRegister(accelerator string, callback callback) (isRegistered bool, err error) {

if err = obj.ctx.Err(); err != nil {
return
}

var gs = GlobalShortcut{Accelerator: accelerator, object: obj}

// Send an event to astilectron to register the global shortcut
var event = Event{Name: EventNameGlobalShortcutCmdRegister, TargetID: gs.id, GlobalShortcut: &gs}
result, err := synchronousEvent(gs.ctx, gs, gs.w, event, EventNameGlobalShortcutEventProcessFinished)

if err != nil {
return
}

// If registered successfully, add the callback to the map
if result.GlobalShortcut.IsRegistered {
gss[accelerator] = &callback
}

isRegistered = result.GlobalShortcut.IsRegistered
return
}

// GlobalShortcutIsRegistered Check if a global shortcut is registered
func GlobalShortcutIsRegistered(accelerator string) (isRegistered bool, err error) {

if err = obj.ctx.Err(); err != nil {
return
}

var gs = GlobalShortcut{Accelerator: accelerator, object: obj}

// Send an event to astilectron to check if global shortcut is registered
var event = Event{Name: EventNameGlobalShortcutCmdIsRegistered, TargetID: gs.id, GlobalShortcut: &gs}
result, err := synchronousEvent(gs.ctx, gs, gs.w, event, EventNameGlobalShortcutEventProcessFinished)

if err != nil {
return
}

isRegistered = result.GlobalShortcut.IsRegistered
return
}

// GlobalShortcutUnregister Unregister a global shortcut
func GlobalShortcutUnregister(accelerator string) (err error) {

if err = obj.ctx.Err(); err != nil {
return
}

var gs = GlobalShortcut{Accelerator: accelerator, object: obj}

// Send an event to astilectron to unregister the global shortcut
var event = Event{Name: EventNameGlobalShortcutCmdUnregister, TargetID: gs.id, GlobalShortcut: &gs}
_, err = synchronousEvent(gs.ctx, gs, gs.w, event, EventNameGlobalShortcutEventProcessFinished)

if err != nil {
return
}

// No need to find the callback from the map and delete it
// because that event will no longer be triggerred
// If the same global shortcut is registered again, the original callback will be replaced with the new one

return
}

// GlobalShortcutUnregisterAll Unregister all global shortcuts
func GlobalShortcutUnregisterAll() (err error) {

if err = obj.ctx.Err(); err != nil {
return
}

// Send an event to astilectron to unregister all global shortcuts
var event = Event{Name: EventNameGlobalShortcutCmdUnregisterAll, TargetID: obj.id}
_, err = synchronousEvent(obj.ctx, obj, obj.w, event, EventNameGlobalShortcutEventProcessFinished)

if err != nil {
return
}

gss = make(map[string]*callback) // Clear the map

return
}

// globalShortcutHandler Handle the GlobalShortcut event triggered from astilectron
func globalShortcutHandler(accelerator string) {
if callback, ok := gss[accelerator]; ok {
(*callback)()
}
}
42 changes: 42 additions & 0 deletions global_shortcut_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package astilectron

import (
"context"
"fmt"
"testing"
)

func TestGlobalShortcut_Actions(t *testing.T) {
var d = newDispatcher()
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt, &logger{})

InitGlobalShortcuts(context.Background(), d, i, w)

// Register
testObjectAction(t, func() error {
_, e := GlobalShortcutRegister("Ctrl+X", func() {})
return e
}, obj, wrt, fmt.Sprintf(`{"name":"%s","targetID":"%s","globalShortcut":{"accelerator":"Ctrl+X"}}%s`, EventNameGlobalShortcutCmdRegister, obj.id, "\n"),
EventNameGlobalShortcutEventProcessFinished, &Event{GlobalShortcut: &GlobalShortcut{Accelerator: "Ctrl+X", IsRegistered: true}})

// IsRegistered
testObjectAction(t, func() error {
_, e := GlobalShortcutIsRegistered("Ctrl+Y")
return e
}, obj, wrt, fmt.Sprintf(`{"name":"%s","targetID":"%s","globalShortcut":{"accelerator":"Ctrl+Y"}}%s`, EventNameGlobalShortcutCmdIsRegistered, obj.id, "\n"),
EventNameGlobalShortcutEventProcessFinished, &Event{GlobalShortcut: &GlobalShortcut{Accelerator: "Ctrl+Y", IsRegistered: false}})

// Unregister
testObjectAction(t, func() error {
return GlobalShortcutUnregister("Ctrl+Z")
}, obj, wrt, fmt.Sprintf(`{"name":"%s","targetID":"%s","globalShortcut":{"accelerator":"Ctrl+Z"}}%s`, EventNameGlobalShortcutCmdUnregister, obj.id, "\n"),
EventNameGlobalShortcutEventProcessFinished, nil)

// UnregisterAll
testObjectAction(t, func() error {
return GlobalShortcutUnregisterAll()
}, obj, wrt, fmt.Sprintf(`{"name":"%s","targetID":"%s"}%s`, EventNameGlobalShortcutCmdUnregisterAll, obj.id, "\n"),
EventNameGlobalShortcutEventProcessFinished, nil)
}