Nog

preview

Nog is a tiling window manager for Windows 10, which uses lua as its scripting language.

Installation

Nog has a very useful powershell script which downloads a given release.

It basically just downloads the Nog.zip file from the assets, unzips it and moves it to the correct location.

Usage

./bin/download_release.ps1 <release_name>

So if you want to download the latest master release you would call the script like this:

./bin/download_release.ps1 master-release

It is also possible to run the script without having to clone the repo with this simple one-liner

(iwr "https://raw.githubusercontent.com/TimUntersberger/nog/master/bin/download_release.ps1").Content > download.ps1; ./download.ps1 master-release; rm download.ps1

The snippet those the following steps:

  1. Download the download_release.ps1 script locally as download.ps1
  2. Executes the script with master-release
  3. Removes the download.ps1 script

Quick Start

After installing nog I would recommend you to do the following steps:

  1. configure nog to fit your needs
  2. Execute start-process $("$env:APPDATA\nog\bin\nog.exe") to start nog
  3. Press Ctrl+Alt+W to enter work mode

When nog is started for the first time it creates an init.lua file in the config folder which contains the following configuration.

local direction_keys = {
  h = "left",
  j = "down",
  k = "up",
  l = "right"
}

-- Nog has 10 workspaces
local workspaces = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }

nog.config.bar.font_size = 18

nog.config.work_mode = false
-- draws the app bar while nog is in work mode
nog.config.display_app_bar = true
nog.config.launch_on_startup = true
nog.config.multi_monitor = true
-- hides the task bar while nog is in work mode
nog.config.remove_task_bar = true

-- We customize the first two workspaces with a custom display text
nog.config.workspaces = {
  [1] = {
    text = " Code "
  },
  [2] = {
    text = " Browser "
  }
}

nog.config.rules = {
  -- we want to ignore explorer.exe, because the user experience is better when in floating mode IMO
  ["explorer.exe"] = { 
    action = "ignore"
  },
  -- same thing here
  ["taskmgr.exe"] = { 
    action = "ignore"
  },
  -- same thing here
  ["snippingtool.exe"] = { 
    action = "ignore"
  },
  ["firefox.exe"] = {
    has_custom_titlebar = true,
    -- Adds special handling for firefox
    firefox = true
  },
  ["chrome.exe"] = {
    has_custom_titlebar = true,
    -- Adds special handling for chrome
    chromium = true
  },
  ["discord.exe"] = {
    has_custom_titlebar = true
  },
  ["spotify.exe"] = {
    has_custom_titlebar = true
  },
  ["code.exe"] = {
    has_custom_titlebar = true
  },
}

nog.nbind("alt+i", nog.win_ignore)
nog.nbind("alt+q", nog.win_close)
nog.nbind("alt+m", nog.win_minimize)
nog.nbind("alt+x", nog.quit)

-- nog.nbind_tbl will map each key to its value so writing the nog.nbind_tbl line is equal to the following
-- nog.nbind("alt+l", function() nog.ws_focus("Right") end)
-- nog.nbind("alt+k", function() nog.ws_focus("Up") end)
-- nog.nbind("alt+j", function() nog.ws_focus("Down") end)
-- nog.nbind("alt+h", function() nog.ws_focus("Left") end)
nog.nbind_tbl("alt", nog.ws_focus, direction_keys)
nog.nbind_tbl("alt+control", nog.ws_swap, direction_keys)

nog.nbind("alt+plus", function()
  nog.ws_set_split_direction("Vertical")
end)
nog.nbind("alt+minus", function()
  nog.ws_set_split_direction("Horizontal")
end)

nog.nbind("alt+control+f", nog.win_toggle_floating)
nog.gbind("alt+control+w", nog.toggle_work_mode)
nog.nbind("alt+f", nog.ws_toggle_fullscreen)

nog.nbind_tbl("alt+shift", nog.win_move_to_ws, workspaces)
nog.nbind_tbl("alt+control", nog.ws_move_to_monitor, workspaces)
nog.nbind_tbl("alt", nog.ws_change, workspaces)

Work mode

Work mode is when nog actively tries to manage windows.

