Skip to content

Commit

Permalink
feat: Implement keyboard Enable/Disable functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
sonjek committed Jul 23, 2024
1 parent d98f426 commit 9eef58d
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


This lightweight application is designed to prevent your computer from entering sleep mode by periodically moving the cursor when it detects periods of inactivity.
Additionally, the program allows you to disable the keyboard programmatically (MacOS only).

## Installation from source

Expand Down
4 changes: 3 additions & 1 deletion cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package main

import (
"github.com/sonjek/mouse-stay-up/internal/config"
"github.com/sonjek/mouse-stay-up/internal/keyboard"
"github.com/sonjek/mouse-stay-up/internal/mouse"
"github.com/sonjek/mouse-stay-up/internal/tray"
)

func main() {
conf := config.NewConfig()
mc := mouse.NewController(conf)
trayIcon := tray.NewTray(mc, conf)
kc := keyboard.NewController()
trayIcon := tray.NewTray(mc, kc, conf)
trayIcon.Run()
}
15 changes: 15 additions & 0 deletions internal/keyboard/keyboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package keyboard

type Controller struct {
KeyboardLocked bool
disableChan chan bool
}

type KeyboardActions interface {

Check failure on line 8 in internal/keyboard/keyboard.go

View workflow job for this annotation

GitHub Actions / lint

exported: type name will be used as keyboard.KeyboardActions by other packages, and that stutters; consider calling this Actions (revive)
LockKeyboard()
UnlockKeyboard()
}

func NewController() *Controller {
return NewKeyboardController()
}
102 changes: 102 additions & 0 deletions internal/keyboard/keyboard_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//go:build darwin
// +build darwin

package keyboard

/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework Quartz
#include <Cocoa/Cocoa.h>
#include <Quartz/Quartz.h>
static CFMachPortRef eventTap;
static CFRunLoopSourceRef runLoopSource;
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
return NULL; // Block all keyboard events
}
void disableKeyboard() {
eventTap = CGEventTapCreate(
kCGHIDEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventFlagsChanged),
myCGEventCallback,
NULL
);
if (!eventTap) {
fprintf(stderr, "Failed to create event tap\n");
exit(1);
}
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
CFRunLoopRun();
}
void enableKeyboard() {
if (eventTap) {
CGEventTapEnable(eventTap, false);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CFRelease(runLoopSource);
CFRelease(eventTap);
}
}
*/
import "C"

func NewKeyboardController() *Controller {
return &Controller{
KeyboardLocked: false,
disableChan: make(chan bool),
}
}

func (c *Controller) LockKeyboard() {
// If already locked, do nothing
if c.KeyboardLocked {
return
}

c.toggleLockUnlock()

if c.disableChan == nil {
c.disableChan = make(chan bool)
}

go func() {
for {
select {
case <-c.disableChan:
return
default:
C.disableKeyboard()
}
}
}()
}

func (c *Controller) UnlockKeyboard() {
// If already unlocked, do nothing
if !c.KeyboardLocked {
return
}

c.toggleLockUnlock()

if c.disableChan != nil {
close(c.disableChan)

// Reset the channel to indicate it has been closed
c.disableChan = nil
}

C.enableKeyboard()
}

func (c *Controller) toggleLockUnlock() {
c.KeyboardLocked = !c.KeyboardLocked
}
19 changes: 19 additions & 0 deletions internal/keyboard/keyboard_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//go:build !darwin
// +build !darwin

package keyboard

import "C"
import "fmt"

func NewKeyboardController() *Controller {
return &Controller{}
}

func (c *Controller) LockKeyboard() {
fmt.Println("LockKeyboard called on non-macOS platform. No operation performed.")
}

func (c *Controller) UnlockKeyboard() {
fmt.Println("UnlockKeyboard called on non-macOS platform. No operation performed.")
}
41 changes: 40 additions & 1 deletion internal/tray/tray.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"embed"
"fmt"
"io"
"runtime"

"github.com/getlantern/systray"
"github.com/sonjek/mouse-stay-up/internal/config"
"github.com/sonjek/mouse-stay-up/internal/keyboard"
"github.com/sonjek/mouse-stay-up/internal/mouse"
"github.com/sonjek/mouse-stay-up/internal/utils"
)
Expand All @@ -31,16 +33,20 @@ func loadIcon() ([]byte, error) {

type Tray struct {
mouseController *mouse.Controller
keyboardController *keyboard.Controller
conf *config.Config
mEnable *systray.MenuItem
mDisable *systray.MenuItem
mWorkingHours *systray.MenuItem
kUnlockKeyboard *systray.MenuItem
kLockKeyboard *systray.MenuItem
workingHoursMenuItems map[string]*systray.MenuItem
}

func NewTray(mouseController *mouse.Controller, conf *config.Config) *Tray {
func NewTray(mouseController *mouse.Controller, keyboardController *keyboard.Controller, conf *config.Config) *Tray {
return &Tray{
mouseController: mouseController,
keyboardController: keyboardController,
conf: conf,
workingHoursMenuItems: make(map[string]*systray.MenuItem),
}
Expand All @@ -65,6 +71,9 @@ func (t *Tray) onReady() {
t.mDisable = systray.AddMenuItem("Disable movement", "Disable mouse movement")
t.mWorkingHours = systray.AddMenuItem("Working hours", "Select a range of working hours")
systray.AddSeparator()
t.kUnlockKeyboard = systray.AddMenuItem("Unlock keyboard", "Unlock keyboard")
t.kLockKeyboard = systray.AddMenuItem("Lock keyboard", "Lock keyboard")
systray.AddSeparator()
mAbout := systray.AddMenuItem("About", "Open GitHub repo")
mQuit := systray.AddMenuItem("Quit", "Quit the application")

Expand All @@ -75,6 +84,7 @@ func (t *Tray) onReady() {

// Adjust visibilities based on the app state
t.applyEnableDisable()
t.initLockKeyboard()

// Set a marker for the default working hours interval
t.workingHoursMenuItems[t.conf.WorkingHoursInterval].Check()
Expand All @@ -94,6 +104,12 @@ func (t *Tray) onReady() {
// When an hours interval item is clicked, update the workingHoursInterval interval and checkmarks
t.conf.SetWorkingHoursInterval(workingHoursInterval)
t.updateNightModeIntervalChecks(t.conf.WorkingHoursInterval)
case <-t.kLockKeyboard.ClickedCh:
t.applyEnableDisableKeyboard()
t.keyboardController.LockKeyboard()
case <-t.kUnlockKeyboard.ClickedCh:
t.applyEnableDisableKeyboard()
t.keyboardController.UnlockKeyboard()
case <-mAbout.ClickedCh:
if err := utils.OpenWebPage(config.GitRepo); err != nil {
panic(err)
Expand Down Expand Up @@ -152,5 +168,28 @@ func (t *Tray) applyEnableDisable() {
}
}

// Init LockKeyboard visibility
func (t *Tray) initLockKeyboard() {
switch runtime.GOOS {
case "darwin":
t.kLockKeyboard.Show()
default:
t.kLockKeyboard.Hide()
}

t.kUnlockKeyboard.Hide()
}

// Adjust visibilities and activity based on the app state
func (t *Tray) applyEnableDisableKeyboard() {
if t.keyboardController.KeyboardLocked {
t.kUnlockKeyboard.Hide()
t.kLockKeyboard.Show()
} else {
t.kUnlockKeyboard.Show()
t.kLockKeyboard.Hide()
}
}

func (t *Tray) onExit() {
}

0 comments on commit 9eef58d

Please sign in to comment.