Added awm main file
main.cpp represents the AWM program before compilation. It contains all the source code for the program.
This commit is contained in:
parent
fec223b975
commit
7b377f1f9e
423
main.cpp
Normal file
423
main.cpp
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
/** 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user