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

Implement global shortcuts #393

merged 10 commits into from
Dec 21, 2023

Conversation

benebsiny
Copy link
Contributor

@benebsiny benebsiny commented Jun 18, 2023

Solved #392

This PR implements globalShortcut like electron.

4 methods are implemented: register, isRegistered, unregister, unregisterAll.

I was going to implement these methods as static methods of GlobalShortcut struct. Unfortunately, Go doesn't support static method. so an alternative way is implemented. See follow examples:

// Register
astilectron.GlobalShortcutRegister("shift+1", func() {
    fmt.Println("shift+1")
})

// Is registered
astilectron.GlobalShortcutIsRegistered("shift+1")  // true

// Unregister
astilectron.GlobalShortcutUnregister("shift+1")

// Unregister all
astilectron.GlobalShortcutUnregisterAll()

TODO

  • Register
  • Is Registered
  • Unregister
  • Unregister all
  • Add test case
  • Add doc

The relevant event will no longer be triggered after the shortcut is unregistered. If the same key is registered again, the original callback will be replaced with new one.
Therefore, we don't need to find the registered key from the map. Besides, we don't need to parse the accelerator, too.

An error will be returned if the method failed.
1. Add test case
2. Add doc to README.md
3. Handle `ctx.Err` in functions
@benebsiny benebsiny marked this pull request as ready for review June 20, 2023 05:30
Copy link
Owner

@asticode asticode left a comment

Choose a reason for hiding this comment

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

Thanks for the PR.

Before diving into the details, let's reorganize your changes a bit 👍

event.go Outdated
@@ -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

astilectron.go Outdated
@@ -187,6 +187,10 @@ func (a *Astilectron) Start() (err error) {
} else {
synchronousFunc(a.worker.Context(), a, nil, "app.event.ready")
}

// 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

@benebsiny
Copy link
Contributor Author

Thanks for your suggestion! I've followed your instructions to make some changes, have a look at it.

Copy link
Owner

@asticode asticode left a comment

Choose a reason for hiding this comment

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

I've not finished reviewing all the PR but I'll continue once the requested changes will be made 👍

@@ -0,0 +1,123 @@
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.shortcuts.event.registered"
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 make sure the events here are the same as in Javascript?

EventNameGlobalShortcutEventTriggered = "global.shortcuts.event.triggered"
)

type callback func()
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 this type to globalShortcutsCallback?

// GlobalShortcuts represents a global shortcut
type GlobalShortcuts struct {
*object
callbacks map[string]*callback // Store all registered Global Shortcuts
Copy link
Owner

Choose a reason for hiding this comment

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

You will need to add a *sync.Mutex as well and use it anywhere you read/write in the map


func newGlobalShortcuts(ctx context.Context, d *dispatcher, i *identifier, w *writer) (gs *GlobalShortcuts) {
var obj = newObject(ctx, d, i, w, i.new())
gs = &GlobalShortcuts{object: obj, callbacks: make(map[string]*callback)}
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 avoid creating the var obj and instead gs = &GlobalShortcuts{object: newObject(ctx, d, i, w, i.new()), ...} and below gs.On(...)?

}

// globalShortcutHandler Handle the GlobalShortcuts event triggered from astilectron
func globalShortcutHandler(gs *GlobalShortcuts, accelerator string) {
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 make this function part of the GlobalShortcuts struct instead and rename it to execCallback:

func (gs *GlobalShortcuts) execCallback(accelerator string) {
// TODO Lock mutex
// Exec callback
}

@benebsiny
Copy link
Contributor Author

Changes have been made, have a look at it.

@roromix
Copy link
Contributor

roromix commented Dec 12, 2023

This is an interesting feature. What is missing to merge and tag it?

README.md Show resolved Hide resolved
astilectron.go Outdated
@@ -187,6 +192,7 @@ 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 ?


const (
EventNameGlobalShortcutsCmdRegister = "global.shortcuts.cmd.register"
EventNameGlobalShortcutsCmdIsRegistered = "global.shortcuts.cmd.is.register"
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 add the missing ed at the end of global.shortcuts.cmd.is.register?


type globalShortcutsCallback func()

// GlobalShortcuts represents a global shortcut
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 replace the comment with // GlobalShortcuts represents global shortcuts?

type GlobalShortcuts struct {
*object
m *sync.Mutex
callbacks map[string]*globalShortcutsCallback // Store all registered Global Shortcuts
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 remove the comment here?

global_shortcuts.go Show resolved Hide resolved
return
}

// IsRegistered Check if a global shortcut is registered
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 update the comment to IsRegistered checks whether a global shortcut is registered

Also could you make the same modifications I proposed for the previous function?

return
}

// Unregister Unregister a global shortcut
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 update the comment to Unregister unregisters a global shortcut

Also could you make the same modifications I proposed for the previous function?

return
}

// No need to find the callback from the map and delete it
Copy link
Owner

Choose a reason for hiding this comment

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

I do think cleaning the map is still a good idea

Copy link
Contributor Author

@benebsiny benebsiny Dec 14, 2023

Choose a reason for hiding this comment

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

The reason why I didn't clean the map is that there are extra costs to determine the corresponded shortcut to be removed from the map. For example, a user registered an event with Ctrl+Shift+S then unregistered with S+Shift+Ctrl, expected that shortcut should be unregistred. Therefore Ctrl+Shift+S should be removed from the map.
A more complex case: registered with CtrlOrCmd+X then unregistered with Cmd+X, expected that shortcut is unregistered. Therefore CtrlOrCmd+X should be removed from the map.
It's too complex to determine the equivalent shortcuts, the best way is to let electron decide whether the shortcuts should be registered or unregistered.

Copy link
Owner

Choose a reason for hiding this comment

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

Mmm last time I used shortcuts in JS, Ctrl+Shift+S was different than S+Shift+Ctrl 🤔 Therefore wanting to register a shortcut Ctrl+Shift+S and unregister it with S+Shift+Ctrl should not work in Electron 🤔

Nevertheless, I think it's not too much to ask the developer to be consistent in the accelerator he/she uses when registering/unregistering a shortcut. Also I'd rather keep the map the closest to reality.

Therefore, I'd rather see the map cleaned 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mmm last time I used shortcuts in JS, Ctrl+Shift+S was different than S+Shift+Ctrl 🤔 Therefore wanting to register a shortcut Ctrl+Shift+S and unregister it with S+Shift+Ctrl should not work in Electron 🤔

Howerver, the combination of ctrl+shift+S was unregistered successfully.

Therefore, I'd rather see the map cleaned 👍

Sure thing

global_shortcuts.go Show resolved Hide resolved
@asticode
Copy link
Owner

That PR got lost in my notifications, my apologies. I've reviewed it and made comments 👍

@benebsiny
Copy link
Contributor Author

@asticode Hi, I've followed suggestions to make changes, have a look at it. astilectron is also updated!

Copy link
Owner

@asticode asticode left a comment

Choose a reason for hiding this comment

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

Thanks for the last changes, there are 2 remaining points 👍

I'll also check the JS PR 👍

m *sync.Mutex
}

