// Read or write text data to or from the clipboard.
//
// Check out a usage example at https://github.com/sven-seyfert/gomisc/blob/main/examples/clipboard/main.go
package clipboard
// Read returns the current text data of the clipboard. In case a file was
// copied to the clipboard, the file path will be returned.
// If there is an error, the error will be returned.
func Read() (string, error) {
return read()
}
// Write sets the given text data to the clipboard. If there is an error,
// the error will be returned.
func Write(text string) error {
return write(text)
}
package clipboard
import (
"fmt"
"os/exec"
"strings"
)
func read() (string, error) {
// Command looks for a file name, in case a file was copied to the clipboard.
command := "Get-Clipboard -Format FileDropList -RAW"
cmd := exec.Command("powershell", command)
content, err := getClipboardText(cmd)
if err != nil {
return "", err
}
if content != "" {
// Return found file path.
return removeTrailingNewline(content), nil
}
// Command simply gets the clipboard text.
command = "Get-Clipboard -Format text"
cmd = exec.Command("powershell", command)
content, err = getClipboardText(cmd)
if err != nil {
return "", err
}
// Return found text.
return removeTrailingNewline(content), nil
}
func getClipboardText(cmd *exec.Cmd) (string, error) {
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
defer stdin.Close()
output, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
return string(output), nil
}
func removeTrailingNewline(text string) string {
return strings.TrimSuffix(text, "\r\n")
}
func write(text string) error {
command := fmt.Sprintf("$rawString = @'\n%s\n'@; Set-Clipboard -Value $rawString", text)
cmd := exec.Command("powershell", command)
_, err := cmd.CombinedOutput()
if err != nil {
return err
}
return nil
}
// Encrypt and decrypt data (strings) by the usage of a secret and GCM.
//
// Check out a usage example at https://github.com/sven-seyfert/gomisc/blob/main/examples/crypt/main.go
package crypt
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
)
// GenerateSecretKey generates a encoded string based on a 32 byte key for
// AES-256. This secret key will be used in Encrypt and Decypt functions.
// If there is an error, the error will be returned.
func GenerateSecretKey() (string, error) {
bytes, err := generate32ByteKeyForAes256()
if err != nil {
return "", err
}
return encodeByteToString(bytes), nil
}
func generate32ByteKeyForAes256() ([]byte, error) {
length := 32
bytes := make([]byte, length)
// Check the correct number of bytes
_, err := rand.Read(bytes)
if err != nil {
return nil, err
}
return bytes, nil
}
func encodeByteToString(bytes []byte) string {
return hex.EncodeToString(bytes)
}
// Encrypt encrypts a string by the usage of a secret and the GCM
// cryptography mode. If there is an error, the error will be returned.
func Encrypt(stringToEncrypt, secret string) (string, error) {
key, err := decodeStringToBytes(secret)
if err != nil {
return "", err
}
plainText := []byte(stringToEncrypt)
block, err := createCipherBlock(key)
if err != nil {
return "", err
}
// Create new GCM (https://en.wikipedia.org/wiki/Galois/Counter_Mode)
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Create a nonce from GCM
nonce := make([]byte, aesGCM.NonceSize())
// Check the correct number of bytes
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return "", err
}
// Encrypt data
cipherText := aesGCM.Seal(nonce, nonce, plainText, nil)
return fmt.Sprintf("%x", cipherText), nil
}
func decodeStringToBytes(data string) ([]byte, error) {
key, err := hex.DecodeString(data)
if err != nil {
return nil, err
}
return key, nil
}
func createCipherBlock(key []byte) (cipher.Block, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return block, nil
}
// Decrypt decrypts a encrypted string by the usage of a secret and the
// GCM cryptography mode. If there is an error, the error will be returned.
func Decrypt(encryptedString, secret string) (string, error) {
key, err := decodeStringToBytes(secret)
if err != nil {
return "", err
}
enc, err := decodeStringToBytes(encryptedString)
if err != nil {
return "", err
}
block, err := createCipherBlock(key)
if err != nil {
return "", err
}
// Create new GCM (https://en.wikipedia.org/wiki/Galois/Counter_Mode)
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Get the nonce size
nonceSize := aesGCM.NonceSize()
// Extract the nonce from the encrypted data
nonce, cipherText := enc[:nonceSize], enc[nonceSize:]
// Decrypt data
plainText, err := aesGCM.Open(nil, nonce, cipherText, nil)
if err != nil {
return "", err
}
return string(plainText), nil
}
// Ensures only a single instance of the program runs at the same time.
//
// Check out a usage example at https://github.com/sven-seyfert/gomisc/blob/main/examples/singleinstance/main.go
package singleinstance
import (
"errors"
"fmt"
"os"
"time"
)
const instanceFile = "instance_indicator.lock"
// CreateInstanceFile creates a instance indicator file for this instance of
// the program. To ensure this current instance of the program is the only one,
// the function checks for a existing instance indicator file. If there is an
// error, the error will be returned.
func CreateInstanceFile() error {
if existsInstanceFile() {
message := `instance indicator file "%s" could not be created because it already exist`
return fmt.Errorf(message, instanceFile) //nolint:goerr113
}
file, err := os.Create(instanceFile)
if err != nil {
return err
}
defer file.Close()
// This 100 milliseconds delay ensures a robust file handling
time.Sleep(time.Millisecond * 100) //nolint:gomnd
return nil
}
func existsInstanceFile() bool {
_, err := os.Stat(instanceFile)
return !errors.Is(err, os.ErrNotExist)
}
// RemoveInstanceFile removes the previously created instance indicator file.
// Always set this to ensure the next single instance of the program can be run.
// If there is an error, the error will be returned.
func RemoveInstanceFile() error {
err := os.Remove(instanceFile)
if err != nil {
return err
}
return nil
}