Nog is intended to be used along Windows 10 instead of completely replacing it, that is why it is possible to "activate" and "deactivate" nog. People that use Windows for development usually use the same machine for gaming and can't be bothered to switch constantly. I am part of this group so I wanted to create a window manager that supports this use case.

Nog constantly saves a snapshot of the current workspace/window layout and when reentering the work mode it tries to recreate the layout. The default state can be configured by setting the work_mode setting. You can always leave/enter the work mode by calling the toggle_work_mode function.

Floating Mode

We describe windows that are not managed by nog as being in floating mode.

Fullscreen Mode

When a workspace is in fullscreen mode it only displays the window that has focus, everything else stays the same. If you want to disable focus changing while being in fullscreen mode you can set the ignore_fullscreen_actions to true (defaults to false).

Pinning Mode

Pinned windows float above all other windows in an "always-on-top" fashion. A program can be pinned or unpinned using win_toggle_pin or win_toggle_ws_pin to pin a window above all workspaces (globally) or just to a specific workspace respectively. Pinned programs' visibility can be toggled with toggle_view_pinned or ws_toggle_view_pinned to toggle either the globally pinned programs or workspace pinned programs respectively.

Introduction

The configuration lives in %APPDATA%/nog/config. At startup the program looks for a init.lua file in the root of this folder and executes it.

Nog comes with a preconfigured init.lua which aims to show you how you can build your own config. We try to explain everything as good as possible with comments in our config so you shouldn't have to look anything up. If you want to know what keybindings are available to you by default please look at the init.lua.

The configuration itself is written in lua and tries to have a similar style to neovim. Any lua code executed by nog has access to the global nog variable which contains a bunch of functions to make it easy to control nog programmatically.

Nog also includes luv and inspect to make development easier for the user.

If you ever want to look at the structure of a lua table you can easily print a very nice overview using the following statement

print(nog.inspect(tbl))

Settings

Below you can find a list of settings you can change and their description:

KeyValueDescription
min_heightNumberThe minimum height a window has to have so that it gets managed automatically
min_widthNumberThe minimum width a window has to have so that it gets managed automatically
inner_gapNumberThe gap between each tile
outer_gapNumberThe margin between workspace and the display
launch_on_startupBooleanStart when you start your computer
multi_monitorBooleanUse all monitors
work_modeBooleanStart in work mode
use_borderBooleanForce managed windows to draw a border. (This can help clarity)
light_themeBooleanChanges how the bar colors get generated to fit light colors
display_app_barBooleanEnable the bar
remove_title_barBooleanRemove the titlebar of managed windows
remove_task_barBooleanRemove the taskbar while the program is running
ignore_fullscreen_actionsBooleanIgnore grid-modifying keybindings (swap, focus, move, etc) while fullscreened

Examples

nog.config.min_height = 200
nog.config.min_width = 200
nog.config.light_theme = true

Keybindings

Nog keybindings can be of three different kinds:

  • Normal (n)
  • Work (w)
  • Global (g)

Normal keybindings are only active while being inside work mode and inside the current mode (or while not in any mode).

Work keybindings are only active while being inside work mode.

Global keybindings are always active as long as nog is running.

Nog comes with 4 different functions which can be used to define a new keybinding:

  • nog.bind(kind, key, cb)
  • nog.nbind(key, cb)
  • nog.wbind(key, cb)
  • nog.gbind(key, cb)

nog.bind expects to receive 3 arguments

  • kind which has to be either "n", "w" or "g"
  • key which has to be a valid key combination
  • cb which has to be a function

nbind, wbind and gbind just call the bind function with their kind and pass the given arguments.

nog.nbind("Alt+F1", function()
  print("Hello World!")
end)

Note that any keybinding currently active will swallow the keys, meaning that if you bind 1 you won't be able to type 1.

Key combos

A key combo is a string which can either have just a key or a list of modifiers followed by a key and separated by +.

Examples

  • 1
  • Alt+Enter
  • Alt+Control+T

Keys

  • A - Z
  • 0 - 9
  • F1 - F12
  • ,
  • .
  • Tab
  • Space
  • Enter
  • Plus
  • Minus
  • Escape
  • Backspace
  • Left
  • Up
  • Right
  • Down
  • OEM_1
  • OEM_2
  • OEM_3
  • OEM_4
  • OEM_5
  • OEM_6
  • OEM_7
  • OEM_8
  • OEM_102

