Skip to content

Instantly share code, notes, and snippets.

@Enr1g
Last active September 24, 2023 20:52
Show Gist options
  • Save Enr1g/f953a939805b06427bcad33b45d937f9 to your computer and use it in GitHub Desktop.
Save Enr1g/f953a939805b06427bcad33b45d937f9 to your computer and use it in GitHub Desktop.
Simple program (faster C version and more readable Python version) to move Guake to the top left corner of the active monitor in i3.
// gcc -Wall -lXrandr -lX11 -lxdo top_left.c -o top_left
#include "i3/ipc.h"
#include "X11/extensions/Xrandr.h"
#include "X11/Xlib.h"
#include "xdo.h"
#include "stddef.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
int inside(int mx, int my, int x, int y, int width, int height) {
return mx >= x && mx <= x + width && my >= y && my <= y + height;
}
void get_mouse_location(int *mx, int *my) {
xdo_t *xdo = xdo_new(NULL);
xdo_get_mouse_location(xdo, mx, my, NULL);
xdo_free(xdo);
}
typedef struct {
void *buf;
size_t size;
} i3_msg_t;
i3_msg_t encode_i3_msg_with_body(const void *body, size_t body_size) {
i3_ipc_header_t header = { I3_IPC_MAGIC, 0, 0 };
i3_msg_t msg;
header.size = body_size;
header.type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
msg.size = sizeof(header) + body_size;
msg.buf = calloc(msg.size, sizeof(char));
memcpy(msg.buf, &header, sizeof(header));
memcpy(msg.buf + sizeof(header), body, body_size);
return msg;
}
void free_i3_msg(i3_msg_t msg) {
free(msg.buf);
}
int i3_ipc_send(int sockfd, uint32_t type, void *msg_body, size_t msg_body_size) {
i3_msg_t msg = encode_i3_msg_with_body(msg_body, msg_body_size);
printf("Sending data...\n");
ssize_t rc = send(sockfd, msg.buf, msg.size, 0);
free_i3_msg(msg);
return rc;
}
int i3_ipc_recv(int sockfd, i3_ipc_header_t *header, void **msg_body) {
printf("Waiting to recieve data...\n");
int rc = recv(sockfd, header, sizeof(i3_ipc_header_t), 0);
if (rc != sizeof(i3_ipc_header_t)) {
perror("Error receiving header: ");
printf("Received only %d bytes of %lu\n", rc, sizeof(i3_ipc_header_t));
return -1;
}
if (strncmp(header->magic, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
printf("Wrong magic in ipc response: %6s. Something went terribly wrong\n", header->magic);
return -1;
}
if (header->size == 0) {
*msg_body = NULL;
return rc;
}
*msg_body = calloc(header->size, sizeof(char));
rc = recv(sockfd, *msg_body, header->size, 0);
if (rc != header->size) {
perror("Error receiving msg body: ");
printf("Received only %d bytes of %lu\n", rc, sizeof(i3_ipc_header_t));
return -1;
}
return rc;
}
void send_command(const char* command) {
const char *client_socket_path = "/tmp/.top_left.i3-ipc.sock";
const char *i3_socket_path = getenv("I3SOCK");
if (i3_socket_path == NULL) {
puts("I3SOCK is NULL");
return;
}
int client_sock, rc, len;
struct sockaddr_un server_sockaddr;
struct sockaddr_un client_sockaddr;
memset(&server_sockaddr, 0, sizeof(struct sockaddr_un));
memset(&client_sockaddr, 0, sizeof(struct sockaddr_un));
client_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_sock == -1) {
perror("Socket error: ");
return;
}
client_sockaddr.sun_family = AF_UNIX;
strcpy(client_sockaddr.sun_path, client_socket_path);
len = sizeof(client_sockaddr);
unlink(client_socket_path);
rc = bind(client_sock, (struct sockaddr *) &client_sockaddr, len);
if (rc == -1){
perror("Bind error: ");
close(client_sock);
return;
}
server_sockaddr.sun_family = AF_UNIX;
strcpy(server_sockaddr.sun_path, i3_socket_path);
rc = connect(client_sock, (struct sockaddr *) &server_sockaddr, len);
if(rc == -1){
perror("Connect error: ");
close(client_sock);
return;
}
rc = i3_ipc_send(client_sock, I3_IPC_MESSAGE_TYPE_RUN_COMMAND, (void *) command, strlen(command));
if (rc == -1) {
perror("Send error: ");
close(client_sock);
return;
} else {
printf("Data sent!\n");
}
i3_ipc_header_t response_header;
char *response_msg_body;
rc = i3_ipc_recv(client_sock, &response_header, (void **) &response_msg_body);
if (rc == -1) {
perror("Recv error: ");
close(client_sock);
return;
} else {
printf("Msg type = %d; data = %.*s\n", response_header.type, response_header.size, response_msg_body);
}
close(client_sock);
return;
}
int main() {
int mx, my;
get_mouse_location(&mx, &my);
Display *dpy = XOpenDisplay(NULL);
Window window = DefaultRootWindow(dpy);
XRRScreenResources *sr = XRRGetScreenResourcesCurrent(dpy, window);
for (int i = 0; i < sr->ncrtc; ++i) {
XRRCrtcInfo *ci = XRRGetCrtcInfo(dpy, sr, sr->crtcs[i]);
if (ci->noutput == 0)
continue;
if (ci->noutput > 1) {
printf("Got multiple outputs for one display: %d\n", ci->noutput);
}
XRROutputInfo *oi = XRRGetOutputInfo(dpy, sr, ci->outputs[0]);
printf("Trying monitor %s\n", oi->name);
if (inside(mx, my, ci->x, ci->y, ci->width, ci->height)) {
printf("Mouse (%d, %d) in %s\n", mx, my, oi->name);
char format[] = "[class=\"Guake\"] move position %d px %d px";
char cmd[sizeof(format) + 1024]; // Come on, who has display that big?!
snprintf(cmd, sizeof(cmd), format, ci->x, ci->y);
printf("Command: %s\n", cmd);
send_command(cmd);
break;
}
}
XCloseDisplay(dpy);
return 0;
}
#!/usr/bin/env python
import subprocess
from dataclasses import dataclass
import re
GREP = re.compile(r"^(?P<name>[^ ]+) .* (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+).*$")
@dataclass
class Monitor:
name: str
width: int
height: int
x: int
y: int
def contains(self, x, y):
return self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height
def main():
mx, my = None, None
output = subprocess.check_output(["xdotool", "getmouselocation", "--shell"]).decode().split("\n")
for line in output:
line = line.split("=")
match line[0]:
case "X":
mx = int(line[1])
case "Y":
my = int(line[1])
print(f"mx = {mx}, my = {my}")
if mx is None or my is None:
return
lines = subprocess.check_output(["xrandr", "--current"]).decode().split("\n")
lines = filter(lambda line: " connected " in line, lines)
for line in lines:
gd = GREP.match(line).groupdict()
print(f"Detected: {gd}")
monitor = Monitor(name=gd["name"], width=int(gd["width"]), height=int(gd["height"]), x=int(gd["x"]), y=int(gd["y"]))
if monitor.contains(mx, my):
print(f"Current monitor: {monitor.name}")
print(
subprocess.check_output(["i3-msg", f'[class="Guake"] move position {monitor.x} px {monitor.y} px']).decode()
)
break
else:
print("Impossible...")
if __name__ == '__main__':
main()
@Enr1g
Copy link
Author

Enr1g commented Sep 24, 2023

C version

Compile binary:

gcc -Wall -lXrandr -lX11 -lxdo top_left.c -o top_left

Add this line to your i3 config:

for_window [class="Guake"] exec --no-startup-id /path/to/top_left

Python version

Add this line to your i3 config:

for_window [class="Guake"] exec --no-startup-id python /path/to/top_left.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment