Skip to content

Commit

Permalink
request: write at maximum the size of Hyprland buffer
Browse files Browse the repository at this point in the history
This removes the old MAX_COMMANDS hack and fixes a bunch of issues in
hyprtabs.
  • Loading branch information
thiagokokada committed Jul 24, 2024
1 parent 77c5b99 commit 73f38ea
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 35 deletions.
101 changes: 76 additions & 25 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,92 @@ import (
"github.com/thiagokokada/hyprland-go/internal/assert"
)

const (
BUF_SIZE = 8192
MAX_COMMANDS = 30
)
const BUF_SIZE = 8192

func prepareRequest(buf *bytes.Buffer, command string, param string) int {
buf.WriteString(command)
buf.WriteString(" ")
buf.WriteString(param)
buf.WriteString(";")

func prepareRequests(command string, params []string) (requests []RawRequest) {
return buf.Len()
}

func prepareRequests(command string, params []string) (requests []RawRequest, err error) {
if command == "" {
// Panic since this is not supposed to happen, i.e.: only by
// misuse since this function is internal
panic("empty command")
}
switch len(params) {
case 0:
if len(command) >= BUF_SIZE {
return nil, fmt.Errorf(
"command is too long (%d>%d): %s",
BUF_SIZE,
len(command),
command,
)
}
requests = append(requests, []byte(command))
case 1:
requests = append(requests, []byte(command+" "+params[0]))
request := command + " " + params[0]
if len(request) >= BUF_SIZE {
return nil, fmt.Errorf(
"command is too long (%d>%d): %s",
BUF_SIZE,
len(request),
request,
)
}
requests = append(requests, []byte(request))
default:
// Hyprland IPC has a hidden limit for commands, so we are
// splitting the commands in multiple requests if the user pass
// more commands that it is supported
var buf bytes.Buffer
for i := 0; i < len(params); i += MAX_COMMANDS {
end := i + MAX_COMMANDS
if end > len(params) {
end = len(params)
}

buf.Reset()
buf.WriteString("[[BATCH]]")
for j := i; j < end; j++ {
buf.WriteString(command)
buf.WriteString(" ")
buf.WriteString(params[j])
buf.WriteString(";")
const batch = "[[BATCH]]"
// Add [[BATCH]] to the buffer
buf.WriteString(batch)
// Initialise current length of buffer
curLen := buf.Len()

for _, param := range params {
// Get the current command + param length
cmdLen := len(command) + len(param) + 2 // ; + <space>
if len(batch)+cmdLen >= BUF_SIZE {
// If batch + command + param length is bigger
// than BUF_SIZE, return an error since it will
// not fit the socket
return nil, fmt.Errorf(
"command is too long (%d>%d): %s %s",
cmdLen,
BUF_SIZE,
command,
param,
)
} else if curLen+cmdLen < BUF_SIZE {
// If the current length of the buffer +
// command + param is less than BUF_SIZE, the
// request will fit
curLen = prepareRequest(&buf, command, param)
} else {
// If not, we will need to split the request,
// so append current buffer contents to the
// requests array
requests = append(requests, buf.Bytes())

// Reset the current buffer and add [[BATCH]]
buf.Reset()
buf.WriteString(batch)

// And finally, add the contents of the request
// to the buffer
curLen = prepareRequest(&buf, command, param)
}

requests = append(requests, buf.Bytes())
}
// Append any remaining buffer content to requests array
requests = append(requests, buf.Bytes())
}
return requests
return requests, nil
}

func (c *RequestClient) validateResponse(params []string, response RawResponse) error {
Expand Down Expand Up @@ -100,7 +148,10 @@ func unmarshalResponse(response RawResponse, v any) (err error) {
}

func (c *RequestClient) doRequest(command string, params ...string) (response RawResponse, err error) {
requests := prepareRequests(command, params)
requests, err := prepareRequests(command, params)
if err != nil {
return nil, fmt.Errorf("error while preparing request: %w", err)
}

var buf bytes.Buffer
for _, req := range requests {
Expand Down
45 changes: 35 additions & 10 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,59 @@ func TestPrepareRequests(t *testing.T) {
}
for _, tt := range tests {
t.Run(fmt.Sprintf("tests_%v-%v", tt.command, tt.params), func(t *testing.T) {
requests := prepareRequests(tt.command, tt.params)
requests, err := prepareRequests(tt.command, tt.params)
assert.NoError(t, err)
for i, e := range tt.expected {
assert.Equal(t, string(requests[i]), e)
}
})
}
}

func TestPrepareRequestsMass(t *testing.T) {
// test massive amount of parameters
massTests := []struct {
tests := []struct {
command string
params []string
expected int
}{
{"command", genParams("param", 5), 1},
{"command", genParams("param", 15), 1},
{"command", genParams("param", 30), 1},
{"command", genParams("param", 60), 2},
{"command", genParams("param", 90), 3},
{"command", genParams("param", 100), 4},
{"command", genParams("very big param list", 1), 1},
{"command", genParams("very big param list", 50), 1},
{"command", genParams("very big param list", 100), 1},
{"command", genParams("very big param list", 500), 2},
{"command", genParams("very big param list", 1000), 4},
{"command", genParams("very big param list", 5000), 18},
{"command", genParams("very big param list", 10000), 35},
}
for _, tt := range massTests {
for _, tt := range tests {
t.Run(fmt.Sprintf("mass_tests_%v-%d", tt.command, len(tt.params)), func(t *testing.T) {
requests := prepareRequests(tt.command, tt.params)
requests, err := prepareRequests(tt.command, tt.params)
assert.NoError(t, err)
assert.Equal(t, len(requests), tt.expected)
})
}
}

func TestPrepareRequestsError(t *testing.T) {
_, err := prepareRequests(
strings.Repeat("c", BUF_SIZE),
nil,
)
assert.Error(t, err)

_, err = prepareRequests(
strings.Repeat("c", BUF_SIZE-len("p")),
genParams("p", 1),
)
assert.Error(t, err)

_, err = prepareRequests(
strings.Repeat("c", BUF_SIZE-len("[[BATCH]]"+"p")),
genParams("p", 5),
)
assert.Error(t, err)
}

func TestValidateResponse(t *testing.T) {
// Dummy client to allow this test to run without Hyprland
c := DummyClient{}
Expand Down

0 comments on commit 73f38ea

Please sign in to comment.