Building Workspace Switcher for MX Master 3
Building Workspace Switcher for MX Master 3
The Idea
Basically, I was annoyed with switching different applications with TAB, Also have used Mac before so Working on different workspaces was easy to manage.
I already used Logitech Mx Master 3 and looked at it’s horizontal scroll wheel so thought I would try to build a workspace switcher using horizontal scroll wheel.
Let’s Build It
I use ubuntu 22.04 with X11 as my window manager.
Quickly went to libX11 to find out how to change workspace.
For getting events for horizontal scroll I used input_event for input.h
header file.
// event0 is input_event file
char fd = open("/dev/input/event0", O_RDONLY);
if((fd == -1) {
perror("opening device");
exit(EXIT_FAILURE);
}
int event_count = 0;
while(read(fd, &ie, sizeof(struct input_event))) {
// Read the event
}
The Code
common.h
header file
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <linux/input.h>
#include <fcntl.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#define AnyPropertyType 0L /* special Atom, passed to GetProperty */
#define WRAP(_var, _from, _to) do { \
if (*_var == (_from)) { \
*_var = (_to); \
return; \
} \
} while (0)
void map_init(Display *dpy);
void map(int *x, int *y);
map_rectangular.c
file
#include "common.h"
/* This file provides a map which assumes the entire
rectangular display area is viewable. */
static int width, height;
void map_init(Display *dpy) {
int screen = DefaultScreen(dpy);
width = DisplayWidth (dpy, screen);
height = DisplayHeight(dpy, screen);
}
void map(int *x, int *y) {
WRAP(x, 0, width-2);
WRAP(x, width-1, 1);
WRAP(y, 0, height-2);
WRAP(y, height-1, 1);
}
main.c
file
int main(int argc, char *argv[])
{
int fd;
assert(argc==2);
struct input_event ie;
assert(strncmp(argv[1], "/dev/input/event", 16)==0);
if((fd = open(argv[1], O_RDONLY)) == -1) {
perror("opening device");
exit(EXIT_FAILURE);
}
int event_count = 0;
while(read(fd, &ie, sizeof(struct input_event))) {
if (ie.type == EV_REL && (ie.code == REL_HWHEEL || ie.code == REL_HWHEEL_HI_RES)) {
event_count++;
if (event_count < 3) {
continue;
}
event_count = 0;
if (ie.code == REL_HWHEEL_HI_RES){
Display *dpy = XOpenDisplay(NULL);
if (dpy == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(EXIT_FAILURE);
}
Window root = DefaultRootWindow(dpy);
Atom atom = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned long *data = NULL;
int status = XGetWindowProperty(dpy, root, atom, 0, 1, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **)&data);
if (status != Success) {
fprintf(stderr, "Cannot get _NET_CURRENT_DESKTOP property\n");
exit(EXIT_FAILURE);
}
int current_desktop = data[0];
XEvent xev;
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = root;
xev.xclient.message_type = atom;
xev.xclient.format = 32;
if (ie.value > 0)
{
xev.xclient.data.l[0] = current_desktop - 1;
}
else
{
xev.xclient.data.l[0] = current_desktop + 1;
}
xev.xclient.data.l[1] = 0;
XSendEvent(dpy, root, False, SubstructureNotifyMask, &xev);
XFlush(dpy);
XFree(data);
XCloseDisplay(dpy);
printf("Switching to desktop %ld from %d\n", xev.xclient.data.l[0], current_desktop);
}
}
}
return 0;
}
Contributing
The code is open source and you can contribute to it by creating an issue or a pull request. The Github repository is the place to go to.