added kitty image cleanup
This commit is contained in:
parent
426e088c43
commit
8046177af0
31
docs/FS.md
31
docs/FS.md
@ -131,9 +131,10 @@
|
||||
|
||||
* Full support for:
|
||||
|
||||
* Linux
|
||||
* macOS
|
||||
* Linux (primary support)
|
||||
* Windows (via WSL or native terminal)
|
||||
* macOS (should work same as on linux hopefully)
|
||||
|
||||
* All features should work identically or degrade gracefully.
|
||||
|
||||
### 2. **Performance & Responsiveness**
|
||||
@ -173,18 +174,20 @@
|
||||
|
||||
```shell
|
||||
~/.config/whspbrd/
|
||||
├── config.json
|
||||
├── keys/
|
||||
│ ├── private.asc
|
||||
│ └── public.asc
|
||||
├── messages/
|
||||
│ ├── user123.json
|
||||
│ └── user456.json
|
||||
├── media/
|
||||
│ ├── user123_avatar.png
|
||||
│ └── msg_img_abc123.png
|
||||
├── plugins/
|
||||
│ └── music_status.so
|
||||
├── config.json
|
||||
├── icon.png
|
||||
└── servers
|
||||
├── default
|
||||
│ ├── server.json
|
||||
│ └── users
|
||||
│ ├── alice
|
||||
│ │ ├── alice.pub
|
||||
│ │ ├── icon.png
|
||||
│ │ └── messages.json
|
||||
│ └── bob
|
||||
│ ├── bob.pub
|
||||
│ └── messages.json
|
||||
└── secondary
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
- <https://github.com/dolmen-go/kittyimg> - rendrování obrázků v terminálu NEE, napsali jsme vlastní
|
||||
(profilové obrázky nebo posílané médium)
|
||||
- <https://github.com/gographics/imagick> - image editing NEEE, nepotřebujeme actually
|
||||
- <https://github.com/gen2brain/beeep> - notification messages
|
||||
- <https://github.com/gookit/color> - colors in terminal
|
||||
- <https://github.com/integrii/flaggy> - terminal commands interface maybe instead of conda
|
||||
|
||||
## C
|
||||
|
||||
@ -31,4 +34,4 @@
|
||||
|
||||
- načítat a sdílet přehrávanou hudbu (discord spotify integration, but with playerctl or some other music protocol)
|
||||
- v go je možné ukládat do binárky standartní soubory, možná se to třeba bude [hodit](https://www.youtube.com/watch?v=7EK06n485nk&pp=ygUJZ28gZW1iZWQg)
|
||||
- automaticky detekovat, že zařízení jsou na stejné síti a pak posílat komunikaci p2p
|
||||
- automaticky detekovat, že zařízení jsou na stejné síti a pak posílat komunikaci p2p
|
||||
|
||||
@ -4,9 +4,11 @@
|
||||
|
||||
- [ ] Initial configuration setup
|
||||
- [ ] Solve colors issue
|
||||
- [ ] No chat handling and no chat selected on the startup of the application
|
||||
- [ ] Complete chat loading and sending messages to contacts so it is written to the file
|
||||
- [ ] Create systray
|
||||
- [ ] Share what music am i listening using mpris
|
||||
- [ ] Colors and themes
|
||||
|
||||
### Chat
|
||||
|
||||
@ -60,6 +62,11 @@
|
||||
|
||||
## Communication
|
||||
|
||||
## Application integration with the system
|
||||
|
||||
- [ ] Systray and how to manage to not have multiple instances of the application running
|
||||
- [ ] Create simple cmd commands that would be intuitive and simple to use
|
||||
|
||||
## IDEAS
|
||||
|
||||
- just set window size of the box to be even, so there should not be need in rendering half of the profile picture
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"whspbrd/pkg/cell_size"
|
||||
"whspbrd/pkg/render_image"
|
||||
"whspbrd/pkg/clean_image"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
@ -40,6 +41,11 @@ func layoutInput(g *gocui.Gui, maxX, maxY int) error {
|
||||
|
||||
func updateChatView(v *gocui.View) {
|
||||
v.Clear()
|
||||
|
||||
clear := cleanimage.NewKittyImageCleaner()
|
||||
// TODO: In future optimize this to only clear certain part of screen
|
||||
fmt.Print(clear.DeleteAllVisiblePlacements(true))
|
||||
|
||||
for i, msg := range messages {
|
||||
fmt.Fprintf(v, "%s\n\n", msg)
|
||||
w, h, err := cell_size.GetTerminalCellSizePixels()
|
||||
@ -65,11 +71,11 @@ func sendMessage(g *gocui.Gui, v *gocui.View) error {
|
||||
v.SetCursor(0, 0)
|
||||
v.SetOrigin(0, 0)
|
||||
|
||||
if input != "" {
|
||||
messages = append(messages, "\t\t\t\t\tYou:\n\t\t\t\t\t"+input)
|
||||
if chatView, err := g.View("chat"); err == nil {
|
||||
updateChatView(chatView)
|
||||
}
|
||||
}
|
||||
WriteMessage(users[selectedUserIdx], "You", users[selectedUserIdx], input)
|
||||
|
||||
messages = []string{}
|
||||
LoadMessages(users[selectedUserIdx])
|
||||
|
||||
updateChatView(g.Views()[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@ func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlK, gocui.ModNone, prevContact); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleProfileView); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ func layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
if err := layoutChat(g, maxX, maxY); err != nil {
|
||||
//updateChatView(g.Views()[1])
|
||||
return err
|
||||
}
|
||||
if err := layoutInput(g, maxX, maxY); err != nil {
|
||||
|
||||
@ -3,9 +3,11 @@ package tui
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -39,6 +41,60 @@ func LoadContacts(path string) {
|
||||
}
|
||||
}
|
||||
|
||||
func WriteMessage(username, sender, receiver, content string) error {
|
||||
chatFile := filepath.Join("configs", "servers", "default", "users", strings.ToLower(username), "messages.json")
|
||||
|
||||
if _, err := os.Stat(chatFile); os.IsNotExist(err) {
|
||||
emptyData := ChatData{Messages: []ChatMessage{}}
|
||||
data, _ := json.MarshalIndent(emptyData, "", " ")
|
||||
if err := os.WriteFile(chatFile, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to create chat file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(chatFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading chat file: %v", err)
|
||||
}
|
||||
|
||||
var chatData ChatData
|
||||
if len(data) > 0 {
|
||||
if err := json.Unmarshal(data, &chatData); err != nil {
|
||||
return fmt.Errorf("error parsing JSON: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
newID := "1"
|
||||
if len(chatData.Messages) > 0 {
|
||||
lastMsg := chatData.Messages[len(chatData.Messages)-1]
|
||||
if lastID, err := strconv.Atoi(lastMsg.ID); err == nil {
|
||||
newID = strconv.Itoa(lastID + 1)
|
||||
}
|
||||
}
|
||||
|
||||
encodedContent := base64.StdEncoding.EncodeToString([]byte(content))
|
||||
|
||||
newMsg := ChatMessage{
|
||||
ID: newID,
|
||||
Sender: sender,
|
||||
Receiver: receiver,
|
||||
Content: encodedContent,
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
chatData.Messages = append(chatData.Messages, newMsg)
|
||||
updatedData, err := json.MarshalIndent(chatData, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding JSON: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(chatFile, updatedData, 0644); err != nil {
|
||||
return fmt.Errorf("error writing chat file: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadMessages(username string) {
|
||||
chatFile := filepath.Join("configs", "servers", "default", "users", strings.ToLower(username), "messages.json")
|
||||
data, err := os.ReadFile(chatFile)
|
||||
|
||||
257
pkg/clean_image/clean_image.go
Normal file
257
pkg/clean_image/clean_image.go
Normal file
@ -0,0 +1,257 @@
|
||||
package cleanimage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KittyImageCleaner provides methods to generate Kitty graphics protocol
|
||||
// commands for deleting images.
|
||||
type KittyImageCleaner struct{}
|
||||
|
||||
// NewKittyImageCleaner creates a new instance of KittyImageCleaner.
|
||||
func NewKittyImageCleaner() *KittyImageCleaner {
|
||||
return &KittyImageCleaner{}
|
||||
}
|
||||
|
||||
// buildCommand constructs the base Kitty graphics protocol command.
|
||||
func (kic *KittyImageCleaner) buildCommand(params map[string]string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\033_Ga=d") // Start with the delete action
|
||||
|
||||
if len(params) > 0 {
|
||||
var paramStrings []string
|
||||
for key, value := range params {
|
||||
paramStrings = append(paramStrings, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
sb.WriteString(",")
|
||||
sb.WriteString(strings.Join(paramStrings, ","))
|
||||
}
|
||||
|
||||
sb.WriteString("\033\\") // End the command
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// DeleteAllVisiblePlacements deletes all images visible on screen.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteAllVisiblePlacements(freeData bool) string {
|
||||
if freeData {
|
||||
return kic.buildCommand(map[string]string{"d": "A"})
|
||||
}
|
||||
return kic.buildCommand(map[string]string{"d": "a"})
|
||||
}
|
||||
|
||||
// DeleteByID deletes images with a specific ID.
|
||||
// 'imageID' is the ID of the image to delete.
|
||||
// 'placementID' is an optional placement ID. If 0, all placements with the imageID are deleted.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByID(imageID int, placementID int, freeData bool) string {
|
||||
params := make(map[string]string)
|
||||
if freeData {
|
||||
params["d"] = "I"
|
||||
} else {
|
||||
params["d"] = "i"
|
||||
}
|
||||
params["i"] = fmt.Sprintf("%d", imageID)
|
||||
if placementID != 0 {
|
||||
params["p"] = fmt.Sprintf("%d", placementID)
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteNewestByID deletes the newest image with a specified number (ID).
|
||||
// 'imageNumber' is the number (ID) of the newest image to delete.
|
||||
// 'placementID' is an optional placement ID. If 0, all placements with the imageNumber are deleted.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteNewestByID(imageNumber int, placementID int, freeData bool) string {
|
||||
params := make(map[string]string)
|
||||
if freeData {
|
||||
params["d"] = "N"
|
||||
} else {
|
||||
params["d"] = "n"
|
||||
}
|
||||
params["I"] = fmt.Sprintf("%d", imageNumber) // Note: Kitty uses 'I' for number here
|
||||
if placementID != 0 {
|
||||
params["p"] = fmt.Sprintf("%d", placementID)
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByCursorPosition deletes all placements that intersect with the current cursor position.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByCursorPosition(freeData bool) string {
|
||||
if freeData {
|
||||
return kic.buildCommand(map[string]string{"d": "C"})
|
||||
}
|
||||
return kic.buildCommand(map[string]string{"d": "c"})
|
||||
}
|
||||
|
||||
// DeleteAnimationFrames deletes animation frames.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteAnimationFrames(freeData bool) string {
|
||||
if freeData {
|
||||
return kic.buildCommand(map[string]string{"d": "F"})
|
||||
}
|
||||
return kic.buildCommand(map[string]string{"d": "f"})
|
||||
}
|
||||
|
||||
// DeleteByCellPosition deletes all placements that intersect a specific cell.
|
||||
// 'x', 'y' are the coordinates of the cell (1-indexed).
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByCellPosition(x, y int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"x": fmt.Sprintf("%d", x),
|
||||
"y": fmt.Sprintf("%d", y),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "P"
|
||||
} else {
|
||||
params["d"] = "p"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByCellAndZIndex deletes all placements that intersect a specific cell
|
||||
// and have a specific z-index.
|
||||
// 'x', 'y' are the coordinates of the cell (1-indexed).
|
||||
// 'zIndex' is the z-index of the placements to delete.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByCellAndZIndex(x, y, zIndex int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"x": fmt.Sprintf("%d", x),
|
||||
"y": fmt.Sprintf("%d", y),
|
||||
"z": fmt.Sprintf("%d", zIndex),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "Q"
|
||||
} else {
|
||||
params["d"] = "q"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByIDRange deletes all images whose ID is within a specified range.
|
||||
// 'minID' is the minimum ID (inclusive).
|
||||
// 'maxID' is the maximum ID (inclusive).
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
// (Requires Kitty version 0.33.0 or later)
|
||||
func (kic *KittyImageCleaner) DeleteByIDRange(minID, maxID int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"x": fmt.Sprintf("%d", minID),
|
||||
"y": fmt.Sprintf("%d", maxID),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "R"
|
||||
} else {
|
||||
params["d"] = "r"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByColumn deletes all placements that intersect the specified column.
|
||||
// 'column' is the column number (1-indexed).
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByColumn(column int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"x": fmt.Sprintf("%d", column),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "X"
|
||||
} else {
|
||||
params["d"] = "x"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByRow deletes all placements that intersect the specified row.
|
||||
// 'row' is the row number (1-indexed).
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByRow(row int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"y": fmt.Sprintf("%d", row),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "Y"
|
||||
} else {
|
||||
params["d"] = "y"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
// DeleteByZIndex deletes all placements that have the specified z-index.
|
||||
// 'zIndex' is the z-index of the placements to delete.
|
||||
// 'freeData' determines if the underlying image data should also be freed.
|
||||
func (kic *KittyImageCleaner) DeleteByZIndex(zIndex int, freeData bool) string {
|
||||
params := map[string]string{
|
||||
"z": fmt.Sprintf("%d", zIndex),
|
||||
}
|
||||
if freeData {
|
||||
params["d"] = "Z"
|
||||
} else {
|
||||
params["d"] = "z"
|
||||
}
|
||||
return kic.buildCommand(params)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cleaner := NewKittyImageCleaner()
|
||||
|
||||
// Example usage:
|
||||
fmt.Println("Kitty Image Cleaning Commands:")
|
||||
fmt.Println("-------------------------------")
|
||||
|
||||
// <ESC>_Ga=d<ESC>\ # delete all visible placements
|
||||
fmt.Println("Delete all visible placements (no data free):",
|
||||
cleaner.DeleteAllVisiblePlacements(false))
|
||||
|
||||
// <ESC>_Ga=d,d=A<ESC>\ # delete all visible placements, freeing data
|
||||
fmt.Println("Delete all visible placements (with data free):",
|
||||
cleaner.DeleteAllVisiblePlacements(true))
|
||||
|
||||
// <ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
|
||||
fmt.Println("Delete image with ID 10 (no data free):",
|
||||
cleaner.DeleteByID(10, 0, false))
|
||||
|
||||
// <ESC>_Ga=d,d=I,i=10<ESC>\ # delete the image with id=10, freeing data
|
||||
fmt.Println("Delete image with ID 10 (with data free):",
|
||||
cleaner.DeleteByID(10, 0, true))
|
||||
|
||||
// <ESC>_Ga=d,d=i,i=10,p=7<ESC>\ # delete the image with id=10 and placement id=7, without freeing data
|
||||
fmt.Println("Delete placement 7 of image ID 10 (no data free):",
|
||||
cleaner.DeleteByID(10, 7, false))
|
||||
|
||||
// <ESC>_Ga=d,d=I,i=10,p=7<ESC>\ # delete the image with id=10 and placement id=7, freeing data
|
||||
fmt.Println("Delete placement 7 of image ID 10 (with data free):",
|
||||
cleaner.DeleteByID(10, 7, true))
|
||||
|
||||
// <ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the placements with z-index -1, also freeing up image data
|
||||
fmt.Println("Delete placements with z-index -1 (with data free):",
|
||||
cleaner.DeleteByZIndex(-1, true))
|
||||
|
||||
// <ESC>_Ga=d,d=z,z=0<ESC>\ # delete the placements with z-index 0, without freeing data
|
||||
fmt.Println("Delete placements with z-index 0 (no data free):",
|
||||
cleaner.DeleteByZIndex(0, false))
|
||||
|
||||
// <ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all placements that intersect the cell at (3, 4), without freeing data
|
||||
fmt.Println("Delete placements at cell (3,4) (no data free):",
|
||||
cleaner.DeleteByCellPosition(3, 4, false))
|
||||
|
||||
// <ESC>_Ga=d,d=P,x=5,y=6<ESC>\ # delete all placements that intersect the cell at (5, 6), freeing data
|
||||
fmt.Println("Delete placements at cell (5,6) (with data free):",
|
||||
cleaner.DeleteByCellPosition(5, 6, true))
|
||||
|
||||
fmt.Println("Delete placements intersecting cursor (no data free):",
|
||||
cleaner.DeleteByCursorPosition(false))
|
||||
|
||||
fmt.Println("Delete placements intersecting column 10 (with data free):",
|
||||
cleaner.DeleteByColumn(10, true))
|
||||
|
||||
fmt.Println("Delete placements intersecting row 5 (no data free):",
|
||||
cleaner.DeleteByRow(5, false))
|
||||
|
||||
fmt.Println("Delete images with ID range 100-200 (with data free):",
|
||||
cleaner.DeleteByIDRange(100, 200, true))
|
||||
|
||||
fmt.Println("Delete placements at cell (1,1) with z-index 10 (no data free):",
|
||||
cleaner.DeleteByCellAndZIndex(1, 1, 10, false))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user