main.cpp represents the AWM program before compilation. It contains all the source code for the program.
424 lines
19 KiB
C++
424 lines
19 KiB
C++
/** awm - Alacritty Window Manager
|
|
|
|
Description:
|
|
A simple WM for organising Alacritty windows up to 4 defined-set of configurations.
|
|
|
|
Program is responsible for identifying all currently running instances of Alacritty
|
|
and organising them depending on the number of open windows. If no windows are open
|
|
when the program is called, the program simply finishes; otherwise, Alacritty windows
|
|
can take one-of-four different configuration layouts shown below.
|
|
|
|
|
|
Usage:
|
|
This program can be called using keybindings defined in an alacritty.yml file located
|
|
in "%APPDATA%\alacritty\alacritty.yml". An example keybinding might be as followed:
|
|
|
|
key_binds:
|
|
- { key: O, mods: Control|Shift, action: command: { program: "awm", args: [] } }
|
|
|
|
|
|
* [[Future]]: The user can fullscreen a Alacritty window by passing the `--fullscreen`
|
|
* argument to the program. An example keybinding might be as followed:
|
|
*
|
|
* key_binds:
|
|
* - { key: F, mods: Control|Shift, action: command: { program: "awm", args: ["--fullscreen"] } }
|
|
|
|
|
|
Disclaimer:
|
|
This program cannot organise once a new instance/window has been created for Alacritty.
|
|
Users must manually call the program to start the organisation of windows. This is simply
|
|
because there is no mechanism within this program to listen for when newly created Alacritty
|
|
instances/windows are made from within an existing Alacritty instance. This is why keybindings
|
|
in Alacritty are recommended to call this program to organise windows - and the keybinding
|
|
can be used in the new window (no window is biased).
|
|
|
|
|
|
Examples:
|
|
1 Window
|
|
----------
|
|
|
|
Desktop Work Area
|
|
====================================================================
|
|
# #
|
|
# #
|
|
# ++++++++++++++++++++++++++++++++++++++++++++++ #
|
|
# + + #
|
|
# + + #
|
|
# + + #
|
|
# + Alacritty.exe + #
|
|
# + + #
|
|
# + + #
|
|
# + + #
|
|
# ++++++++++++++++++++++++++++++++++++++++++++++ #
|
|
# #
|
|
# #
|
|
====================================================================
|
|
|
|
|
|
2 Windows
|
|
---------
|
|
|
|
Desktop Work Area
|
|
=====================================================================
|
|
# #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# + + + + #
|
|
# + + + + #
|
|
# + + + + #
|
|
# + + + + #
|
|
# + Alacritty.exe + + Alacritty.exe + #
|
|
# + + + + #
|
|
# + + + + #
|
|
# + + + + #
|
|
# + + + + #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# #
|
|
=====================================================================
|
|
|
|
|
|
3 Windows
|
|
---------
|
|
|
|
Desktop Work Area
|
|
=====================================================================
|
|
# #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# + + + + #
|
|
# + Alacritty.exe + + + #
|
|
# + + + + #
|
|
# +++++++++++++++++++++++++++++ + + #
|
|
# + Alacritty.exe + #
|
|
# +++++++++++++++++++++++++++++ + + #
|
|
# + + + + #
|
|
# + Alacritty.exe + + + #
|
|
# + + + + #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# #
|
|
=====================================================================
|
|
|
|
|
|
4 Windows
|
|
---------
|
|
|
|
Desktop Work Area
|
|
=====================================================================
|
|
# #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# + + + + #
|
|
# + Alacritty.exe + + Alacritty.exe + #
|
|
# + + + + #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# + + + + #
|
|
# + Alacritty.exe + + Alacritty.exe + #
|
|
# + + + + #
|
|
# +++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ #
|
|
# #
|
|
=====================================================================
|
|
|
|
|
|
All windows tiled are given an amount of padding against the desktop work area so
|
|
they don't take the full desktop work area space. Note, the "desktop work area" is
|
|
different from the resolution of the monitor. This area is the area which
|
|
applications will consume when maximised, thus it's the space which excludes the
|
|
taskbar. This means that regardless of the position of the taskbar, the window
|
|
manager will tile to utilise as much of the work area as possible, and centre to it
|
|
accordingly as well.
|
|
|
|
In all examples (exc. 1 window), windows are sized accordingly with equal amounts
|
|
of padding against the work area, and centred accordingly to their new sizes in the
|
|
work area. In the case of 1 window, the WM will resize it back to a default defined
|
|
macro which specifies the width and height of the window - can be changed when
|
|
compiling with `-DWIN_DEFAULT_SIZE_X n` and `-DWIN_DEFAULT_SIZE_Y m`, where n,m are
|
|
non-negative and non-zero integers.
|
|
|
|
|
|
TODO:
|
|
- Ensure that no window can be resized less than 0 (more important for macros if changed).
|
|
- Consider bug that Task Manager is a collected window when open, but only occurring when
|
|
optimisation is not -O3 at least.
|
|
- Implement a fullscreen feature for a single window - possible toggle to/from fullscreen.
|
|
*/
|
|
|
|
|
|
#include <cstring>
|
|
#include <regex>
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <winuser.h>
|
|
#include <Psapi.h>
|
|
#pragma comment(lib, "user32.lib")
|
|
|
|
// the module name of the executable
|
|
#define PROC_IMAGE_FILENAME "alacritty.exe"
|
|
// the default pixel size of Windows
|
|
#define WIN_DEFAULT_SIZE_X 1064
|
|
#define WIN_DEFAULT_SIZE_Y 560
|
|
|
|
/**
|
|
A structure which stores a collection of window handles and process IDs.
|
|
|
|
PIDs are stored to keep track if the enumeration over windows returns a
|
|
PID multiple times (NOTE: the same window might appear multiple times in
|
|
this process. Might be a bug, consider future research for potential opts.).
|
|
|
|
Struct also keeps count of how many window handles have been collected, so
|
|
the length of handle array doesn't require sizeof calculations.
|
|
*/
|
|
struct HandleCollector {
|
|
// create array to store collected handles (4 max instances allowed)
|
|
HWND* handles;
|
|
unsigned long* pids;
|
|
int count;
|
|
};
|
|
|
|
|
|
/** Organise all the current window instances of Alacritty.
|
|
|
|
Function takes a `HandleCollector`, which contains a pointer array of all handles
|
|
collected during enumeration of windows. Different configurations of organisation
|
|
of windows depends on the number of window handles collected, stored in `count` of
|
|
the `HandleCollector` struct.
|
|
|
|
Works by starting a defer window position process which allows for multiple windows
|
|
to be resized and repositioned simultaneously, creating a seamless transition of
|
|
tiling. Once all the windows have been deferred accordingly, the deferral is closed,
|
|
finally performing all the transitions.
|
|
|
|
[[Technical]]: The actual process is known as creating a multiple-window-position
|
|
structure, which is achieved with `BeginDeferWindowPos` to allocates memory for
|
|
this structure and returns a handle to said structure in memory. Any time `DeferWindowPos`
|
|
is called, the information about the changes of a window are then stored on the structure.
|
|
Once all deferrals have taken place, the `EndDeferWindowPos` is called with the handle
|
|
to complete the process and move all windows "simultaneously" by the information stored
|
|
in the structure.
|
|
|
|
|
|
@Return: a BOOL value if the defferal process was successful on all windows collected.
|
|
*/
|
|
static BOOL organiseWindows(HandleCollector* hc) {
|
|
// start a new defferal for simultaneous organisation of windows
|
|
HDWP defer = BeginDeferWindowPos(hc->count);
|
|
// get the current workspace area of the desktop
|
|
RECT workArea;
|
|
if ( !SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0) ) return FALSE;
|
|
// declare all needed parameters
|
|
int x, y, cx, cy;
|
|
HWND wh;
|
|
// create match for 4 different ways of organising windows
|
|
switch (hc->count) {
|
|
case 1: // recentre the single window
|
|
{
|
|
// initialise handler for window
|
|
wh = hc->handles[0];
|
|
// set the window's X,Y coordinates
|
|
x = (workArea.right - workArea.left)/2 - WIN_DEFAULT_SIZE_X/2;
|
|
y = (workArea.bottom - workArea.top)/2 - WIN_DEFAULT_SIZE_Y/2;
|
|
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x, y,
|
|
WIN_DEFAULT_SIZE_X, WIN_DEFAULT_SIZE_Y,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
break;
|
|
}
|
|
case 2: // arrange tiles as left|right
|
|
{
|
|
//printf("Organising first window.\n");
|
|
wh = hc->handles[0];
|
|
// set the window's width
|
|
cx = (workArea.right - workArea.left - 32) * 0.50f;
|
|
cy = (workArea.bottom - workArea.top) - 32;
|
|
|
|
x = (workArea.right - workArea.left) * 0.50f - cx;
|
|
y = (workArea.bottom - workArea.top) * 0.50f - cy * 0.50f;
|
|
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x-4, y, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
//printf("Organising second window.\n");
|
|
wh = hc->handles[1];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x+cx+4, y, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
break;
|
|
}
|
|
case 3: // arrange tiles as 1---2|3
|
|
{
|
|
//printf("Organising first window.\n");
|
|
wh = hc->handles[0];
|
|
// set the window's width
|
|
cx = (workArea.right - workArea.left - 32) * 0.50f;
|
|
cy = (workArea.bottom - workArea.top - 32) * 0.50f;
|
|
|
|
x = (workArea.right - workArea.left) * 0.50f - cx;
|
|
y = (workArea.bottom - workArea.top) * 0.50f - cy;
|
|
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x-4, y-4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
//printf("Organising second window.\n");
|
|
wh = hc->handles[1];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x-4, y+cy+4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
cy = (workArea.bottom - workArea.top) - 24;
|
|
y = (workArea.bottom - workArea.top) * 0.50f - cy * 0.50f;
|
|
|
|
//printf("Organising third window.\n");
|
|
wh = hc->handles[2];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x+cx+4, y, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
break;
|
|
}
|
|
case 4: // arrange tiles in quarts 1|2---3|4
|
|
{
|
|
//printf("Organising first window.\n");
|
|
wh = hc->handles[0];
|
|
// set the window's width
|
|
cx = (workArea.right - workArea.left - 32) * 0.50f;
|
|
cy = (workArea.bottom - workArea.top - 32) * 0.50f;
|
|
|
|
x = (workArea.right - workArea.left) * 0.50f - cx;
|
|
y = (workArea.bottom - workArea.top) * 0.50f - cy;
|
|
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x-4, y-4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
//printf("Organising second window.\n");
|
|
wh = hc->handles[1];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x+cx+4, y-4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
//printf("Organising third window.\n");
|
|
wh = hc->handles[2];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x-4, y+cy+4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
//printf("Organising second window.\n");
|
|
wh = hc->handles[3];
|
|
defer = DeferWindowPos(defer, wh, (HWND)0, x+cx+4, y+cy+4, cx, cy,
|
|
SWP_SHOWWINDOW | SWP_NOZORDER );
|
|
|
|
}
|
|
default: break; // there's nothing to organise if no windows
|
|
}
|
|
|
|
if ( !EndDeferWindowPos(defer) ) {
|
|
//printf("Failed to complete window deferral.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/** A callback function for `HwndCollector` during enumeration over windows.
|
|
|
|
This function performs the intensive work of correctly identifying if a
|
|
window running should be gathered by the collector. If the callback deems
|
|
to have identified a window to collect, it'll add the window's handle and
|
|
PID to their respectful arrays, and increment the counter of collected windows.
|
|
|
|
[[Technical]]: A callback is given an argument of type `LPARAM`, which can be
|
|
any type of object, cast in the function call, and cast back inside the callback.
|
|
A `HandleCollector` is given to the callback function to store all collected
|
|
window handles - this must be casted appropriately to the callback.
|
|
|
|
To identify if a window belongs to an Alacritty executable, simple regex is used
|
|
to identify if a module filename belongs to an Alacritty executable. `GetModuleFileNameEx`
|
|
returns a full path to the window's image; that is, given the window's PID, this can be
|
|
used to attain the full pathspec to the executable a window belongs to. By using
|
|
regex to search if the Alacritty binary name is part of the pathspec, windows that are
|
|
Alacritty can be identified and thus collected.
|
|
|
|
Finally, the length of the handle array can be checked before adding a newly
|
|
identified handle. If there's enough space, the handle is added; otherwise, if there
|
|
is no more space, the enumeration should halt (even if more instances exist). Returning
|
|
`FALSE` tells the enumerator to stop, and `TRUE` tells it to continue, but this never
|
|
needs to be checked in the location the callback function is called by the enumerator.
|
|
|
|
|
|
@Return: a BOOL identifying if the enumerator should continue iterating or finish.
|
|
*/
|
|
static BOOL CALLBACK HwndCollectorCallback( HWND hWnd, LPARAM lParam ) {
|
|
HandleCollector* hwnds = (HandleCollector*)lParam; // cast `lParam` back appropriately
|
|
TCHAR procMFN[32767]; // buffer to store the module filename
|
|
// setup regex pattern to see if a image filename is Alacritty
|
|
std::regex re(PROC_IMAGE_FILENAME);
|
|
std::cmatch m;
|
|
|
|
unsigned long pid; // store the PID of the window
|
|
GetWindowThreadProcessId(hWnd, &pid); // get the PID of the window by handle
|
|
// check that the handle isn't already in the buffer (Windows stuff, idk)
|
|
for (int i = 0; i < hwnds->count; i++) {
|
|
if (hwnds->pids[i] == pid) {
|
|
return TRUE; // move onto the next window in the enumerator
|
|
}
|
|
}
|
|
|
|
// create a handle for the PID we wish to query
|
|
HANDLE procH = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
|
// query the process for its image filename (e.g., "abc.exe")
|
|
GetModuleFileNameEx(procH, NULL, procMFN, sizeof(procMFN));
|
|
CloseHandle(procH); // make sure to close the handle once we've gotten the image filename
|
|
// check if the title of the image filename is one we are looking for
|
|
if ( std::regex_search(procMFN, m, re) ) {
|
|
// std::cout << "Found " << hWnd << "." << procMFN << ".\n";
|
|
// ensure that we have space to store a handle
|
|
if ( hwnds->count < 4 ) {
|
|
hwnds->handles[hwnds->count] = hWnd; // add it to the array of handles
|
|
hwnds->pids[hwnds->count] = pid; // add the PID to the array of PIDs
|
|
hwnds->count += 1; // incrememnt the counter
|
|
} else { return FALSE; } // indicate we have maxed out windows to tile
|
|
}
|
|
|
|
return TRUE; // enumerator should continue until no more windows are left, or array is full.
|
|
}
|
|
|
|
|
|
/** Collect all window handles of currently running instances of Alacritty.
|
|
|
|
Function takes a pointer to a `HandleCollector` which will store all the
|
|
window handles identified as Alacritty instances running. This is done by
|
|
enumerating over all windows currently running on the system, which is
|
|
given a callback function to perform additional operations on a window
|
|
handle picked up by the enumerator.
|
|
|
|
|
|
@Return: the number of windows gathered by the collector.
|
|
*/
|
|
static int HwndCollector( HandleCollector* hwnds ) {
|
|
// iterate over all the windows currently running on the system
|
|
// NOTE: the callback actually identifies the correct windows to collect.
|
|
EnumWindows(HwndCollectorCallback, (LPARAM)hwnds);
|
|
return hwnds->count; // return the number of handles added.
|
|
}
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// construct a new collector for windows to be gathered and organised
|
|
HandleCollector handles = {
|
|
.handles = new HWND[4],
|
|
.pids = new unsigned long[4],
|
|
.count = 0 };
|
|
|
|
// no windows were collected, nothing to organise
|
|
if ( HwndCollector(&handles) == 0 ) return 0;
|
|
// attempt to organise all windows collected
|
|
if ( !organiseWindows(&handles) ) {
|
|
//printf("There was an issue organising the current window(s).\n");
|
|
return 1;
|
|
}
|
|
|
|
// debug stuff
|
|
// for (int i = 0; i<handles.count; i++) {
|
|
// unsigned long pid;
|
|
// GetWindowThreadProcessId(handles.handles[i], &pid);
|
|
// std::cout << "Handle: " << handles.handles[i] << ". PID: " << pid << "\n";
|
|
// }
|
|
|
|
|
|
return 0;
|
|
}
|