a-simple-triangle / Part 7 - Setup Windows

In this article we are going to add in the Windows platform target to our existing workspace. This article almost never got written - I didn’t have a Windows machine that I used for development during most of the research for these articles. As I mentioned in part 1 of this series, in April 2019 I bought myself an Acer Predator Helios 500 Windows gaming laptop.

I bought it for a few reasons:

I still spend almost 100% of my day job using a Mac, and most of my home development is still done on my MacBook Air 2012, but I am really enjoying having the Helios 500 handy for not only gaming but as a very capable alternate development platform. And let’s face it - Visual Studio on Windows is pretty much the gold standard in terms of C++ development IDEs, so I very much had this series of articles in mind when deciding to buy the Windows machine.

The purpose of that ramble was to highlight that I would probably never have folded in the Windows platform into these articles if I hadn’t bought my Windows laptop. So let’s jump in and get our engine running on Windows!!


Prerequisite tooling

I am going to be making a couple of assumptions about your Windows developer machine, specifically:

Before continuing further you will need to install Microsoft Visual Studio 2019 - Community Edition. If you are a Microsoft developer you might have the full edition of Visual Studio which is fantastic, though only the community edition is necessary for what we are doing. Visit this link to download it: https://visualstudio.microsoft.com/vs/community. We will actually be using CMake via Visual Studio, so be sure to install 2019 or later - I’m not sure if or how well earlier versions of Visual Studio support CMake projects.

When installing Visual Studio, make sure to choose the following Workloads:

CRITICAL: Do not forget to install Linux development with C++!!! The option is somewhat hidden in the installer wizard - I missed it initially and was bashing my head against a wall for days trying to work out why Visual Studio would simply freeze on starting with a CMake project open. I took one for the team on this one - you have been warned!

Once you’ve installed Visual Studio with the workloads listed above we are ready to start creating our Windows platform.


Windows platform basics

To get started, create a new folder named windows as a sibling to our other platform targets:

root
  + project
    + windows

We will keep the theme of automated scripting going with our Windows platform however we no longer have a rich MacOS terminal at our disposal. If you have installed Git for Windows you will probably have Git Bash, however it is not a fully featured Linux environment. We will instead use the native scripting tools that ship with Windows itself. Historically we might have used Windows batch files (they end with .bat) but modern versions of Windows ship with another scripting environment provisioned through the PowerShell application.

CMake loves you!

You may have noticed that in the Visual Studio setup I stressed that the Linux development with C++ workload had to be installed. This may seem a bit odd considering we are on Windows now. There is a very good reason - it allows Visual Studio to open a folder containing a CMakeLists.txt file, which it then treats as a fully functional CMake project - allowing us to define our Windows project just like some of our CMake powered platforms. A lovely aspect of this approach is that it avoids the monolithic GUI driven project structure that a normal .sln Visual Studio project would have. By using a CMakeLists.txt file we never have to manually perform any kind of file sync when code is added to our main source folder - CMake will simply see any changes automatically. The integration into Visual Studio feels very similar to the experience in Visual Studio Code for our Mac console application.

Create a new empty CMakeLists.txt file in the windows folder and enter the following:

cmake_minimum_required(VERSION 3.4.1)

project(a-simple-triangle)

set(THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../third-party")
set(MAIN_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../main/src")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/out)

set(LIB_SDL2 ${THIRD_PARTY_DIR}/sdl-windows/lib/x64/SDL2.lib)
set(LIB_SDL2_MAIN ${THIRD_PARTY_DIR}/sdl-windows/lib/x64/SDL2main.lib)
set(LIB_GLEW ${THIRD_PARTY_DIR}/glew/lib/Release/x64/glew32s.lib)

include_directories(${THIRD_PARTY_DIR}/sdl-windows/include)
include_directories(${THIRD_PARTY_DIR}/glew/include)

file(GLOB_RECURSE CPP_HEADERS ${MAIN_SOURCE_DIR}/*.hpp)
file(GLOB_RECURSE CPP_SOURCES ${MAIN_SOURCE_DIR}/*.cpp)

# If we are generating a release build, then request that we produce a Windows desktop
# application executable by specifying WIN32 as an executable option. This avoids the
# console window from appearing when we launch the .exe file. However for debug builds,
# the console window makes it really easy to view logging output so we'll allow it by
# NOT specifying the WIN32 executable option.
if(CMAKE_BUILD_TYPE MATCHES "Release")
    set(EXECUTABLE_TYPE WIN32)
endif()

add_executable(
    a-simple-triangle ${EXECUTABLE_TYPE}
    ${CPP_HEADERS}
    ${CPP_SOURCES}
)

set_property(TARGET a-simple-triangle PROPERTY CXX_STANDARD 17)
set_property(TARGET a-simple-triangle PROPERTY CXX_STANDARD_REQUIRED ON)

target_link_libraries(
    a-simple-triangle
    opengl32
    ${LIB_GLEW}
    ${LIB_SDL2}
    ${LIB_SDL2_MAIN}
)

Quite a lot of this CMakeLists.txt file should feel very familiar - large parts of it are the same as for some of our other CMake platforms. Let’s step through it and I’ll highlight some of the more interesting parts.

Note: We haven’t yet written our setup script to download the third party dependencies - we’ll do that after we create the CMakeLists.txt file.

The first section is very similar to the other platforms, really we are just defining a couple of variables (THIRD_PARTY_DIR and MAIN_SOURCE_DIR) and declaring that we’d like the build output to appear in an out folder.

cmake_minimum_required(VERSION 3.4.1)

project(a-simple-triangle)

set(THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../third-party")
set(MAIN_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../main/src")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/out)

The next part is a bit interesting - for Windows builds we need to link .lib files when consuming external libraries. Often the .lib file is a stub representing another .dll file that is loaded at runtime, and other times the .lib file is a static library that is compiled into our code. To include the SDL2 third party library in our Windows platform we will actually need to use a Windows specific variant of SDL2 which includes the .lib and .dll files we need. The following two lines simply declare where in the file system to locate the .libs needed for SDL2:

set(LIB_SDL2 ${THIRD_PARTY_DIR}/sdl-windows/lib/x64/SDL2.lib)
set(LIB_SDL2_MAIN ${THIRD_PARTY_DIR}/sdl-windows/lib/x64/SDL2main.lib)

Note: For this series of articles, I’ll be using the 64 bit versions of third party libraries. You can see an example above where we are creating paths to the x64 variants of .lib files. If you need or want to perform 32 bit builds, you will have to use the 32 bit .lib files and include some sort of conditional compilation code for x64 vs x86.

There is one more third party library that we must link with for our Windows platform which is not used on our other platforms - the OpenGL Extension Wrangler library, or GLEW for short. For the Windows platform, we will use the static GLEW library, so the library code is compiled directly into our application instead of loading at runtime from a .dll.

set(LIB_GLEW ${THIRD_PARTY_DIR}/glew/lib/Release/x64/glew32s.lib)

We will add our include folders to our third party library sources. For Windows we will use the sdl-windows variant of SDL2 and additionally include the headers for the GLEW library. We will also add all our C++ source code via the file commands - similar to our other CMake platforms.

include_directories(${THIRD_PARTY_DIR}/sdl-windows/include)
include_directories(${THIRD_PARTY_DIR}/glew/include)

file(GLOB_RECURSE CPP_HEADERS ${MAIN_SOURCE_DIR}/*.hpp)
file(GLOB_RECURSE CPP_SOURCES ${MAIN_SOURCE_DIR}/*.cpp)

The next part is not strictly necessary but I found that showing a console window while the app is running can be very helpful to easily see logging output messages. Luckily this is the default behaviour however if you actually wanted to run the .exe file without a hideous console window popping up, we need to tell CMake about what EXECUTABLE_TYPE to produce. The value of WIN32 means that it should be presented as a desktop application, not a console application. So, if the current build is a "Release" build, we will set its type to produce a desktop application, otherwise leave it alone.

if(CMAKE_BUILD_TYPE MATCHES "Release")
    set(EXECUTABLE_TYPE WIN32)
endif()

The next couple of properties configure the C++ version we want to target.

set_property(TARGET a-simple-triangle PROPERTY CXX_STANDARD 17)
set_property(TARGET a-simple-triangle PROPERTY CXX_STANDARD_REQUIRED ON)

Finally, we define what to link into the executable - notice we are referring to the previously specified .lib references.

target_link_libraries(
    a-simple-triangle
    opengl32
    ${LIB_GLEW}
    ${LIB_SDL2}
    ${LIB_SDL2_MAIN}

Creating our setup script

Close the CMakeLists.txt file and create a new text file in the windows folder named setup.ps1. This will be our setup script that we will run through Windows PowerShell. We will write a set of commands to check for the required third party libraries and fetch them if they don’t exist. Conceptually it is the same thing we’ve done for the other platforms via the setup.sh and shared-scripts.sh files, though for Windows we won’t be sharing any scripts as there are no other Windows platforms in our solution (though we do have the Android on Windows setup scripts).

Edit the setup.ps1 file in text editor and enter the following:

# Don't allow our script to continue if any errors are observed
$ErrorActionPreference = "Stop"

# Check that we have a 'third-party' folder
Push-Location -Path "..\..\"
if (!(Test-Path "third-party")) {
    New-Item -ItemType Directory -Path "third-party"
}
Pop-Location

# Check that we have the SDL2 third party Windows dev library
if (!(Test-Path "..\..\third-party\sdl-windows")) {
    Write-Host "Downloading SDL Windows Dev library into third party folder sdl-windows ..."
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip", "..\..\third-party\SDL2-devel-2.0.9-VC.zip")

    Push-Location -Path "..\..\third-party"
        Write-Host "Unzipping SDL Windows Dev library into third-party\sdl-windows ..."
        cmd.exe /c 'tar -xf SDL2-devel-2.0.9-VC.zip'
        Move-Item -Path SDL2-2.0.9 -Destination sdl-windows
        Remove-Item -Path SDL2-devel-2.0.9-VC.zip
    Pop-Location
}

# Check that we have the GLEW third party library
if (!(Test-Path "..\..\third-party\glew")) {
    Write-Host "Downloading GLEW into third party folder glew ..."
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("https://github.com/nigels-com/glew/releases/download/glew-2.1.0/glew-2.1.0-win32.zip", "..\..\third-party\glew-2.1.0-win32.zip")

    Push-Location -Path "..\..\third-party"
        Write-Host "Unzipping GLEW library into third-party\glew ..."
        cmd.exe /c 'tar -xf glew-2.1.0-win32.zip'
        Move-Item -Path glew-2.1.0 -Destination glew
        Remove-Item -Path glew-2.1.0-win32.zip
    Pop-Location
}

So let’s take a look at the components in our setup script. First we will make sure that if any script errors happen that we stop - the default behaviour is to keep going (no idea why that’s the default…).

$ErrorActionPreference = "Stop"

Next we use the push / pop concept to navigate down and see if we have a third-party folder and if not, create one.

Push-Location -Path "..\..\"
if (!(Test-Path "third-party")) {
    New-Item -ItemType Directory -Path "third-party"
}
Pop-Location

The first third party library we will look for and fetch if necessary is SDL2. We will be fetching the Windows Developer version as it comes bundled with the necessary .lib and .dll files that we’ll need for linking and runtime. The System.Net.WebClient request behaves similarly to our wget commands for the other platforms and was explained in the Android for Windows article:

if (!(Test-Path "..\..\third-party\sdl-windows")) {
    Write-Host "Downloading SDL Windows Dev library into third party folder sdl-windows ..."
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip", "..\..\third-party\SDL2-devel-2.0.9-VC.zip")

    Push-Location -Path "..\..\third-party"
        Write-Host "Unzipping SDL Windows Dev library into third-party\sdl-windows ..."
        cmd.exe /c 'tar -xf SDL2-devel-2.0.9-VC.zip'
        Move-Item -Path SDL2-2.0.9 -Destination sdl-windows
        Remove-Item -Path SDL2-devel-2.0.9-VC.zip
    Pop-Location
}

Then we need to fetch the GLEW library if needed. Notice also that once these libraries have been fetched and unpacked into the third-party folder, their locations line up to the linked libraries we specified in the CMakeLists.txt file.

if (!(Test-Path "..\..\third-party\glew")) {
    Write-Host "Downloading GLEW into third party folder glew ..."
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("https://github.com/nigels-com/glew/releases/download/glew-2.1.0/glew-2.1.0-win32.zip", "..\..\third-party\glew-2.1.0-win32.zip")

    Push-Location -Path "..\..\third-party"
        Write-Host "Unzipping GLEW library into third-party\glew ..."
        cmd.exe /c 'tar -xf glew-2.1.0-win32.zip'
        Move-Item -Path glew-2.1.0 -Destination glew
        Remove-Item -Path glew-2.1.0-win32.zip
    Pop-Location
}

Close the setup.ps1 file, then start PowerShell and navigate into the windows folder. Try to run your setup script in PowerShell:

.\setup.ps1

Downloading SDL Windows Dev library into third party folder sdl-windows ...
Unzipping SDL Windows Dev library into third-party\sdl-windows ...
Downloading GLEW into third party folder glew ...
Unzipping GLEW library into third-party\glew ...

As I mentioned in the earlier Android article there is a fair chance that if you have not run a PowerShell script on your computer before, you will get an execution policy error. You can change the execution policy in PowerShell, but the other way to get around this problem is to enable Developer mode on your computer. To find the Developer mode option, go to your Windows settings -> Update & Security -> For developers. Enable the Developer mode radio choice then reboot your computer. After doing this your script should be able to run successfully.

When you have successfully run the setup.ps1 script, check that you have a third-party folder with the following children:

root
  + third-party
    + glew
    + sdl-windows

Open the code base already!

Now that we have the required third party libraries thanks to our setup script, and we have our CMakeLists.txt file ready to go, we can open Visual Studio and try to run our project. Start Visual Studio, then select Open a local folder.

Browse to our windows folder and click Select Folder. Visual Studio will open the folder and detect our CMake project - check the CMake output pane to see that our project has been synced.

We can switch the project view to the CMake view to show our C++ code base. Tap the small Toggle between Solution and Folder views button, next to the icon that looks like a house and choose CMake Targets View.

After toggling to the CMake Targets View your project pane should look a bit like this:

To run our project we need to choose a target. Tap the small drop down arrow on the Select Startup Item button and choose the a-simple-triangle.exe (....) option. Now tap the green play button to start building the application.


Build failed - fixing the issues

Our project will fail its build with a few different errors. A few of these errors will feel familiar as they are related to us not having added the windows enumeration to our platform code.

For example, one of the first errors I get looks like this:

... main\src\core\platform.cpp(17): error C4716: 'ast::getCurrentPlatform': must return a value

To fix this error we need to add our new windows platform to our platform.hpp and platform.cpp files. You can edit these files directly in Visual Studio if you like.

Open platform.hpp and update it like this:

#pragma once

namespace ast
{
    enum class Platform
    {
        mac,
        ios,
        android,
        emscripten,
        windows
    };

    Platform getCurrentPlatform();
} // namespace ast

Then edit platform.cpp and update it to include a clause to return the windows enum:

#include "platform.hpp"

ast::Platform ast::getCurrentPlatform()
{
#if defined(__EMSCRIPTEN__)
    return ast::Platform::emscripten;
#elif __APPLE__
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE
    return ast::Platform::ios;
#else
    return ast::Platform::mac;
#endif
#elif __ANDROID__
    return ast::Platform::android;
#elif WIN32
    return ast::Platform::windows;
#endif
}

Cool, now run the project again to get the next batch of errors. For me I have a series of OpenGL related errors, starting with one that looks like this:

... main\src\main.cpp(16): error C3861: 'glClearColor': identifier not found

You might suspect the problem - we need to update our graphics-wrapper.hpp to include a Windows specific condition:

#pragma once

#if defined(__EMSCRIPTEN__)
#include <GLES2/gl2.h>
#elif __APPLE__
#define GL_SILENCE_DEPRECATION
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE
#include <OpenGLES/ES2/gl.h>
#else
#include <OpenGL/gl3.h>
#endif
#elif __ANDROID__
#include <GLES2/gl2.h>
#elif WIN32
#define GLEW_STATIC
#include <GL/glew.h>
#endif

The important part is the new WIN32 condition, where we will include the GLEW library which helps us a great deal on Windows to get the OpenGL functionality working. The #define GLEW_STATIC is important because it tells the GLEW library that we will be using the static .lib instead of a .dll.

Run the app again - this time we shouldn’t get compilation errors, but we will get a dirty big runtime error that looks like this:

The reason for this error is that our a-simple-triangle.exe is compiled and generated into the out folder, and because we linked to the SDL2 library in our CMakeLists.txt file like this:

set(LIB_SDL2 ${THIRD_PARTY_DIR}/sdl-windows/lib/x64/SDL2.lib)

our executable is searching the out folder for the accompanying SDL2.dll to dynamically load. Obviously the SDL2.dll file is not there because we haven’t done anything to cause it to be there, hence the runtime crash. This problem can be solved by adding a post build step to our CMakeLists.txt file. We did the same thing for the Mac console platform to run some script after a build.

Create a new text file named cmake-post-build.ps1 in the windows folder and open it with a text editor with the following content:

$ErrorActionPreference = "Stop"

Push-Location -Path "out"
if (!(Test-Path "SDL2.dll")) {
    Copy-Item -Path "..\..\..\third-party\sdl-windows\lib\x64\SDL2.dll"
}
Pop-Location

This post build script navigates into the out folder then checks if there is a file named SDL2.dll. If the file isn’t there, it copies it from our third party sdl-windows folder. Save and close the script and edit the CMakeLists.txt file again so we can add in the post build command to run the new script:

add_custom_command(
    TARGET a-simple-triangle
    POST_BUILD
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    COMMAND PowerShell -File cmake-post-build.ps1
)

As you can see, we are just invoking the command PowerShell -File cmake-post-build.ps1 as a CMake post build step. Save and close the CMakeLists.txt. If you edited the CMakeLists.txt file in Visual Studio you will notice that upon saving it, the CMake project is automatically synced, however our post build script won’t be triggered until the next time a build happens due to a code change.

To ensure our post build script will run, select Build -> Rebuild All in Visual Studio to force it to recompile, therefore triggering our post build step. You should observe there is now an SDL2.dll file in the out folder.

Re-run our application in Visual Studio and we will be greeted with yet another error like so:

This error is related to the GLEW library. To use it correctly we have to not only include the library, but we must initialise it at the correct point in time. Because we haven’t initialised it yet, our OpenGL calls aren’t routing through it correctly. Initialising it is pretty straight forward - it must be done after we have obtained an OpenGL context but before we make any subsequent OpenGL invocations. Edit the main.cpp file and add a conditional block of code which invokes glewInit(); like so:

context = SDL_GL_CreateContext(window);

#ifdef WIN32
glewInit();
#endif

glClearDepthf(1.0f);

We need the conditional guard because we only included the GLEW library for the Windows platform. Run the application again and …

Hooray! We have our Windows application running successfully.


C++ code format style

Since we have a shared C++ code base that we could be editing on either Mac with Visual Studio Code, or Windows with Visual Studio we would like the auto formatting rules to be the same across both. We can instruct Visual Studio on Windows to use Clang formatting based on the same kind of rules we configured for Visual Studio Code earlier. If Visual Studio finds a file named .clang-format or _clang-format within the same folder structure as the source code, it will use it when formatting code. I managed to get this working but it required me to place a _clang-format file in the main/src folder otherwise Visual Studio did not detect it.

Create a new text file in the main/src folder named _clang-format with the following text.

Note: Be sure to remove the .txt file extension or it won’t work

BasedOnStyle: LLVM
AccessModifierOffset: -4
DerivePointerAlignment: false
PointerAlignment: Left
UseTab: Never
IndentWidth: 4
BreakBeforeBraces: Allman
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: true
ColumnLimit: 0
TabWidth: 4
NamespaceIndentation: All

Save and close the file, then open Visual Studio and within a source file, type the chords CTRL+K, followed by CTRL+D to trigger a format. Visual Studio will confirm that it found our code formatting rules and will use them.


A gift for Mac users

If you are like me and spend most of your time working on a Mac, you will have grown very used to the typical standard keyboard navigation commands. For example here are a few common commands that you would use on auto pilot:

Command Result
CMD + Left / Right Jump to beginning or end of current line
CMD + Up / Down Jump to top or bottom of the current document (depends on application sometimes)
OPTION + Left / Right Jump a word at a time left or right
CMD + Tab Cycle through running applications
CTRL + Left / Right Slide back and forth between virtual desktops
CMD + C Copy
CMD + V Paste
CMD + S Save

Additionally, the physical placement of the special keys is different on a Windows keyboard vs a Mac keyboard. On Windows the keys - at least on my machine - are ordered like this:

Ctrl - Fn - Windows - Alt

Whereas on a Mac they are like this:

Fn - Ctrl - Alt - Cmd

Jumping from my Mac onto my Windows laptop became really annoying as my muscle memory wanted to always press the Mac style keys. So I found myself constantly resizing windows or opening drop down menus when I was probably trying to simply move my cursor to the end of a line of code.

I also discovered that on my new Windows laptop the Home and End keys that you would normally use in Windows to jump to the start or end of a line of text were tucked at the top right of my keyboard and needed to be pressed with the Fn key held down. Although different Windows keyboards will have different configurations I suspect that most Windows laptops won’t place the Home and End keys in a sensible position for someone who does a lot of text entry - like a programmer. Needless to say it made programming somewhat infuriating and I felt a level of rage building inside me. So here is how I fixed this problem by morphing the keyboard on my Windows machine into something that feels a lot more like my Mac keyboard. After applying these tweaks my keyboard experience on my Windows machine is now surprisingly pleasant and I can switch back and forth to my Mac and not have to cope with much difference.

Step 1 - Key remapping at the registry level

We will use a program to remap some of our Windows keys like this:

Physical key Remapped behaviour
Ctrl Triggers Windows key
Windows Triggers Alt key
Alt Triggers Ctrl key

By doing this, we can press Alt + C etc in Windows, which mimics the same physical action as pressing CMD + C in Mac OS.

This stage of remapping needs to be done by editing the Windows registry. I used a tool named SharpKeys to do this. You can get SharpKeys here: https://github.com/randyrants/sharpkeys. The configuration you need to apply in this tool should look exactly like this:

Once you add those mapping rules in SharpKeys you will need to Write to Registry then reboot your computer for the changes to apply.

Step 2 - Key remapping at the macro level

Now that our physical keys have been remapped, we need to add some additional mapping to achieve the Alt + Left / Right and a few other behaviours.

To do this we will use another tool named AutoHotKey which you can find here: https://www.autohotkey.com. I’ve actually used AutoHotKey for years to remap keys while playing games. For example, in the original Dawn of War https://en.wikipedia.org/wiki/Warhammer_40,000:_Dawn_of_War - which is an awesome game - the keyboard controls to scroll the battle map were the arrow keys. I found that to be really awkward so I used AutoHotKey to remap the W, A, S, D keys to mimic the arrow keys. I was then able to scroll around using WASD instead.

The nice thing about AutoHotKey is that you don’t need to reboot your computer to apply its scripts. You can simply create or edit scripts and load / unload them at will in real time. We will create a new script that you can load whenever you intend to do programming work - though to be honest its probably useful all the time.

After you’ve installed AutoHotKey, create a new text file somewhere handy - we’ll name it MacKeys.ahk. Right click on the file and choose Edit Script to open in a text editor rather than running it.

I found a few helpful snippets from this conversation thread which I modified and refined a bit: https://autohotkey.com/board/topic/60675-osx-style-command-keys-in-windows.

Enter the following to give us the remaining key mapping, then save and close it:

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

; Command-backspace deletes whole line
#BS::Send {LShift down}{Home}{LShift Up}{Del}

; alt-delete deletes previous word
!BS::Send {LShift down}{LCtrl down}{Left}{LShift Up}{Lctrl up}{Del}

; Navigation of smaller chunks (skip word)
<!Left::Send {ctrl down}{Left}{ctrl up}
<!Right::Send {ctrl down}{Right}{ctrl up}

; Navigation using of bigger chunks (Skip to start/end of line/paragraph/document)
^Left::Send {Home}
^Right::Send {End}
!Up::Send {ctrl down}{Up}{ctrl up}
!Down::Send {ctrl down}{Down}{ctrl up}
^Up::Send {Lctrl down}{Home}{Lctrl up}
^Down::Send {Lctrl down}{End}{Lctrl up}

; Selection (uses a combination of the above with shift)
<!+Left::Send {ctrl down}{shift down}{Left}{shift up}{ctrl up}
<!+Right::Send {ctrl down}{shift down}{Right}{shift up}{ctrl up}
^+Left::Send {shift down}{Home}}{shift up}
^+Right::Send {shift down}{End}}{shift up}
!+Up::Send {ctrl down}{shift down}{Up}}{shift up}{ctrl up}
!+Down::Send {ctrl down}{shift down}{Down}}{shift up}{ctrl up}
^+Up::Send {Lctrl down}{shift down}{Home}}{shift up}{Lctrl up}
^+Down::Send {Lctrl down}{shift down}{End}}{shift up}{Lctrl up}

; Close current application
^q::Send {alt down}{F4 down}{alt up}{F4 up}

Double click the script so it is loaded in AutoHotKey - you will see the AutoHotKey icon in your status area and if you hover your cursor on it a tooltip will display showing what script is currently running. Take it for a test drive - you should now be able to navigate around in a very similar way to on your Mac.

Not everything is a 100% match but having these core basics working the same goes most of the way to keeping your sanity between operating systems.


Syncing CMake

If you want to add new source files from the file system into your Visual Studio project you will need to tell CMake to refresh itself so it can sync the file system. To force CMake to refresh itself, select:

Project -> Generate Cache for a-simple-triangle

You will see output a bit like this in the output window after:

1> CMake generation started.
1> CMake generation finished.

Git ignore

If you were to commit A Simple Triangle into version control, you would want to create a new .gitignore file for the windows folder, so it won’t include all the files that get auto generated by running the build. Here is a sample .gitignore that would achieve this for our project:

.vs
out

The code for this article can be found here.

In the next article we will refactor our C++ code base to clean it up a bit.

Continue to Part 8: Refactor foundation.

End of part 7