Repository: qtile/qtile

Issue:
Emacs (and chrome) raising itself
# Issue description

Emacs insists on focusing itself when "interesting stuff happens" (I think it's messages in the minibuffer when I refresh my org-mode agenda.) I've tried within emacs how to stop that behaviour but couldn't figure it out. Now I've tried setting `focus_on_window_activation = False` (or `None`) in my qtile config, which didn't help either.

One way to reproduce that behaviour is `M-x server-start<RET>` from within emacs and then from the shell:
```sh
emacsclient -e '(make-frame-visible)'
```

(However one could argue that what happens, i.e. emacs making itself visible, is exactly what is *supposed* to happen.)

Here's the xprop output for emacs (without the icon):
```
_NET_WM_STATE(ATOM) = 
_NET_WM_DESKTOP(CARDINAL) = 0
WM_STATE(WM_STATE):
		window state: Normal
		icon window: 0x0
_MOTIF_DRAG_RECEIVER_INFO(_MOTIF_DRAG_RECEIVER_INFO) = 0x6c, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
WM_HINTS(WM_HINTS):
		Client accepts input or input focus: True
		Initial state is Normal State.
		bitmap id # to use for icon: 0x1609c02
		bitmap id # of mask for icon: 0x1609c08
		window id # of group leader: 0x1600001
_GTK_THEME_VARIANT(UTF8_STRING) = 
_NET_WM_ICON(CARDINAL) = 	Icon (48 x 48):
```
[...]
```
XdndAware(ATOM) = BITMAP
_NET_WM_OPAQUE_REGION(CARDINAL) = 7, 0, 2546, 7, 0, 7, 2560, 1403
_NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL
_NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 23068994, 23068995
_NET_WM_USER_TIME_WINDOW(WINDOW): window id # 0x1600141
WM_CLIENT_LEADER(WINDOW): window id # 0x1600001
_NET_WM_PID(CARDINAL) = 4498
WM_LOCALE_NAME(STRING) = "de_DE.UTF-8"
WM_CLIENT_MACHINE(STRING) = "bluki.dfn-cert.de"
WM_NORMAL_HINTS(WM_SIZE_HINTS):
		user specified location: 0, 0
		user specified size: 26 by 46
		program specified minimum size: 26 by 46
		program specified resize increment: 10 by 21
		program specified base size: 26 by 46
		window gravity: NorthWest
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW, WM_TAKE_FOCUS, _NET_WM_PING, _NET_WM_SYNC_REQUEST
WM_CLASS(STRING) = "emacs", "Emacs"
WM_ICON_NAME(STRING) = " *Minibuf-1*"
_NET_WM_ICON_NAME(UTF8_STRING) = " *Minibuf-1*"
WM_NAME(STRING) = " *Minibuf-1*"
_NET_WM_NAME(UTF8_STRING) = " *Minibuf-1*"
```

And since `google-chrome-beta http://qtile.org` will also raise a
chrome window, here's the xprop output for chrome (without the icon):

```
XdndTypeList(ATOM) = STRING, TEXT, UTF8_STRING, _NETSCAPE_URL, chromium/x-renderer-taint, text/html, text/plain, text/x-moz-url
_NET_WM_DESKTOP(CARDINAL) = 0
WM_STATE(WM_STATE):
		window state: Normal
		icon window: 0x0
_NET_WM_USER_TIME(CARDINAL) = 27989072
WM_NORMAL_HINTS(WM_SIZE_HINTS):
		program specified location: 1919, 0
		program specified minimum size: 501 by 105
WM_NAME(UTF8_STRING) = "New Issue · qtile/qtile - Google Chrome"
_NET_WM_NAME(UTF8_STRING) = "New Issue · qtile/qtile - Google Chrome"
XdndAware(ATOM) = BITMAP
_MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x2, 0x0, 0x1, 0x0, 0x0
_NET_WM_ICON(CARDINAL) = 	Icon (128 x 128):
```
[...]
```
_NET_WM_BYPASS_COMPOSITOR(CARDINAL) = 2
_NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 12582914, 12582915
WM_WINDOW_ROLE(STRING) = "browser"
WM_CLASS(STRING) = "google-chrome-beta", "Google-chrome-beta"
_NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL
_NET_WM_PID(CARDINAL) = 4501
WM_LOCALE_NAME(STRING) = "de_DE.UTF-8"
WM_CLIENT_MACHINE(STRING) = "bluki.dfn-cert.de"
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW, _NET_WM_PING, _NET_WM_SYNC_REQUEST
```

# Qtile version

7b43c9f8af839831306a99d717a9afb0280a7189

# Config

```python
import os
import re
import socket
import subprocess

from libqtile.config import Key, Screen, Group, Drag, Click
from libqtile.command import lazy
from libqtile import layout, bar, widget, hook
import xcffib.xproto

# copied from libqtile/resources/default_config.py and adapted by me

mod = "mod4"


def hostname():
    return socket.gethostname()


gemini = (hostname == 'gemini')


# thanks to rogerduran for the implementation of my idea (borrowed
# from stumpwm)
class PrevFocus(object):
    """Store last focus per group and go back when called"""

    def __init__(self):
        self.focus = None
        self.old_focus = None
        self.groups_focus = {}
        hook.subscribe.client_focus(self.on_focus)

    def on_focus(self, window):
        group = window.group
        # only store focus if the group is set
        if not group:
            return
        group_focus = self.groups_focus.setdefault(group.name, {
            "current": None, "prev": None
        })
        # don't change prev if the current focus is the same as before
        if group_focus["current"] == window:
            return
        group_focus["prev"] = group_focus["current"]
        group_focus["current"] = window

    def __call__(self, qtile):
        group = qtile.current_group
        group_focus = self.groups_focus.get(group.name, {"prev": None})
        prev = group_focus["prev"]
        if prev and group.name == prev.group.name:
            group.focus(prev, False)


# taken from
# https://github.com/qtile/qtile-examples/blob/master/roger/config.py#L34
# and adapted
def pull_window_group_here(**kwargs):
    """Switch to the *next* window matched by match_window_re with the given
    **kwargs

    If you have multiple windows matching the args, switch_to will
    cycle through them.

    (Those semantics are similar to the fvwm Next commands with
    patterns)
    """

    def callback(qtile):
        windows = windows_matching_shuffle(qtile, **kwargs)
        if windows:
            window = windows[0]
            qtile.current_screen.set_group(window.group)
            window.group.focus(window, False)
            window.focus(window, False)

    return lazy.function(callback)


def window_switch_to_screen_or_pull_group(**kwargs):
    """If the group of the window matched by match_window_re with the
    given **kwargs is in a visible on another screen, switch to the
    screen, otherwise pull the group to the current screen

    """

    def callback(qtile):
        windows = windows_matching_shuffle(qtile, **kwargs)
        if windows:
            window = windows[0]
            if window.group != qtile.current_group:
                if window.group.screen:
                    qtile.cmd_to_screen(window.group.screen.index)
                qtile.current_screen.set_group(window.group)
            window.group.focus(window, False)

    return lazy.function(callback)


switch_window = window_switch_to_screen_or_pull_group


def make_sticky(qtile, *args):
    window = qtile.current_window
    screen = qtile.current_screen.index
    window.static(
        screen,
        window.x,
        window.y,
        window.width,
        window.height)


def pull_window_here(**kwargs):

    """pull the matched window to the current group and focus it

    matching behaviour is the same as in switch_to
    """
    def callback(qtile):
        windows = windows_matching_shuffle(qtile, **kwargs)
        if windows:
            window = windows[0]
            window.togroup(qtile.current_group.name)
            qtile.current_group.focus(window, False)

    return lazy.function(callback)


def windows_matching_shuffle(qtile, **kwargs):
    """return a list of windows matching window_match_re with **kwargs,
    ordered so that the current Window (if it matches) comes last
    """
    windows = sorted(
        [
            w
            for w in qtile.windows_map.values()
            if w.group and window_match_re(w, **kwargs)],
        key=lambda ww: ww.window.wid)
    idx = 0
    if qtile.current_window is not None:
        try:
            idx = windows.index(qtile.current_window)
            idx += 1
        except ValueError:
            pass
    if idx >= len(windows):
        idx = 0
    return windows[idx:] + windows[:idx]


def window_match_re(window, wmname=None, wmclass=None, role=None):
    """
    match windows by name/title, class or role, by regular expressions

    Multiple conditions will be OR'ed together
    """

    if not (wmname or wmclass or role):
        raise TypeError(
            "at least one of name, wmclass or role must be specified"
        )
    ret = False
    if wmname:
        ret = ret or re.match(wmname, window.name)
    try:
        if wmclass:
            cls = window.window.get_wm_class()
            if cls:
                for v in cls:
                    ret = ret or re.match(wmclass, v)
        if role:
            rol = window.window.get_wm_window_role()
            if rol:
                ret = ret or re.match(role, rol)
    except (xcffib.xproto.WindowError, xcffib.xproto.AccessError):
        return False
    return ret


def modifier_window_commands(match, spawn, *keys):
    # Use switch_window by default (just mod)
    # Use pull_window_here with additional ctrl
    # spawn new window with additional shift
    # Use pull_window_group_here with additional shift (mod, "shift", "control")
    mapping = (
        ([mod], switch_window(**match)),
        ([mod, "control"], pull_window_here(**match)),
        ([mod, "shift"], lazy.spawn(spawn)),
        ([mod, "shift", "control"], pull_window_group_here(**match)))
    return [Key(mods, key, command)
            for mods, command in mapping
            for key in keys]


keys = (
    modifier_window_commands(
        {'wmclass': "URxvt"},
        "urxvtc -e tmux -2 new-session -A -s {} \; set-window-option -q allow-rename on \; set-window-option -q automatic-rename on".format(
            os.environ.get('TMUX_MAIN_SESSION', '0')),
        'a', '1')  # I used to use Aterm
    + modifier_window_commands(
        {'wmclass': "Emacs"},
        "em",
        'e', '2')  # Emacs
    + modifier_window_commands(
        {'role': "browser"},
        "google-chrome --remote-debugging-port=9222",
        'g', '3')  # Galeon used to be my browser
    + modifier_window_commands(
        {"wmname": ".*pdf$", "wmclass": "(Evince|Acroread|Xpdf|Okular)"},
        "evince",
        'd')  # pDf
    + modifier_window_commands(
        {"wmclass": "jetbrains-pycharm"},
        "/afs/dfn-cert.de/pet/software/bin/pycharm.sh",
        "c", '4')  # pyCharm
    + modifier_window_commands(
        {"wmclass": "dolphin"},
        "dolphin",
        "p")  # dolpPhin
    + modifier_window_commands(
        {'wmclass': "Thunderbird"},
        "thunderbird",
        'z')  # used to be: chatZilla is started from firefox but I
              # (normally) don't use firefox for anything else
              #
              # since chatZilla is not supported any more, I use thunderbird
              # chat for IRC
    + modifier_window_commands(
        {'wmclass': "Claws-mail"},
        "claws-mail",
        'm')  # claws-Mail
    + modifier_window_commands(
        {'wmclass': "keepassxc"},
        "keepassxc",
        'k')  # keepassxc
    + modifier_window_commands(
        {'wmclass': "TelegramDesktop"},
        "Telegram",
        't')  # Telegram
    + [
        Key(
            [mod, "control", "shift"], "Return",
            lazy.spawn("sh -c 'screen -S tmuxttyS0 -X kill ; (i3lock --ignore-empty-password --show-failed-attempts --tiling --image ~/Bilder/OrionWF1920x1200.png || xlock -mode blank); while pgrep \"(xlock|i3lock)\"; do sleep 2; done; tmux-on-serial'")),

        # Switch between windows in current stack pane
        Key(
            [mod], "Down",
            lazy.layout.down()
        ),
        Key(
            [mod], "Up",
            lazy.layout.up()
        ),

        Key(
            [mod], "Left",
            lazy.layout.next()
        ),
        Key(
            [mod], "Right",
            lazy.layout.previous()
        ),

        # Move windows in current stack pane -> I'd like to have this, but
        # move_* commands do not exist
        Key(
            [mod, "shift"], "Down",
            lazy.layout.shuffle_down()
        ),
        Key(
            [mod, "shift"], "Up",
            lazy.layout.shuffle_up()
        ),

        Key(
            [mod, "shift"], "Left",
            lazy.layout.client_to_previous(),
            lazy.layout.shuffle_left()
        ),
        Key(
            [mod, "shift"], "Right",
            lazy.layout.client_to_next(),
            lazy.layout.shuffle_right()
        ),

        # resize columns
        Key(
            [mod, "control", "shift"], "Down",
            lazy.layout.grow_down()
        ),
        Key(
            [mod, "control", "shift"], "Up",
            lazy.layout.grow_up()
        ),

        Key(
            [mod, "control", "shift"], "Left",
            lazy.layout.grow_left()
        ),
        Key(
            [mod, "control", "shift"], "Right",
            lazy.layout.grow_right()
        ),

        # Switch window focus to other pane(s) of stack
        Key(
            [mod], "Tab",
            lazy.layout.previous()
        ),
        Key(
            [mod, "shift"], "Tab",
            lazy.layout.next()
        ),

        # Swap panes of split stack
        Key(
            [mod, "control"], "space",
            lazy.layout.rotate()
        ),

        # Toggle between split and unsplit sides of stack.
        # Split = all windows displayed
        # Unsplit = 1 window displayed, like Max layout, but still with
        # multiple stack panes
        Key(
            [mod, "shift"], "Return",
            lazy.layout.toggle_split()
        ),
        Key([mod], "Return", lazy.spawn("xterm")),

        Key(
            [mod, "shift"], "plus",
            lazy.layout.add_column()
        ),
        Key(
            [mod, "shift"], "minus",
            lazy.layout.remove_column()
        ),
        Key(
            [mod], "n",
            lazy.layout.normalize
        ),

        # Toggle between different layouts as defined below
        Key([mod], "space", lazy.next_layout()),
        Key([mod, "shift"], "space", lazy.prev_layout()),

        Key([mod, "control"], "r", lazy.restart()),
        Key([mod, "control"], "q", lazy.shutdown()),
        Key([mod, "shift"], "r", lazy.spawncmd()),
        Key([mod], "r", lazy.spawn("rofi -show-icons -show combi")),
        Key([mod], "x", lazy.qtilecmd()),
        Key([mod], "b", lazy.hide_show_bar()),
        Key([mod, "control"], "Delete", lazy.window.kill()),
        # Key([mod], "w", lazy.window.kill()),

        Key([mod], "y",
            lazy.function(PrevFocus())),
        Key([mod, "control"], "Left", lazy.prev_screen()),
        Key([mod, "control"], "Right", lazy.next_screen()),
        Key([mod, "control"], "f", lazy.window.toggle_floating()),
        Key([mod], "f", lazy.window.toggle_fullscreen()),
        Key([mod], "comma", lazy.window.toggle_minimize()),
        Key([mod], "period", lazy.window.toggle_maximize()),
        Key([mod], "s", lazy.spawn("rofi -show-icons -show window")),
        # not focusable or managable any more, use with caution
        Key([mod, "control"], "s", lazy.function(make_sticky)),
        Key([mod, "mod1"], "s",
            lazy.spawn("sudo -n pm-suspend-hybrid")),
        Key([mod, "control", "mod1"], "s",
            lazy.spawn("sudo pm-suspend")),
        Key(["mod1", "mod4"], "Scroll_Lock",
            lazy.spawn("/usr/bin/env bash -c 'setxkbmap -variant basic; "
                       "xmodmap ~/.Xmodmap; xmodmap ~/.Xmodmap.$(hostname)'"),
            lazy.restart()),
        Key([mod], "odiaeresis", lazy.spawn("playerctl previous")),
        Key([mod], "adiaeresis", lazy.spawn("playerctl next")),
        Key([mod], "numbersign", lazy.spawn("playerctl play-pause")),
        Key(["mod1", "mod4"], 'a', lazy.next_urgent())
    ]
)

if gemini:
    keys.extend([
        Key(["mod5"], "b", lazy.spawn("gemini-brightness -")),
        Key(["mod5"], "n", lazy.spawn("gemini-brightness +")),
        Key([], "XF86MonBrightnessDown", lazy.spawn("gemini-brightness -")),
        Key([], "XF86MonBrightnessUp", lazy.spawn("gemini-brightness +")),
        Key(["mod5"], "XF86MonBrightnessDown", lazy.spawn("gemini-brightness -")),
        Key(["mod5"], "XF86MonBrightnessUp", lazy.spawn("gemini-brightness +")),
        Key(["mod5"], "c",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v -||amixer sset Master 1%-'")),
        Key(["mod5"], "v",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v +||amixer sset Master 1%+'")),
        Key([], "XF86AudioLowerVolume",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v -||amixer sset Master 1%-'")),
        Key([], "XF86AudioRaiseVolume",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v +||amixer sset Master 1%+'")),
    ])
else:
    keys.extend([
        Key([mod], "minus",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v -||amixer sset Master 1%-'")),
        Key([mod], "plus",
            lazy.spawn(
                "/usr/bin/env bash -c 'aumix -v +||amixer sset Master 1%+'")),
    ])


groups = [Group(i, persist=(int(i) < 4), init=(int(i) < 4))
          for i in "12345678"]

for i in groups:
    # mod + F+ number of group = switch to group
    keys.append(
        Key([mod], "F%d" % int(i.name), lazy.group[i.name].toscreen())
    )

    # mod1 + shift + letter of group = switch to & move focused window to group
    keys.append(
        Key([mod, "shift"], "F%d" % int(i.name), lazy.window.togroup(i.name))
    )

layouts = [
    layout.Max(),
    layout.Stack(autosplit=True, fair=True),
    layout.Columns(name="3Columns", num_columns=3, fair=True),
    layout.RatioTile(),
]

if gemini:
    widget_defaults = dict(
        font='Arial',
        fontsize=23,
        padding=1,
    )
else:
    widget_defaults = dict(
        font='Arial',
        fontsize=16,
        padding=3,
    )


def _barstart():
    return [
        widget.GroupBox(this_current_screen_border='00FF00'),
        widget.Prompt(),
        # widget.WindowName(),  # either this
        # or this, not both!:
        widget.TaskList(highlight_method='block',
                        # I need this for windows without icons:
                        unfocused_border='#333333'),
        widget.Sep(),
        # widget.TextBox("Friedel's config", name="config"),
    ]


def _barend():
    return [
        widget.TextBox("Vol", name="volume_label"),
        widget.Volume(),
        widget.BatteryIcon(),
        widget.CurrentLayout(markup=True, fontsize=20, fontshadow='0000FF'),
        widget.Sep(),
        widget.Clock(format='%Y-%m-%d %a %H:%M:%S'),
    ]


screens = [
    Screen(
        bottom=bar.Bar(
            _barstart() + [
                widget.TextBox("C", name="cpu_label"),
                widget.CPUGraph(),
                widget.TextBox("M", name="memory_label"),
                widget.MemoryGraph(),
                widget.TextBox("N", name="net_label"),
                widget.NetGraph(),
                widget.Systray(),
                widget.Sep(),
            ] + _barend(),
            30)),
    Screen(
        bottom=bar.Bar(
            _barstart()
            + [
                widget.Sep()
            ]
            + _barend(), 30))
]

# Drag floating layouts.
mouse = [
    Drag([mod], "Button1", lazy.window.set_position_floating(),
         start=lazy.window.get_position()),
    Drag([mod], "Button3", lazy.window.set_size_floating(),
         start=lazy.window.get_size()),
    Click([mod], "Button2", lazy.window.bring_to_front())
]

dgroups_key_binder = None
dgroups_app_rules = []
main = None
follow_mouse_focus = True
bring_front_click = True
cursor_warp = False
floating_layout = layout.Floating()
auto_fullscreen = True

# XXX: Gasp! We're lying here. In fact, nobody really uses or cares about this
# string besides java UI toolkits; you can see several discussions on the
# mailing lists, github issues, and other WM documentation that suggest setting
# this string if your java app doesn't work correctly. We may as well just lie
# and say that we're a working one by default.
#
# We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in
# java that happens to be on java's whitelist.
wmname = "LG3D"


# see
# http://docs.qtile.org/en/latest/manual/faq.html#my-pointer-mouse-cursor-isn-t-the-one-i-expect-it-to-be  # noqa
# see https://wiki.dfn-cert.de/cgi-bin/wiki.pl/Workstations15.1Not_WorkingYet#toc15
@hook.subscribe.startup
def runner():
    subprocess.Popen(['xsetroot', '-cursor_name', 'left_ptr'])
    subprocess.Popen("gnome-keyring-daemon --replace --daemonize --components=secrets".split())

# prevent xfce4-notifyd windows from jumping around by ignoring them
@hook.subscribe.client_new
def auto_sticky(window):
    if window.name == "xfce4-notifyd":
        if window.group:
            screen = window.group.screen.index
        else:
            screen = window.qtile.current_screen.index
        window.window.configure(stackmode=xcffib.xproto.StackMode.Above)
        window.static(screen)


# from http://qtile.readthedocs.org/en/latest/manual/config/hooks.html#automatic-floating-dialogs
@hook.subscribe.client_new
def floating_dialogs(window):
    dialog = window.window.get_wm_type() == 'dialog'
    transient = window.window.get_wm_transient_for()
    bubble = window.window.get_wm_window_role() == 'bubble'
    if dialog or transient or bubble:
        window.floating = True


@hook.subscribe.client_new
def float_plasma(window):
    if window:
        plasma = window_match_re(window, wmclass="plasmashell")
        if plasma:
            window.floating = True

# from https://github.com/ramnes/qtile-config/blob/98e097cfd8d5dd1ab1858c70babce141746d42a7/config.py#L108
@hook.subscribe.screen_change
def set_screens(qtile, event):
    if not os.path.exists(os.path.expanduser('~/NO-AUTORANDR')):
        subprocess.run(["autorandr", "--change"])
        qtile.cmd_restart()

```




Discussion:
I forgot to mention that emacs was behaving like this for at least two years.
I've started using qtile not long ago and I also happen to have this behavior with Emacs raising itself !    
Could this be linked to this issue https://github.com/qtile/qtile/issues/537 ?
Try focus_on_window_activation = "never"?
This is another issue suggesting we should figure out type validation in configs. The OP says they tried False and None, but the value is really a string :(
> Try focus_on_window_activation = "never"?

That works, thanks!

> This is another issue suggesting we should figure out type validation in configs. The OP says they tried False and None, but the value is really a string :(

It might help if "never" was documented as a possible value. At least I can't find it at http://docs.qtile.org/en/latest/manual/config/index.html#configuration-variables
On Wed, Jun 03, 2020 at 10:37:56AM -0700, Friedrich Delgado wrote:
> > Try focus_on_window_activation = "never"?
> 
> That works, thanks!
> 
> > This is another issue suggesting we should figure out type validation in configs. The OP says they tried False and None, but the value is really a string :(
> 
> It might help if "never" was documented as a possible value. At least I can't find it at http://docs.qtile.org/en/latest/manual/config/index.html#configuration-variables

Well looking at the actual code, it really seems like False or None
should have actually worked. So I'm not sure what the deal is :(

On my side, I already had this line in the configuration file, but I still have the issue.     
I'm currently on master (599e0b6ac51ba2accc098b6a00667d23dae85207), I'll try to update to a newer version as there seem to be some focus-related commits recently.    
If you have any hints about where I could look to try to debug this, feel free to tell me.

Resolving patch:
diff --git a/libqtile/window.py b/libqtile/window.py
--- a/libqtile/window.py
+++ b/libqtile/window.py
@@ -1230,8 +1230,10 @@ def handle_ClientMessage(self, event):  # noqa: N802
                 elif focus_behavior == "urgent" or (focus_behavior == "smart" and not self.group.screen):
                     logger.info("Setting urgent flag for window")
                     self.urgent = True
-                else:
+                elif focus_behavior == "never":
                     logger.info("Ignoring focus request")
+                else:
+                    logger.warning("Invalid value for focus_on_window_activation: {}".format(focus_behavior))
 
     def handle_PropertyNotify(self, e):  # noqa: N802
         name = self.qtile.conn.atoms.get_name(e.atom)


Test patch: