diff --git a/docs/17-porting-tips.md b/docs/17-porting-tips.md
new file mode 100644
index 000000000..ede354c2e
--- /dev/null
+++ b/docs/17-porting-tips.md
@@ -0,0 +1,341 @@
+# Porting tips
+
+## From 3.5 to 4.0
+
+The
+**best advice is to start from the default `rc.lua` and backport any changes**
+you might have added to your `rc.lua`.
+This avoids most of the possible errors due to missing important changes.
+
+If you still wish to ignore this advice, first read the
+NEWS section about the breaking changes. This
+document assumes you did.
+
+Be warned, even if it looks like it's working after changing some lines, **it wont
+supports dynamic screen changes and will have many subtle bugs**. The changes
+mentioned below **are important for the stability of your window manager**.
+
+Here is a diff of the 3.5.9 `rc.lua` with the 4.0 one. All changes due to
+new features and new syntaxes have been removed. A `-` in front of the line
+correspond to content of the 3.5 `rc.lua` and `+` its replacement in 4.0.
+
+This document does not cover the new features added in the Awesome 4 `rc.lua`,
+it only covers the minimal required changes to have a properly behaving config.
+
+To test during the port, we recommend the `Xephyr` X11 server.
+
+If Awesome4 **is not installed yet**, we recommand to install it in its own
+prefix to avoid conflicts in case you wish to stay on 3.5 for a little
+longer:
+
+ cd path/to/awesome/code
+ mkdir -p $HOME/.config/awesome4
+ mkdir build -p
+ cd build
+
+ # Install in a local folder, this assumes all the dependencies are installed
+ cmake -DCMAKE_INSTALL_PREFIX=$HOME/awesome4_test ..
+ make -j4
+
+ # Use a copy of rc.lua to avoid it being overwritten accidentally
+ cp awesomerc.lua $HOME/.config/awesome4/rc.lua
+
+ make install
+
+ # Start Awesome in a 1280x800 window
+ Xephyr :1 -screen 1280x800 &
+ DISPLAY=:1 $HOME/awesome4_test/bin/awesome \
+ -c $HOME/.config/awesome4/rc.lua \
+ --search $HOME/awesome4_test/share/awesome/lib
+
+If Awesome 4 is **already installed**, then backup your old `rc.lua` and overwrite
+`~/.config/awesome/rc.lua` (replace this by another path if you use a custom
+XDH config local directory). And only execute:
+
+ Xephyr :1 -screen 1280x800 &
+ DISPLAY=:1 awesome
+
+Screens are now added and removed without reloading `rc.lua`. The wallpaper are
+now set in a signal callback.
+
+ --- {{{ Wallpaper
+ -if `beautiful.wallpaper` then
+ - for s = 1, screen.count() do
+ - `gears.wallpaper.maximized`(beautiful.wallpaper, s, true)
+ - end
+ -end
+ --- }}}
+
+ +local function set_wallpaper(s)
+ + -- Wallpaper
+ + if `beautiful.wallpaper` then
+ + local wallpaper = `beautiful.wallpaper`
+ + -- If wallpaper is a function, call it with the screen
+ + if type(wallpaper) == "function" then
+ + wallpaper = wallpaper(s)
+ + end
+ + `gears.wallpaper.maximized`(wallpaper, s, true)
+ + end
+ +end
+ +
+ +-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
+ +`screen.connect_signal`("property::geometry", set_wallpaper)
+
+Tags need to be created for each screens, the old static initialization cannot
+work. Remove this section.
+
+ --- {{{ Tags
+ --- Define a tag table which hold all screen tags.
+ -tags = {}
+ -for s = 1, `screen.count`() do
+ - -- Each screen has its own tag table.
+ - tags[s] = awful.tag({ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, s, layouts[1])
+ - end
+ --- }}}
+
+The textclock is now part of the `wibox` library, rename it.
+
+ -- {{{ Wibar
+ -- Create a textclock widget
+ -mytextclock = `awful.widget.textclock`()
+ +mytextclock = `wibox.widget.textclock`()
+
+
+Widgets were previously added to static global tables. This isn't going to
+behave correctly when screen are added and removed. Remove this section.
+
+ -- Create a wibox for each screen and add it
+ -mywibox = {}
+ -mypromptbox = {}
+ -mylayoutbox = {}
+ -mytaglist = {}
+ -mytasklist = {}
+
+Many functions have been converted to methods. The old functions are deprecated,
+they are still supported, but will be removed in the next release.
+
+ -mytaglist.buttons = awful.util.table.join(
+ +local taglist_buttons = awful.util.table.join(
+ - awful.button({ }, 1, `awful.tag.viewonly`),
+ + awful.button({ }, 1, function(t) t:`view_only`() end),
+ - awful.button({ modkey }, 1, `awful.client.movetotag`),
+ + awful.button({ modkey }, 1, function(t)
+ + if `client.focus` then
+ + `client.focus:move_to_tag`(t)
+ + end
+ + end),
+ `awful.button`({ }, 3, `awful.tag.viewtoggle`),
+ - `awful.button`({ modkey }, 3, `awful.client.toggletag`),
+ + `awful.button`({ modkey }, 3, function(t)
+ + if client.focus then
+ + client.focus:toggle_tag(t)
+ + end
+ + end),
+ - `awful.button`({ }, 4, function(t) `awful.tag.viewnext`(awful.tag.getscreen(t)) end),
+ - `awful.button`({ }, 5, function(t) `awful.tag.viewprev`(awful.tag.getscreen(t)) end)
+ + `awful.button`({ }, 4, function(t) `awful.tag.viewnext`(t.screen) end),
+ + `awful.button`({ }, 5, function(t) `awful.tag.viewprev`(t.screen) end)
+ )
+
+ -mytasklist.buttons = awful.util.table.join(
+ +local tasklist_buttons = awful.util.table.join(
+ awful.button({ }, 1, function (c)
+ if c == `client.focus` then
+ c.minimized = true
+ -- Without this, the following
+ -- :isvisible() makes no sense
+ c.minimized = false
+ - if not c:isvisible() then
+ - `awful.tag.viewonly`(c:tags()[1])
+ + if not c:isvisible() and c.`first_tag` then
+ + c.first_tag:view_only()
+ end
+ -- This will also un-minimize
+ -- the client, if needed
+ c:raise()
+ end
+ end),
+
+
+This section is **very important**. This is where adding and removing screens is
+handled (including during startup). Note that the `mysomething` table
+previously removed are replaced by custom screens attributes.
+
+ -for s = 1, `screen.count`() do
+ +`awful.screen.connect_for_each_screen`(function(s)
+ + -- Wallpaper
+ + set_wallpaper(s)
+ +
+ + -- Each screen has its own tag table.
+ + awful.tag({ "1", "2", "3", "4", "5", "6", "7", "8", "9" }, s, awful.layout.layouts[1])
+ +
+ -- Create a promptbox for each screen
+ - mypromptbox[s] = `awful.widget.prompt`()
+ + s.mypromptbox = `awful.widget.prompt`()
+ -- Create an imagebox widget which will contains an icon indicating which layout we're using.
+ -- We need one layoutbox per screen.
+ - mylayoutbox[s] = `awful.widget.layoutbox`(s)
+ + s.mylayoutbox = `awful.widget.layoutbox`(s)
+ - mylayoutbox[s]:buttons(`awful.util.table.join`(
+ + s.mylayoutbox:buttons(`awful.util.table.join`(
+ `awful.button`({ }, 1, function () `awful.layout.inc`( 1) end),
+ `awful.button`({ }, 3, function () `awful.layout.inc`(-1) end),
+ `awful.button`({ }, 4, function () `awful.layout.inc`( 1) end),
+ `awful.button`({ }, 5, function () `awful.layout.inc`(-1) end)))
+ -- Create a taglist widget
+ - mytaglist[s] = `awful.widget.taglist`(s, `awful.widget.taglist.filter.all`, mytaglist.buttons)
+ + s.mytaglist = `awful.widget.taglist`(s, `awful.widget.taglist.filter.all`, taglist_buttons)
+
+ -- Create a tasklist widget
+ - mytasklist[s] = `awful.widget.tasklist`(s, `awful.widget.tasklist.filter.currenttags`, mytasklist.buttons)
+ + s.mytasklist = `awful.widget.tasklist`(s, `awful.widget.tasklist.filter.currenttags`, tasklist_buttons)
+
+ -- Create the wibox
+ - mywibox[s] = `awful.wibox`({ position = "top", screen = s })
+ + s.mywibox = `awful.wibar`({ position = "top", screen = s })
+
+ -- Widgets that are aligned to the left
+ local left_layout = wibox.layout.fixed.horizontal()
+ - left_layout:add(mylauncher)
+ - left_layout:add(mytaglist[s])
+ - left_layout:add(mypromptbox[s])
+ + left_layout:add(s.mytaglist)
+ + left_layout:add(s.mypromptbox)
+
+ -- Widgets that are aligned to the right
+ local right_layout = wibox.layout.fixed.horizontal()
+ - if s == 1 then right_layout:add(wibox.widget.systray()) end
+ + right_layout:add(wibox.widget.systray())
+ right_layout:add(mytextclock)
+ - right_layout:add(mylayoutbox[s])
+ + right_layout:add(s.mylayoutbox)
+
+ -- Now bring it all together (with the tasklist in the middle)
+ local layout = wibox.layout.align.horizontal()
+ layout:set_left(left_layout)
+ - layout:set_middle(mytasklist[s])
+ + layout:set_middle(s.mytasklist)
+ layout:set_right(right_layout)
+
+ - mywibox[s]:set_widget(layout)
+ + s.mywibox:set_widget(layout)
+ end)
+ -- }}}
+
+
+`awful.util.spawn` is now called `awful.spawn`.
+
+ -- Standard program
+ - `awful.key`({ modkey, }, "Return", function () `awful.util.spawn`(terminal) end),
+ + `awful.key`({ modkey, }, "Return", function () `awful.spawn`(terminal) end),
+
+Another dynamic screen related changes.
+
+ -- Prompt
+ - awful.key({ modkey }, "r", function () mypromptbox[mouse.screen]:run() end),
+ + awful.key({ modkey }, "r", function () `awful.screen.focused`().mypromptbox:run() end),
+
+`awful.prompt` now uses a more future proof arguments table instead of many
+optional arguments.
+
+ awful.key({ modkey }, "x",
+ function ()
+ - `awful.prompt.run`({ prompt = "Run Lua code: " },
+ - mypromptbox[mouse.screen].widget,
+ - `awful.util.eval`, nil,
+ - `awful.util.getdir`("cache") .. "/history_eval")
+ - end),
+ + awful.prompt.run {
+ + prompt = "Run Lua code: ",
+ + textbox = `awful.screen.focused`().mypromptbox.widget,
+ + exe_callback = `awful.util.eval`,
+ + history_path = `awful.util.get_cache_dir`() .. "/history_eval"
+ + }
+ + end),
+
+
+Another function-to-method API change:
+
+ - `awful.key`({ modkey, }, "o", `awful.client.movetoscreen` ),
+ + `awful.key`({ modkey, }, "o", function (c) c:`move_to_screen`() end),
+
+The `mod4+[1-9]` keybindings also have some changes related to deprecated
+functions.
+
+ -- Bind all key numbers to tags.
+ -- Be careful: we use keycodes to make it works on any keyboard layout.
+ -- This should map on the top row of your keyboard, usually 1 to 9.
+ for i = 1, 9 do
+ -- View tag only.
+ awful.key({ modkey }, "#" .. i + 9,
+ function ()
+ - local screen = `mouse.screen`
+ - local tag = `awful.tag.gettags`(screen)[i]
+ + local screen = `awful.screen.focused`()
+ + local tag = `screen.tags`[i]
+ if tag then
+ - `awful.tag.viewonly`(tag)
+ + `tag:view_only`()
+ end
+ end),
+ - -- Toggle tag.
+ + -- Toggle tag display.
+ awful.key({ modkey, "Control" }, "#" .. i + 9,
+ function ()
+ - local screen = `mouse.screen`
+ - local tag = `awful.tag.gettags`(screen)[i]
+ + local screen = `awful.screen.focused`()
+ + local tag = `screen.tags`[i]
+ if tag then
+ `awful.tag.viewtoggle`(tag)
+ end
+ for i = 1, 9 do
+ awful.key({ modkey, "Shift" }, "#" .. i + 9,
+ function ()
+ if client.focus then
+ - local tag = `awful.tag.gettags`(client.focus.screen)[i]
+ + local tag = `client.focus.screen.tags`[i]
+ if tag then
+ - `awful.client.movetotag`(tag)
+ + `client.focus:move_to_tag`(tag)
+ end
+ end
+ end),
+ - -- Toggle tag.
+ + -- Toggle tag on focused client.
+ awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
+ function ()
+ if client.focus then
+ - local tag = `awful.tag.gettags`(client.focus.screen)[i]
+ + local tag = `client.focus.screen.tags`[i]
+ if tag then
+ - `awful.client.toggletag`(tag)
+ + `client.focus:toggle_tag`(tag)
+ end
+ end
+ - end))
+ + end),
+ + )
+ end
+
+The default rules need to be changed to avoid having offscreen clients:
+
+ awful.rules.rules = {
+ -- All clients will match this rule.
+ { rule = { },
+ properties = { border_width = beautiful.border_width,
+ awful.rules.rules = {
+ focus = awful.client.focus.filter,
+ raise = true,
+ keys = clientkeys,
+ + buttons = clientbuttons,
+ + screen = `awful.screen.preferred`,
+ + placement = `awful.placement.no_overlap`+`awful.placement.no_offscreen`
+
+The `tags` global table has been removed to support dynamic screens, you can
+now access tags by name.
+
+ - -- properties = { tag = tags[1][2] } },
+ + -- properties = { screen = 1, tag = "2" } },
+
+
diff --git a/docs/89-NEWS.md b/docs/89-NEWS.md
index a1ea00fec..2d536718c 100644
--- a/docs/89-NEWS.md
+++ b/docs/89-NEWS.md
@@ -532,6 +532,8 @@ Many changes now cause a deprecation warning instead of breaking hard.
However, it is important to take note of these changes in order to avoid new
bugs.
+Also see the porting tips
+
### There can be off-screen clients unless rc.lua is adapted
diff --git a/docs/config.ld b/docs/config.ld
index 829501106..d8399f1f6 100644
--- a/docs/config.ld
+++ b/docs/config.ld
@@ -24,6 +24,7 @@ topics={
'05-awesomerc.md',
'06-appearance.md',
'16-using-cairo.md',
+ '17-porting-tips.md',
'90-FAQ.md',
'89-NEWS.md',
}