Skip to content

Commit af5db80

Browse files
Added back the copy to clipboard function and util functions for recording the screen
1 parent 35bdf2a commit af5db80

File tree

6 files changed

+169
-28
lines changed

6 files changed

+169
-28
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ require (
77
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
88
github.com/go-vgo/robotgo v0.110.8
99
github.com/manifoldco/promptui v0.9.0
10+
github.com/robotn/gohook v0.42.2
1011
github.com/schollz/progressbar/v3 v3.18.0
1112
github.com/tailscale/win v0.0.0-20250627215312-f4da2b8ee071
1213
golang.design/x/clipboard v0.7.1
14+
golang.design/x/hotkey v0.4.1
1315
)
1416

1517
require (
@@ -38,7 +40,6 @@ require (
3840
github.com/vcaesar/screenshot v0.11.1 // indirect
3941
github.com/vcaesar/tt v0.20.1 // indirect
4042
github.com/yusufpapurcu/wmi v1.2.4 // indirect
41-
golang.design/x/hotkey v0.4.1 // indirect
4243
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
4344
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
4445
golang.org/x/image v0.29.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
5656
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
5757
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
5858
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
59+
github.com/robotn/gohook v0.42.2 h1:AI9OVh5o59c76jp9Xcc4NpIvze2YeKX1Rn8JvflAUXY=
60+
github.com/robotn/gohook v0.42.2/go.mod h1:PYgH0f1EaxhCvNSqIVTfo+SIUh1MrM2Uhe2w7SvFJDE=
5961
github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ=
6062
github.com/robotn/xgb v0.10.0 h1:O3kFbIwtwZ3pgLbp1h5slCQ4OpY8BdwugJLrUe6GPIM=
6163
github.com/robotn/xgb v0.10.0/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ=
@@ -91,6 +93,8 @@ golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
9193
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
9294
golang.design/x/hotkey v0.4.1 h1:zLP/2Pztl4WjyxURdW84GoZ5LUrr6hr69CzJFJ5U1go=
9395
golang.design/x/hotkey v0.4.1/go.mod h1:M8SGcwFYHnKRa83FpTFQoZvPO5vVT+kWPztFqTQKmXA=
96+
golang.design/x/mainthread v0.3.0 h1:UwFus0lcPodNpMOGoQMe87jSFwbSsEY//CA7yVmu4j8=
97+
golang.design/x/mainthread v0.3.0/go.mod h1:vYX7cF2b3pTJMGM/hc13NmN6kblKnf4/IyvHeu259L0=
9498
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
9599
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
96100
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=

main.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,27 +54,17 @@ func extractFFmpegExe(zipPath, destDir string) error {
5454
return os.ErrNotExist
5555
}
5656

57-
type Config struct {
58-
SaveLocation string `json:"save_location"`
59-
RecordFunc bool `json:"record_func_enabled"`
60-
RecordingOpts RecordingOptions `json:"recording_options"`
57+
type hotkeyConfig struct {
58+
Modkeys []string `json:"modkeys"`
59+
Finalkey string `json:"finalkey"`
60+
Note string `json:"note"`
6161
}
6262

63-
func setConfig(key string, value any) {
64-
data := make(map[string]any)
65-
content, err := os.ReadFile(configFilePath)
66-
if err == nil {
67-
_ = json.Unmarshal(content, &data)
68-
}
69-
70-
data[key] = value
71-
72-
updData, err := json.MarshalIndent(data, "", " ")
73-
if err != nil {
74-
panic(err)
75-
}
76-
os.WriteFile(configFilePath, updData, 0644)
77-
initConfig()
63+
type Config struct {
64+
SaveLocation string `json:"save_location"`
65+
RecordFunc bool `json:"record_func_enabled"`
66+
RecordingOpts *RecordingOptions `json:"recording_options,omitempty"`
67+
HotkeyConfig *hotkeyConfig `json:"hotkey_config"`
7868
}
7969

8070
func initConfig() {
@@ -88,8 +78,10 @@ func initConfig() {
8878
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
8979
home, _ := os.UserHomeDir()
9080
config = Config{
91-
SaveLocation: filepath.Join(home, "Desktop"),
92-
RecordFunc: true,
81+
SaveLocation: filepath.Join(home, "Desktop"),
82+
RecordFunc: true,
83+
RecordingOpts: nil,
84+
HotkeyConfig: nil,
9385
}
9486
data, err := json.MarshalIndent(config, "", " ")
9587
if err != nil {
@@ -167,12 +159,14 @@ func initDownloads() {
167159
}
168160
}
169161

162+
const HOTKEY_WARNING = "DO NOT CHANGE THESE MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING"
163+
170164
func init() {
171165
if !(runtime.GOOS == "windows" && runtime.GOARCH == "amd64") {
172166
panic("Captr is only supported on Windows x64")
173167
}
174168
initConfig()
175-
configMode := flag.Bool("config", false, "Configure Captr")
169+
configMode, reset, hotkeyConfigMode := flag.Bool("config", false, "Configure Captr"), flag.Bool("reset", false, "Reset Captr and delete appdata"), flag.Bool("hotkey", false, "Register a hotkey for stopping recording")
176170
flag.Parse()
177171
if *configMode {
178172
cmd := exec.Command("notepad.exe", configFilePath)
@@ -182,6 +176,37 @@ func init() {
182176
}
183177
os.Exit(0)
184178
}
179+
if *reset {
180+
// Declaring again for safety. Even if anything fails, atleast it won't delete your entire appdata directory
181+
appdata, _ := os.UserConfigDir()
182+
prompt := promptui.Prompt{
183+
Label: "Are you sure you want to reset Captr",
184+
IsConfirm: true,
185+
}
186+
_, err := prompt.Run()
187+
if err != nil {
188+
fmt.Println("Action Aborted")
189+
os.Exit(0)
190+
}
191+
err = os.RemoveAll(filepath.Join(appdata, "captr"))
192+
if err != nil {
193+
fmt.Println("Couldn't delete appdata directory")
194+
os.Exit(1)
195+
}
196+
fmt.Println("Captr has been reset")
197+
os.Exit(0)
198+
}
199+
if *hotkeyConfigMode {
200+
mods, finalkey := RegisterHotkey()
201+
hotkeyConfig := hotkeyConfig{
202+
Modkeys: mods,
203+
Finalkey: finalkey,
204+
Note: HOTKEY_WARNING,
205+
}
206+
setConfig("hotkey_config", hotkeyConfig)
207+
fmt.Println("Hotkeys have been registered successfully")
208+
os.Exit(0)
209+
}
185210
initDownloads()
186211
}
187212

ssdisplay.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"image"
65
"path/filepath"
76
"time"
87

@@ -11,15 +10,15 @@ import (
1110
"github.com/manifoldco/promptui"
1211
)
1312

14-
func takeScreenshot(fileName string, displayNum int) image.Image {
13+
func takeScreenshot(fileName string, displayNum int) {
1514
robotgo.DisplayID = displayNum
1615
img, err := robotgo.CaptureImg()
1716
if err != nil {
1817
fmt.Println(err)
19-
return nil
18+
return
2019
}
2120
robotgo.Save(img, fileName)
22-
return img
21+
CopyImgToClipboard(img)
2322
}
2423

2524
func Screenshot_Display() {

sswindow.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func Screenshot_Window() {
3838
filename := filepath.Join(config.SaveLocation, fmt.Sprintf("Screenshot_%s.png", time.Now().Format("20060102_150405")))
3939
robotgo.Save(img, filename)
4040
fmt.Printf("Screenshot saved at %s", filename)
41+
CopyImgToClipboard(img)
4142
notification := toast.Notification{
4243
AppID: "Captr",
4344
Title: "Screenshot Captured",

utils.go

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,39 @@ package main
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"fmt"
67
"image"
78
"image/png"
9+
"os"
810
"os/exec"
911
"path/filepath"
1012
"syscall"
1113
"unsafe"
1214

1315
"github.com/JamesHovious/w32"
1416
"github.com/manifoldco/promptui"
17+
hook "github.com/robotn/gohook"
1518
"golang.design/x/clipboard"
1619
)
1720

21+
func setConfig(key string, value any) {
22+
data := make(map[string]any)
23+
content, err := os.ReadFile(configFilePath)
24+
if err == nil {
25+
_ = json.Unmarshal(content, &data)
26+
}
27+
28+
data[key] = value
29+
30+
updData, err := json.MarshalIndent(data, "", " ")
31+
if err != nil {
32+
panic(err)
33+
}
34+
os.WriteFile(configFilePath, updData, 0644)
35+
initConfig()
36+
}
37+
1838
var (
1939
user32 = syscall.NewLazyDLL("user32.dll")
2040
procEnumWindows = user32.NewProc("EnumWindows")
@@ -47,7 +67,7 @@ func CopyImgToClipboard(img image.Image) {
4767
return
4868
}
4969
clipboard.Write(clipboard.FmtImage, buf.Bytes())
50-
fmt.Println("Image copied to clipboard")
70+
fmt.Println("\nScreenshot copied to clipboard")
5171
}
5272

5373
func SetProcessDPIAware() error {
@@ -426,3 +446,94 @@ func getFfmpegPath() string {
426446

427447
return filepath.Join(appdataDir, "bin", "ffmpeg.exe")
428448
}
449+
450+
func ternary[T any](cond bool, a, b T) T {
451+
if cond {
452+
return a
453+
}
454+
return b
455+
}
456+
457+
func RegisterHotkey() ([]string, string) {
458+
var (
459+
modKeys = map[uint16]string{
460+
29: "ctrl", // LCtrl
461+
3613: "ctrl", // RCtrl
462+
56: "alt", // LAlt
463+
3640: "alt", // RAlt
464+
42: "shift", // LShift
465+
54: "shift", // RShift
466+
}
467+
468+
keyOrder = []string{"ctrl", "alt", "shift"}
469+
470+
keyMap = map[uint16]string{
471+
2: "1", 3: "2", 4: "3", 5: "4", 6: "5",
472+
7: "6", 8: "7", 9: "8", 10: "9", 11: "0",
473+
16: "q", 17: "w", 18: "e", 19: "r", 20: "t",
474+
21: "y", 22: "u", 23: "i", 24: "o", 25: "p",
475+
30: "a", 31: "s", 32: "d", 33: "f", 34: "g",
476+
35: "h", 36: "j", 37: "k", 38: "l",
477+
44: "z", 45: "x", 46: "c", 47: "v", 48: "b",
478+
49: "n", 50: "m",
479+
}
480+
)
481+
fmt.Println("Press your hotkey combo (e.g. ctrl+alt+z)...")
482+
483+
pressedMods := map[string]bool{}
484+
var mainKey string
485+
486+
evChan := hook.Start()
487+
defer hook.End()
488+
489+
for ev := range evChan {
490+
if ev.Kind != hook.KeyDown {
491+
continue
492+
}
493+
494+
if mod, ok := modKeys[ev.Keycode]; ok {
495+
pressedMods[mod] = true
496+
continue
497+
}
498+
499+
if k, ok := keyMap[ev.Keycode]; ok {
500+
mainKey = k
501+
break
502+
} else {
503+
fmt.Println("Invalid key. Only A-Z or 0-9 accepted.")
504+
os.Exit(1)
505+
}
506+
}
507+
508+
var mods []string
509+
for _, mod := range keyOrder {
510+
if pressedMods[mod] {
511+
mods = append(mods, mod)
512+
}
513+
}
514+
return mods, mainKey
515+
516+
// mods := []hotkey.Modifier{}
517+
// var finalKey rune
518+
// for _, mod := range combo {
519+
// switch mod {
520+
// case "ctrl":
521+
// mods = append(mods, hotkey.ModCtrl)
522+
// case "alt":
523+
// mods = append(mods, hotkey.ModAlt)
524+
// case "shift":
525+
// mods = append(mods, hotkey.ModShift)
526+
// default:
527+
// finalKey = []rune(mod)[0]
528+
// }
529+
// }
530+
531+
// hk := hotkey.New(mods, hotkey.Key(finalKey))
532+
// hk.Register()
533+
// defer hk.Unregister()
534+
// keyChan := hk.Keydown()
535+
// for range keyChan {
536+
// fmt.Println("Combo pressed!")
537+
// os.Exit(0)
538+
// }
539+
}

0 commit comments

Comments
 (0)