Modifiers

  • Win
  • Shift
  • Control
  • Alt

Workspaces

Note: The monitor ids are counted from left to right and from top to bottom


Nog has 10 different workspaces. Each workspace can be customized.

When customizing a workspace you can change the following settings:

KeyValueDescription
monitorNumberId of the monitor this workspace resides on per default
textStringText to display instead of the id (can be unicode)

Example

nog.workspaces = {
  [1] = {
    text = "First Workspace"
  },
  [2] = {
    text = "Second Workspace",
    monitor = 1
  },
}

Modes

Modes in nog are an easy way to condionally reuse keybindings for different actions.

Defining a new mode is very easy

nog.mode("my mode", function()
  nog.nbind("L", function()
    print("L")
  end)
  nog.nbind("Escape", function()
    nog.toggle_mode("my mode")
  end)
end)

It is important to not forget a binding which will leave the mode else you will probably be stuck inside the mode until you restart nog.

Now to toggle a mode all you have to do is call nog.toggle_mode

nog.toggle_mode("my mode")

When entering a mode nog will unbind all normal keybindings and execute the provided cb. Leaving a mode will cause nog to unbind all normal keybindings again and afterwards rebind all normal keybindings that were defined previously.

Rules

Rules are used to add special handling to windows that match a regex pattern.

nog.rules = {
  ["notepad.exe"] = {
    ...
  }
}

The above snippet defines a rule for any window that either has a title or executable name matching notepade.exe.

A rule can contain the following settings

KeyValueDescription
has_custom_titlebarBooleanChanges how we align the window (applications like vscode should have this enabled)
actionStringTells nog how to behave when a new window is created. The value is expected to be one of the following:
  • Ignore - nog will ignore the window
  • Manage - nog will manage the window and add it to the currently active workspace
  • Pin - nog will globally pin the window (see pinning mode)
  • Validate - nog will check other config values to determine whether to manage or not
chromiumBooleanAdds chromium specific handling (anything based on chromium like the new microsoft edge should have this enabled)
firefoxBooleanAdds firefx specific handling
workspace_idNumberWhich workspace this window gets moved to

The default config contains a few useful rules if you want to see them in action.

Bar

The bar at the top of the screen when having display_app_bar enabled can display a lot of useful information.

It is possible to change the following settings

KeyValueDescription
fontStringThe font of the bar
font_sizeNumberThe font size of the bar
colorNumberThe color of the bar
componentsTableThe component layout of the bar
nog.config.bar.font_size = 20

Components

Components are the building blocks of the bar. The components table has the following properties:

  • left contains a list of components which are aligned to the left side of the bar
  • center contains a list of components which are aligned to the center of the bar
  • right contains a list of components which are aligned to the right side of the bar
nog.config.bar.components = {
  left = {},
  center = { nog.components.workspaces() },
  right = {}
}

The above snippet changes the layout so that the left side and right side are empty and the center part gets replaced by the workspaces overview.

Builtin

All of the builtin components can be found under nog.components.

  • datetime
  • workspaces
  • current_window
  • padding
  • fullscreen_indicator
  • split_direction
  • active_mode

Custom

A bar component is a table which has to have a name and render field.

The name can be any string of your chosing and is only used for debugging purposes.

render is the important part. It has to be a function which returns a list of components texts. A component text is a table with a required text field and optional fg, bg and value fields which take a number and change the colors of the component. The function also receives the current display id as argument so you can know which display the component is currently being rendered on.

It is common practice to define a component as a function to make it easy to add customization options later on.

local hello = function(name)
  return {
    name = "my_component",
    render = function()
      return {{ text = "Hello " .. name .. "!" }}
    end
  }
end

nog.config.bar.components = {
  left = {},
  center = { hello("User") },
  right = {}
}

A component can also have an on_click function. If a component has a valid on_click field, the cursor changes to a pointer when hovered over the rendered component. This function receives three arguments. The display_id where the component is rendered on, the value and index of the component text which was clicked.

-- The `counter` component tracks the amout of the times the component has been clicked globally.
local counter_value = 0
local counter = function(increment)
  return {
    name = "counter",
    render = function()
      return {{ text = counter_value }}
    end,
    on_click = function()
      counter_value = counter_value + increment
    end
  }
end

nog.config.bar.components = {
  left = {},
  center = { counter(1) },
  right = {}
}

Plugins

The plugins live the in the plugins folder located next to the config (ref).

You can install plugins either manually or using the provided API. Manually installing plugins is as easy as cloning a valid plugin into the plugins folder. This works because nog uses this folder as the plugins index. If you don't want to install the plugins manually you can use nog.plug_install.

nog.plug_install("GithubUser/NogPlugin")

This function will clone the repo if it hasn't been cloned yet.

You can update your plugins by calling nog.plug_update. This function will check for any new commits upstream and pull them if there are some. Uninstalling is as simple as removing its folder or calling nog.plug_uninstall.

nog.plug_uninstall("GithubUser/NogPlugin")

If you want to get a list of all installed plugins you can use the nog.plug_list functions which will return a list of absolute file paths.

Writing Plugins

Any repository that contains a top-level lua folder inside is a valid nog plugin. At startup nog will add the path to the lua folder to the package.path variable so you can require its content. This also means that you will have to namespace your lua files to not cause conflicts. Usually you would have a folder with your plugins name underneath the lua folder where all of your source lives.

Example Structure

  • README.md
  • lua
    • cool_plugin
      • init.lua

You can then use the plugin by requiring cool_plugin.

local x = require 'cool_plugin'

General

Note: keep in mind that all of the functions/variables in the api documentation are part of the nog global. So to use the below uv variable you will have to write nog.uv.

uv

lua bindings for libuv provided by luv. You can also require it directly by using require 'luv'.

version

A string that contains the nog version you are currently running. If you downloaded a vX.X.X release this will contain the version number, but if you downloaded a release of a branch like development this will contain the branch name together with the commit hash.

runtime_path

A string that contains the path to the runtime folder.

config_path

A string that contains the path to the nog folder.

config

A table that contains the configuration of nog.

quit()

Exits the nog process.

fmt_datetime(pattern)

Formats the local time using the provided pattern and returns it.

Arguments:

Return: a string that contains the local time formatted using the provided pattern.

launch(name)

Runs the executable with name in a subprocess.

Arguments:

  • name [string] name of the executable (ex. notepad.exe)

scale_color(color, factor)

Scales the color by factor. Useful for creating different shades of a color.

Arguments:

  • color [number] the color to be scaled
  • factor [number] how much the color gets scaled

Return: color scaled by factor.

toggle_work_mode()

Either leaves or enters work mode

inspect(value, [options])

The kikito/inspect.lua function.

Arguments:

  • value the value you want information on
  • options an optional table which can have the following fields
    • depth sets the maximum depth that will be printed out. When the max depth is reached, it will stop parsing tables and just return {...}
    • newline the string used to add a newline to each level of a table. (Default \n)
    • indent the string used to add an indent to each level of a table. (Default - two spaces)
    • process a function which allow altering the passed object before transforming it into a string. A typical way to use it would be to remove certain values so that they don't appear at all.

Return: a human readable representation of the lua value in a string.

get_focused_win()