var callbacks map[string]*globalShortcutsCallback
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 not use a pointer to globalShortcutsCallback in the map?

return
}

// No need to find the callback from the map and delete it
Copy link
Owner

Choose a reason for hiding this comment

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

Mmm last time I used shortcuts in JS, Ctrl+Shift+S was different than S+Shift+Ctrl 🤔 Therefore wanting to register a shortcut Ctrl+Shift+S and unregister it with S+Shift+Ctrl should not work in Electron 🤔

Nevertheless, I think it's not too much to ask the developer to be consistent in the accelerator he/she uses when registering/unregistering a shortcut. Also I'd rather keep the map the closest to reality.

Therefore, I'd rather see the map cleaned 👍

@benebsiny
Copy link
Contributor Author

@asticode Hi, I've updated, have a look at it 👍

m *sync.Mutex
}

var callbacks map[string]globalShortcutsCallback
Copy link
Owner

Choose a reason for hiding this comment

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

I missed it in my previous reviews, but could you move callbacks to the GlobalShortcuts struct and make sure you initialize it with make(map[string]globalShortcutsCallback) in newGlobalShortcuts?


// Send an event to astilectron to unregister the global shortcut
_, err = synchronousEvent(gs.ctx, gs, gs.w, Event{Name: EventNameGlobalShortcutsCmdUnregister, TargetID: gs.id, GlobalShortcuts: &EventGlobalShortcuts{Accelerator: accelerator}}, EventNameGlobalShortcutsEventUnregistered)
if err != nil {
Copy link
Owner

Choose a reason for hiding this comment

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

Shouldn't it be instead:

if err != nil {
  return
}

gs.m.Lock()
delete(callbacks, accelerator)
gs.m.Unlock()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah.. yes, my fault

@benebsiny
Copy link
Contributor Author

@asticode I've updated, have a look at the latest changes

Copy link
Owner

@asticode asticode left a comment

Choose a reason for hiding this comment

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

I've merged the JS PR but we need to update the astilectron here as well.

Also I've found a minor issue to fix 👍

}
return
})
gs.callbacks = make(map[string]globalShortcutsCallback)
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 remove this line?

"net"
"os/exec"
"runtime"
"time"

"github.com/asticode/go-astikit"
)

// Versions
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 update DefaultVersionAstilectron to v0.58.0 below?

@benebsiny
Copy link
Contributor Author

@asticode Hi, minor issues are fixed, have a look at it

@asticode asticode merged commit e91cd32 into asticode:master Dec 21, 2023
@asticode
Copy link
Owner

Thanks for sticking with this PR! ❤️

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants