Nog
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:
- Download the
download_release.ps1
script locally asdownload.ps1
- Executes the script with
master-release
- Removes the
download.ps1
script
Quick Start
After installing nog I would recommend you to do the following steps:
- configure nog to fit your needs
- Execute
start-process $("$env:APPDATA\nog\bin\nog.exe")
to start nog - 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:
Key | Value | Description |
---|---|---|
min_height | Number | The minimum height a window has to have so that it gets managed automatically |
min_width | Number | The minimum width a window has to have so that it gets managed automatically |
inner_gap | Number | The gap between each tile |
outer_gap | Number | The margin between workspace and the display |
launch_on_startup | Boolean | Start when you start your computer |
multi_monitor | Boolean | Use all monitors |
work_mode | Boolean | Start in work mode |
use_border | Boolean | Force managed windows to draw a border. (This can help clarity) |
light_theme | Boolean | Changes how the bar colors get generated to fit light colors |
display_app_bar | Boolean | Enable the bar |
remove_title_bar | Boolean | Remove the titlebar of managed windows |
remove_task_bar | Boolean | Remove the taskbar while the program is running |
ignore_fullscreen_actions | Boolean | Ignore 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 combinationcb
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:
Key | Value | Description |
---|---|---|
monitor | Number | Id of the monitor this workspace resides on per default |
text | String | Text 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
Key | Value | Description |
---|---|---|
has_custom_titlebar | Boolean | Changes how we align the window (applications like vscode should have this enabled) |
action | String | Tells nog how to behave when a new window is created. The value is expected to be one of the following:
|
chromium | Boolean | Adds chromium specific handling (anything based on chromium like the new microsoft edge should have this enabled) |
firefox | Boolean | Adds firefx specific handling |
workspace_id | Number | Which 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
Key | Value | Description |
---|---|---|
font | String | The font of the bar |
font_size | Number | The font size of the bar |
color | Number | The color of the bar |
components | Table | The 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 barcenter
contains a list of components which are aligned to the center of the barright
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
- cool_plugin
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:
pattern
a string that follows the chrono spec
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 scaledfactor
[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 onoptions
an optional table which can have the following fieldsdepth
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. (Defaultprocess
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 workspaceis_fullscreen
whether the workspace is in fullscreen modeis_empty
whether the workspace is emptysplit_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 normalw
for workg
for global
key
[string] the key combination that activates this bindingcb
[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 bindingcb
[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 bindingcb
[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 bindingcb
[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 keybindingscb
[function] the function that gets called on keybinding activationtb
[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 keybindingscb
[function] the function that gets called on keybinding activationtb
[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 keybindingscb
[function] the function that gets called on keybinding activationtb
[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
popup_create(settings)
Closes the current popup and opens a new one.
Arguments:
settings
[table] popup settingstext
[table] array of strings. Each item represents a separate line.padding
[number] the padding of the popup.
popup_close()
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:
pattern
[string] a pattern for fmt_datetime
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