File: ./go.mod
   1 module breakrem
   2 
   3 go 1.18
   4 
   5 require golang.org/x/sys v0.2.0

     File: ./go.sum
   1 golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
   2 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

     File: ./info.txt
   1 This app keeps reminding you to take breaks from your
   2 computer with pop-ups every %d minutes: time isn't
   3 counted while pop-ups are showing.
   4 
   5 The pop-ups will keep coming regularly as long as you
   6 keep pressing OK to dismiss them when they show up;
   7 pressing Cancel or the ✕ on the top corner will quit
   8 this app and stop the reminders.
   9 
  10 Note: you can start this app from the command-line with
  11 a number between 1 and 20 to set how many minutes to
  12 wait for each break reminder.

     File: ./main.go
   1 package main
   2 
   3 import (
   4     "os"
   5     "strconv"
   6     "strings"
   7     "time"
   8 
   9     _ "embed"
  10 )
  11 
  12 // note: the code is avoiding using the fmt package to save hundreds of
  13 // kilobytes on the resulting executable, which is a noticeable difference
  14 
  15 // note: info.txt is a format string with a %d for the # of minutes to wait
  16 
  17 //go:embed info.txt
  18 var startMsgFormat string
  19 
  20 // replace the %d with the # of minutes to wait
  21 const timeUpFormat = "" +
  22     "The %d minutes have passed; time for a break\n" +
  23     "\n" +
  24     "To start the countdown for the next reminder, press OK\n" +
  25     "For no more reminders, press Cancel or X at the top"
  26 
  27 func main() {
  28     // find how many minutes to wait: default is 15
  29     minutes := 15
  30     if len(os.Args) > 1 {
  31         f, err := strconv.ParseFloat(os.Args[1], 64)
  32         if err == nil && 1 <= f && f <= 20 {
  33             // round up any valid float between 10 and 20
  34             minutes = int(f + 0.5)
  35         }
  36     }
  37 
  38     // explain what the program does and give a chance to quit right away
  39     startMsg := makeMessage(startMsgFormat, minutes)
  40     if !show("Break Reminder", startMsg) {
  41         return
  42     }
  43 
  44     breakMsg := makeMessage(timeUpFormat, minutes)
  45     period := time.Duration(minutes) * time.Minute
  46 
  47     // show popup periodically until cancelled
  48     for {
  49         time.Sleep(period)
  50         if !show("Time for a Break", breakMsg) {
  51             // quit if latest popup wasn't OK-ed
  52             return
  53         }
  54     }
  55 }
  56 
  57 // makeMessage takes a format string whose only special sequence is `%d`,
  58 // along with an integer to go along with it
  59 func makeMessage(fs string, n int) string {
  60     // return fmt.Sprintf(fs, n)
  61     return strings.Replace(fs, `%d`, strconv.Itoa(n), 1)
  62 }

     File: ./ui_windows.go
   1 package main
   2 
   3 import (
   4     "unsafe"
   5 
   6     "golang.org/x/sys/windows"
   7 )
   8 
   9 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messageboxw
  10 
  11 const (
  12     // dialog-window options
  13     okCancel        = 0x00000001
  14     exclamationIcon = 0x00000030
  15     infoIcon        = 0x00000040
  16     systemModal     = 0x00001000
  17 
  18     // dialog results
  19     okButton = 1 // result when OK was chosen
  20 )
  21 
  22 var (
  23     user32         = windows.MustLoadDLL("user32.dll")
  24     msgBox         = user32.MustFindProc("MessageBoxW")
  25     getShellWindow = user32.MustFindProc("GetShellWindow")
  26 )
  27 
  28 // show an OK/Cancel message-box and return whether OK was pressed; this func blocks
  29 func show(title, msg string) (ok bool) {
  30     const style = okCancel | infoIcon | systemModal
  31     desktop, _, _ := getShellWindow.Call()
  32     result, _, _ := msgBox.Call(desktop, winstring(msg), winstring(title), style)
  33     return result == okButton
  34 }
  35 
  36 // turn strings into data compatible with the windows API
  37 func winstring(s string) uintptr {
  38     return uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(s)))
  39 }