Return: the window ID of the currently focused window (regardless of whether it's managed by nog or not)

toggle_view_pinned()

Toggles the visibility of all non-workspace specific pinned programs.

Window

get_win_title(win_id)

Returns the title of window with the given win_id

Arguments:

  • win_id [number] id of window

Return: [string] window title

get_current_win()

Returns the id of the window that has currently focus and is managed by nog.

Return: [number] window id

get_focused_win_of_display(display_id)

Returns the id of the window on the given display that is focused and is managed by nog.

Arguments:

  • display_id [number] id of display

Return: [number] window id

win_minimize()

Minimizes the currently focused window and unmanages it.

win_ignore()

Unmanages the currently focused window and adds a rule that prevents this window from being managed while nog is running.

win_close()

Closes the currently focused window.

win_toggle_floating()

Toggles floating mode of the currently focused window.

win_move_to_ws(ws_id)

Moves the currently focused window to the workspace with the provided ws_id.

Arguments:

  • ws_id [number] id of workspace

win_toggle_pin(win_id)

Pins or unpins the given window. This is a no-op if the window is currently tile-managed by nog.

Arguments:

  • win_id [number] id of window

win_toggle_ws_pin(win_id)

Pins or unpins the given window to the current workspace. This is a no-op if the window is currently tile-managed by nog. Note: when using multiple monitors only one workspace is focused. Ensure you're pinning the window to the correct workspace.

Arguments:

  • win_id [number] id of window

win_is_pinned(win_id)

Returns whether the given window is pinned or not.

Arguments:

  • win_id [number] id of window

Return: [bool] true if pinned, otherwise false

win_hide_title_bar(win_id)

Hides the title bar on the given window.

Arguments:

  • win_id [number] id of window

win_show_title_bar(win_id)

Shows the title bar on the given window.

Arguments:

  • win_id [number] id of window

win_hide_border(win_id)

Hides the border on the given window.

Arguments:

  • win_id [number] id of window

win_show_border(win_id)

Shows the border on the given window.

Arguments:

  • win_id [number] id of window

Workspace

get_active_ws_of_display(display_id)

Returns an array of ids of active workspaces of the display with display_id. An "active" workspace is a workspace that is managing windows.

Arguments:

  • display_id [number] id of display

Return: [array] ids of active workspaces

is_ws_focused(ws_id)

Checks if the workspace with the ws_id is focused.

Arguments:

  • ws_id [number] id of workspace

Return: [boolean] whether the workspace is focused

get_ws_info(ws_id)

Returns a table with various information about the workspace with the ws_id.

Arguments:

  • ws_id [number] id of workspace

Return: [table] information about workspace

  • id id of workspace
  • is_fullscreen whether the workspace is in fullscreen mode
  • is_empty whether the workspace is empty
  • split_direction in which direction a new window gets managed ("Vertical" or "Horizontal")
  • windows a list of window ids that are inside the workspace

get_current_ws()

Returns the id of the currently focused workspace.

Return: [number] id of workspace

get_ws_text(ws_id)

Returns the display text of the workspace with the ws_id.

Arguments:

  • ws_id [number] id of workspace

Return: [string] display text of workspace

ws_toggle_fullscreen()

Toggles the fullscreen mode of the current workspace.

ws_reset_row()

Resets any resizing done on the current row.

ws_reset_col()

Resets any resizing done on the current column.

ws_move_to_monitor(display_id)

Moves the current workspace to the display with the display_id.

Arguments:

  • display_id [number] id of display

ws_replace(ws_id)

Empties the workspace with the ws_id and moves the content of the current workspace into the given one.

Arguments:

  • ws_id [number] id of workspace

ws_change(ws_id)

Changes the focus from the current workspace to the workspace with the ws_id.

Arguments:

  • ws_id [number] id of workspace

ws_focus(direction)

Changes the focus from the current window to the next window in the direction.

Arguments:

  • direction [string] has to be one of the following:
    • Left
    • Up
    • Right
    • Down

ws_resize(direction)

Resizes either the row or the column by the given amount depending on the given direction.

Left | Right -> Column

Up | Down -> Row

Arguments:

  • direction [string] has to be one of the following:
    • Left
    • Up
    • Right
    • Down
  • amount [number] by how much to resize

ws_swap(direction)

Swaps the position of the current window with the next window in the direction.

Arguments:

  • direction [string] has to be one of the following:
    • Left
    • Up
    • Right
    • Down

ws_swap_columns_and_rows()

Turns all columns in the current workspace into rows and all rows into columns

ws_set_split_direction(direction)

Sets the split direction of the current workspace.

Arguments:

  • direction [string] has to be one of the following:
    • Vertical
    • Horizontal

ws_move_in(direction)

Moves the current window into the adjacent row/column/window found in the given direction.

  • If the adjecent item is a row or column, this simply moves the window to the end of the row or column.
  • If the adjacent item is a window, this introduces a new column or row container, whichever is the opposite of the current window's parent, and appends the window and the adjacent window within the new container.

Arguments:

  • direction [string] has to be one of the following:
    • Left
    • Up
    • Right
    • Down

ws_move_out(direction)

Moves the current window out of a row/column in the given direction. The behavior of this movement is essentially moving the current window so that it is a sibling of its parent and introducing a new parent node that is the opposite type of the previous parent if necessary.

Arguments:

  • direction [string] has to be one of the following:
    • Left
    • Up
    • Right
    • Down

ws_toggle_view_pinned(ws_id)

Toggles the visibility of all programs pinned to the current workspace.

Arguments:

  • ws_id [number] id of workspace

Plugin

plug_install(id)

Clones the github repository with the given id into the plugins folder. If a folder with the same name already exists, this function is noop.

Arguments:

  • id [string] id of repo (ex. TimUntersberger/nog)

plug_uninstall(id)

Removes the cloned repo locally.

Arguments:

  • id [string] id of repo (ex. TimUntersberger/nog)

plug_update()

Checks each folder inside the plugins folder for new commits upstream and if some exist it pulls them.

plug_list()

Returns a list of installed plugins.

Return: a list of absolute paths

Keybindings

bind(mode, key, cb)

Registers a new keybinding.

Arguments:

  • mode [string] one of the following:
    • n for normal
    • w for work
    • g for global
  • key [string] the key combination that activates this binding
  • cb [function] the function that gets called on keybinding activation

See Also:

nbind(key, cb)

Registers a new keybinding in normal mode.

Arguments:

  • key [string] the key combination that activates this binding
  • cb [function] the function that gets called on keybinding activation

See Also:

gbind(key, cb)

Registers a new keybinding in global mode.

Arguments:

  • key [string] the key combination that activates this binding
  • cb [function] the function that gets called on keybinding activation

See Also:

wbind(key, cb)

Registers a new keybinding in work mode.

Arguments:

  • key [string] the key combination that activates this binding
  • cb [function] the function that gets called on keybinding activation

See Also:

nbind_tbl(modifiers, cb, tbl)

Registers a new keybinding for each pair from the given tbl in normal mode, where the key of the pair is prepended with the modifiers and used as the key and the value gets passed to the callback when activated.

Arguments:

  • modifiers [string] the modifiers which will get prepended when registering the keybindings
  • cb [function] the function that gets called on keybinding activation
  • tb [table] the mappgins

wbind_tbl(modifiers, cb, tbl)

Registers a new keybinding for each pair from the given tbl in work mode, where the key of the pair is prepended with the modifiers and used as the key and the value gets passed to the callback when activated.

Arguments:

  • modifiers [string] the modifiers which will get prepended when registering the keybindings
  • cb [function] the function that gets called on keybinding activation
  • tb [table] the mappgins

gbind_tbl(modifiers, cb, tbl)

Registers a new keybinding for each pair from the given tbl in global mode, where the key of the pair is prepended with the modifiers and used as the key and the value gets passed to the callback when activated.

Arguments:

  • modifiers [string] the modifiers which will get prepended when registering the keybindings
  • cb [function] the function that gets called on keybinding activation
  • tb [table] the mappgins

unbind(key)

Unregisters the keybinding that has the given key.

Arguments:

  • key [string] the key combination that activates this binding

See Also:

Popup

Closes the current popup and opens a new one.

Arguments:

  • settings [table] popup settings
    • text [table] array of strings. Each item represents a separate line.
    • padding [number] the padding of the popup.

Closes the current popup.

Components

Component

A component is a table with the following fields:

  • name
  • render
  • on_click (optional)

workspaces()

Creates a component that displays the workspaces currently being used on this display. The workspace that has focus is highlighted.

Return: Component

datetime(pattern)

Creates a component that displays the current datetime formatted with the pattern.

Arguments:

Return: Component

padding(amount)

Creates a component that displays a space for amount.

Arguments:

  • amount [number] amount of spaces

Return: Component

active_mode()

Creates a component that displays either nothing or the active mode.

Return: Component

current_window(max_width)

Creates a component that displays either nothing or the title of the window that has focus.

Arguments:

  • max_width [number] the maximum width of the component

Return: Component

split_direction(values)

Creates a component that displays either the first item of values or the last one depending on the current split direction.

Arguments:

  • values [table] must have 2 items where the first one is for vertical and the second one for horizontal

Return: Component

fullscreen_indicator(indicator)

Creates a component that displays either nothing or the indicator if the workspace is in fullscreen mode.

Arguments:

  • indicator [string] the text to display

Return: Component