Merge pull request #2409 from Elv13/spawn_once
A better run_or_raise/spawn.once/singleton API
This commit is contained in:
commit
2f70fd6cce
|
@ -8,7 +8,7 @@
|
|||
|
||||
-- Grab environment we need
|
||||
local gdebug = require("gears.debug")
|
||||
local spawn = require("awful.spawn")
|
||||
local spawn = nil --TODO v5 deprecate
|
||||
local set_shape = require("awful.client.shape").update.all
|
||||
local object = require("gears.object")
|
||||
local grect = require("gears.geometry").rectangle
|
||||
|
@ -1170,8 +1170,11 @@ end
|
|||
-- @tparam bool|function merge If true then merge tags (select the client's
|
||||
-- first tag additionally) when the client is not visible.
|
||||
-- If it is a function, it will be called with the client as argument.
|
||||
-- @see awful.spawn.once
|
||||
-- @see awful.spawn.single_instance
|
||||
-- @see awful.spawn.raise_or_spawn
|
||||
--
|
||||
-- @function awful.client.run_or_raise
|
||||
-- @deprecated awful.client.run_or_raise
|
||||
-- @usage -- run or raise urxvt (perhaps, with tabs) on modkey + semicolon
|
||||
-- awful.key({ modkey, }, 'semicolon', function ()
|
||||
-- local matcher = function (c)
|
||||
|
@ -1180,6 +1183,10 @@ end
|
|||
-- awful.client.run_or_raise('urxvt', matcher)
|
||||
-- end);
|
||||
function client.run_or_raise(cmd, matcher, merge)
|
||||
gdebug.deprecate("Use awful.spawn.single_instance instead of"..
|
||||
"awful.client.run_or_raise", {deprecated_in=5})
|
||||
|
||||
spawn = spawn or require("awful.spawn")
|
||||
local clients = capi.client.get()
|
||||
local findex = gtable.hasitem(clients, capi.client.focus) or 1
|
||||
local start = gmath.cycle(#clients, findex + 1)
|
||||
|
|
|
@ -16,6 +16,7 @@ local aplace = require("awful.placement")
|
|||
local asuit = require("awful.layout.suit")
|
||||
local beautiful = require("beautiful")
|
||||
local alayout = require("awful.layout")
|
||||
local atag = require("awful.tag")
|
||||
|
||||
local ewmh = {
|
||||
generic_activate_filters = {},
|
||||
|
@ -83,6 +84,10 @@ end
|
|||
-- @tparam string context The context where this signal was used.
|
||||
-- @tparam[opt] table hints A table with additional hints:
|
||||
-- @tparam[opt=false] boolean hints.raise should the client be raised?
|
||||
-- @tparam[opt=false] boolean hints.switch_to_tag should the client's first tag
|
||||
-- be selected if none of the client's tags are selected?
|
||||
-- @tparam[opt=false] boolean hints.switch_to_tags Select all tags associated
|
||||
-- with the client.
|
||||
function ewmh.activate(c, context, hints) -- luacheck: no unused args
|
||||
hints = hints or {}
|
||||
|
||||
|
@ -121,12 +126,18 @@ function ewmh.activate(c, context, hints) -- luacheck: no unused args
|
|||
return
|
||||
end
|
||||
|
||||
if hints and hints.raise then
|
||||
if hints.raise then
|
||||
c:raise()
|
||||
if not awesome.startup and not c:isvisible() then
|
||||
c.urgent = true
|
||||
end
|
||||
end
|
||||
|
||||
-- The rules use `switchtotag`. For consistency and code re-use, support it,
|
||||
-- but keep undocumented. --TODO v5 remove switchtotag
|
||||
if hints.switchtotag or hints.switch_to_tag or hints.switch_to_tags then
|
||||
atag.viewmore(c:tags(), c.screen, (not hints.switch_to_tags) and 0 or nil)
|
||||
end
|
||||
end
|
||||
|
||||
--- Add an activate (focus stealing) filter function.
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
-- * honor_workarea
|
||||
-- * tag
|
||||
-- * new_tag
|
||||
-- * switchtotag
|
||||
-- * switch_to_tags (also called switchtotag)
|
||||
-- * focus
|
||||
-- * titlebars_enabled
|
||||
-- * callback
|
||||
|
@ -83,7 +83,7 @@ If you want to put Emacs on a specific tag at startup, and immediately switch
|
|||
to that tag you can add:
|
||||
|
||||
{ rule = { class = "Emacs" },
|
||||
properties = { tag = mytagobject, switchtotag = true } }
|
||||
properties = { tag = mytagobject, switch_to_tags = true } }
|
||||
|
||||
If you want to apply a custom callback to execute when a rule matched,
|
||||
for example to pause playing music from mpd when you start dosbox, you
|
||||
|
@ -374,6 +374,46 @@ end
|
|||
|
||||
rules.add_rule_source("awful.spawn", apply_spawn_rules, {}, {"awful.rules"})
|
||||
|
||||
local function apply_singleton_rules(c, props, callbacks)
|
||||
local persis_id, info = c.single_instance_id, nil
|
||||
|
||||
-- This is a persistent property set by `awful.spawn`
|
||||
if awesome.startup and persis_id then
|
||||
info = aspawn.single_instance_manager.by_uid[persis_id]
|
||||
elseif c.startup_id then
|
||||
info = aspawn.single_instance_manager.by_snid[c.startup_id]
|
||||
aspawn.single_instance_manager.by_snid[c.startup_id] = nil
|
||||
elseif aspawn.single_instance_manager.by_pid[c.pid] then
|
||||
info = aspawn.single_instance_manager.by_pid[c.pid].matcher(c) and
|
||||
aspawn.single_instance_manager.by_pid[c.pid] or nil
|
||||
end
|
||||
|
||||
if info then
|
||||
c.single_instance_id = info.hash
|
||||
gtable.crush(props, info.rules)
|
||||
table.insert(callbacks, info.callback)
|
||||
table.insert(info.instances, c)
|
||||
|
||||
-- Prevent apps with multiple clients from re-using this too often in
|
||||
-- the first 30 seconds before the PID is cleared.
|
||||
aspawn.single_instance_manager.by_pid[c.pid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- The rule source for clients spawned by `awful.spawn.once` and `single_instance`.
|
||||
--
|
||||
-- **Has priority over:**
|
||||
--
|
||||
-- * `awful.rules`
|
||||
--
|
||||
-- **Depends on:**
|
||||
--
|
||||
-- * `awful.spawn`
|
||||
--
|
||||
-- @rulesources awful.spawn_once
|
||||
|
||||
rules.add_rule_source("awful.spawn_once", apply_singleton_rules, {"awful.spawn"}, {"awful.rules"})
|
||||
|
||||
--- Apply awful.rules.rules to a client.
|
||||
-- @client c The client.
|
||||
function rules.apply(c)
|
||||
|
@ -437,7 +477,7 @@ rules.high_priority_properties = {}
|
|||
-- @tfield table awful.rules.delayed_properties
|
||||
-- By default, the table has the following functions:
|
||||
--
|
||||
-- * switchtotag
|
||||
-- * switch_to_tags
|
||||
rules.delayed_properties = {}
|
||||
|
||||
local force_ignore = {
|
||||
|
@ -471,11 +511,17 @@ function rules.high_priority_properties.tag(c, value, props)
|
|||
end
|
||||
end
|
||||
|
||||
function rules.delayed_properties.switchtotag(c, value)
|
||||
function rules.delayed_properties.switch_to_tags(c, value)
|
||||
if not value then return end
|
||||
atag.viewmore(c:tags(), c.screen)
|
||||
end
|
||||
|
||||
function rules.delayed_properties.switchtotag(c, value)
|
||||
gdebug.deprecate("Use switch_to_tags instead of switchtotag", {deprecated_in=5})
|
||||
|
||||
rules.delayed_properties.switch_to_tags(c, value)
|
||||
end
|
||||
|
||||
function rules.extra_properties.geometry(c, _, props)
|
||||
local cur_geo = c:geometry()
|
||||
|
||||
|
|
|
@ -165,7 +165,9 @@ local lgi = require("lgi")
|
|||
local Gio = lgi.Gio
|
||||
local GLib = lgi.GLib
|
||||
local util = require("awful.util")
|
||||
local timer = require("gears.timer")
|
||||
local gtable = require("gears.table")
|
||||
local gtimer = require("gears.timer")
|
||||
local aclient = require("awful.client")
|
||||
local protected_call = require("gears.protected_call")
|
||||
|
||||
local spawn = {}
|
||||
|
@ -210,6 +212,39 @@ do
|
|||
end
|
||||
end
|
||||
|
||||
local function hash_command(cmd, rules)
|
||||
rules = rules or {}
|
||||
cmd = type(cmd) == "string" and cmd or table.concat(cmd, ';')
|
||||
|
||||
-- Do its best at adding some entropy
|
||||
local concat_rules = nil
|
||||
concat_rules = function (r, depth)
|
||||
if depth > 2 then return end
|
||||
|
||||
local keys = gtable.keys(rules)
|
||||
|
||||
for _, k in ipairs(keys) do
|
||||
local v = r[k]
|
||||
local t = type(v)
|
||||
|
||||
if t == "string" or t == "number" then
|
||||
cmd = cmd..k..v
|
||||
elseif t == "tag" then
|
||||
cmd = cmd..k..v.name
|
||||
elseif t == "table" and not t.connect_signal then
|
||||
cmd = cmd .. k
|
||||
concat_rules(v, depth + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
concat_rules(rules, 1)
|
||||
|
||||
local glibstr = GLib.String(cmd)
|
||||
|
||||
return string.format('%x', math.ceil(GLib.String.hash(glibstr)))
|
||||
end
|
||||
|
||||
spawn.snid_buffer = {}
|
||||
|
||||
function spawn.on_snid_callback(c)
|
||||
|
@ -220,7 +255,7 @@ function spawn.on_snid_callback(c)
|
|||
--TODO v5: Remove this signal
|
||||
c:emit_signal("spawn::completed_with_payload", props, callback)
|
||||
|
||||
timer.delayed_call(function()
|
||||
gtimer.delayed_call(function()
|
||||
spawn.snid_buffer[c.startup_id] = nil
|
||||
end)
|
||||
end
|
||||
|
@ -435,6 +470,199 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close)
|
|||
start_read()
|
||||
end
|
||||
|
||||
-- When a command should only be executed once or only have a single instance,
|
||||
-- track the SNID set on them to prevent multiple execution.
|
||||
spawn.single_instance_manager = {
|
||||
by_snid = {},
|
||||
by_pid = {},
|
||||
by_uid = {},
|
||||
}
|
||||
|
||||
aclient.property.persist("single_instance_id", "string")
|
||||
|
||||
-- Check if the client is running either using the rule source or the matcher.
|
||||
local function is_running(hash, matcher)
|
||||
local status = spawn.single_instance_manager.by_uid[hash]
|
||||
if not status then return false end
|
||||
|
||||
if #status.instances == 0 then return false end
|
||||
|
||||
for _, c in ipairs(status.instances) do
|
||||
if c.valid then return true end
|
||||
end
|
||||
|
||||
if matcher then
|
||||
for _, c in ipairs(client.get()) do
|
||||
if matcher(c) then return true end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Keep the data related to this hash.
|
||||
local function register_common(hash, rules, matcher, callback)
|
||||
local status = spawn.single_instance_manager.by_uid[hash]
|
||||
if status then return status end
|
||||
|
||||
status = {
|
||||
rules = rules,
|
||||
callback = callback,
|
||||
instances = setmetatable({}, {__mode = "v"}),
|
||||
hash = hash,
|
||||
exec = false,
|
||||
matcher = matcher,
|
||||
}
|
||||
|
||||
spawn.single_instance_manager.by_uid[hash] = status
|
||||
|
||||
return status
|
||||
end
|
||||
|
||||
local function run_once_common(hash, cmd, keep_pid)
|
||||
local pid, snid = spawn.spawn(cmd)
|
||||
|
||||
if type(pid) == "string" or not snid then return pid, snid end
|
||||
|
||||
assert(spawn.single_instance_manager.by_uid[hash])
|
||||
|
||||
local status = spawn.single_instance_manager.by_uid[hash]
|
||||
status.exec = true
|
||||
|
||||
spawn.single_instance_manager.by_snid[snid] = status
|
||||
|
||||
if keep_pid then
|
||||
spawn.single_instance_manager.by_pid[pid] = status
|
||||
end
|
||||
|
||||
-- Prevent issues related to PID being re-used and a memory leak
|
||||
gtimer {
|
||||
single_shot = true,
|
||||
autostart = true,
|
||||
timeout = 30,
|
||||
callback = function()
|
||||
spawn.single_instance_manager.by_pid [pid ] = nil
|
||||
spawn.single_instance_manager.by_snid[snid] = nil
|
||||
end
|
||||
}
|
||||
|
||||
return pid, snid
|
||||
end
|
||||
|
||||
local function run_after_startup(f)
|
||||
-- The clients are not yet managed during the first execution, so it will
|
||||
-- miss existing instances.
|
||||
if awesome.startup then
|
||||
gtimer.delayed_call(f)
|
||||
else
|
||||
f()
|
||||
end
|
||||
end
|
||||
|
||||
--- Spawn a command if it has not been spawned before.
|
||||
--
|
||||
-- This function tries its best to preserve the state across `awesome.restart()`.
|
||||
--
|
||||
-- By default, when no `unique_id` is specified, this function will generate one by
|
||||
-- hashing the command and its rules. If you have multiple instance of the same
|
||||
-- command and rules, you need to specify an UID or only the first one will be
|
||||
-- executed.
|
||||
--
|
||||
-- The `rules` are standard `awful.rules`.
|
||||
--
|
||||
-- This function depends on the startup notification protocol to be correctly
|
||||
-- implemented by the command. See `client.startup_id` for more information.
|
||||
-- Note that this also wont work with shell or terminal commands.
|
||||
--
|
||||
-- @tparam string|table cmd The command.
|
||||
-- @tparam table rules The properties that need to be applied to the client.
|
||||
-- @tparam[opt] function matcher A matching function to find the instance
|
||||
-- among running clients.
|
||||
-- @tparam[opt] string unique_id A string to identify the client so it isn't executed
|
||||
-- multiple time.
|
||||
-- @tparam[opt] function callback A callback function when the client is created.
|
||||
-- @see awful.rules
|
||||
function spawn.once(cmd, rules, matcher, unique_id, callback)
|
||||
local hash = unique_id or hash_command(cmd, rules)
|
||||
local status = register_common(hash, rules, matcher, callback)
|
||||
run_after_startup(function()
|
||||
if not status.exec and not is_running(hash, matcher) then
|
||||
run_once_common(hash, cmd, matcher ~= nil)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Spawn a command if an instance is not already running.
|
||||
--
|
||||
-- This is like `awful.spawn.once`, but will spawn new instances if the previous
|
||||
-- has finished.
|
||||
--
|
||||
-- The `rules` are standard `awful.rules`.
|
||||
--
|
||||
-- This function depends on the startup notification protocol to be correctly
|
||||
-- implemented by the command. See `client.startup_id` for more information.
|
||||
-- Note that this also wont work with shell or terminal commands.
|
||||
--
|
||||
-- Note that multiple instances can still be spawned if the command is called
|
||||
-- faster than the client has time to start.
|
||||
--
|
||||
-- @tparam string|table cmd The command.
|
||||
-- @tparam table rules The properties that need to be applied to the client.
|
||||
-- @tparam[opt] function matcher A matching function to find the instance
|
||||
-- among running clients.
|
||||
-- @tparam[opt] string unique_id A string to identify the client so it isn't executed
|
||||
-- multiple time.
|
||||
-- @tparam[opt] function callback A callback function when the client is created.
|
||||
-- @see awful.rules
|
||||
function spawn.single_instance(cmd, rules, matcher, unique_id, callback)
|
||||
local hash = unique_id or hash_command(cmd, rules)
|
||||
register_common(hash, rules, matcher, callback)
|
||||
run_after_startup(function()
|
||||
if not is_running(hash, matcher) then
|
||||
return run_once_common(hash, cmd, matcher ~= nil)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local raise_rules = {focus = true, switch_to_tags = true, raise = true}
|
||||
|
||||
--- Raise a client if it exists or spawn a new one then raise it.
|
||||
--
|
||||
-- This function depends on the startup notification protocol to be correctly
|
||||
-- implemented by the command. See `client.startup_id` for more information.
|
||||
-- Note that this also wont work with shell or terminal commands.
|
||||
--
|
||||
-- @tparam string|table cmd The command.
|
||||
-- @tparam table rules The properties that need to be applied to the client.
|
||||
-- @tparam[opt] function matcher A matching function to find the instance
|
||||
-- among running clients.
|
||||
-- @tparam[opt] string unique_id A string to identify the client so it isn't executed
|
||||
-- multiple time.
|
||||
-- @tparam[opt] function callback A callback function when the client is created.
|
||||
-- @see awful.rules
|
||||
-- @treturn client The client if it already exists.
|
||||
function spawn.raise_or_spawn(cmd, rules, matcher, unique_id, callback)
|
||||
local hash = unique_id or hash_command(cmd, rules)
|
||||
|
||||
local status = spawn.single_instance_manager.by_uid[hash]
|
||||
if status then
|
||||
for _, c in ipairs(status.instances) do
|
||||
if c.valid then
|
||||
c:emit_signal("request::activate", "spawn.raise_or_spawn", raise_rules)
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Do not modify the original. It also can't be a metatable.__index due to
|
||||
-- its "broken" `pairs()` support.
|
||||
local props = gtable.join(rules, raise_rules)
|
||||
|
||||
spawn.single_instance(cmd, props, matcher, unique_id, callback)
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
capi.awesome.connect_signal("spawn::canceled" , spawn.on_snid_cancel )
|
||||
capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel )
|
||||
capi.client.connect_signal ("manage" , spawn.on_snid_callback )
|
||||
|
|
|
@ -1281,19 +1281,39 @@ function tag.viewonly(t)
|
|||
end
|
||||
|
||||
--- View only a set of tags.
|
||||
--
|
||||
-- If `maximum` is set, there will be a limit on the number of new tag being
|
||||
-- selected. The tags already selected do not count. To do nothing if one or
|
||||
-- more of the tags are already selected, set `maximum` to zero.
|
||||
--
|
||||
-- @function awful.tag.viewmore
|
||||
-- @param tags A table with tags to view only.
|
||||
-- @param[opt] screen The screen of the tags.
|
||||
function tag.viewmore(tags, screen)
|
||||
-- @tparam[opt=#tags] number maximum The maximum number of tags to select.
|
||||
function tag.viewmore(tags, screen, maximum)
|
||||
maximum = maximum or #tags
|
||||
local selected = 0
|
||||
screen = get_screen(screen or ascreen.focused())
|
||||
local screen_tags = screen.tags
|
||||
for _, _tag in ipairs(screen_tags) do
|
||||
if not gtable.hasitem(tags, _tag) then
|
||||
_tag.selected = false
|
||||
elseif _tag.selected then
|
||||
selected = selected + 1
|
||||
end
|
||||
end
|
||||
for _, _tag in ipairs(tags) do
|
||||
if selected == 0 and maximum == 0 then
|
||||
_tag.selected = true
|
||||
break
|
||||
end
|
||||
|
||||
if selected >= maximum then break end
|
||||
|
||||
if not _tag.selected then
|
||||
selected = selected + 1
|
||||
_tag.selected = true
|
||||
end
|
||||
end
|
||||
screen:emit_signal("tag::history::update")
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ local gtable = {}
|
|||
-- @return A new table containing all keys from the arguments.
|
||||
function gtable.join(...)
|
||||
local ret = {}
|
||||
for _, t in pairs({...}) do
|
||||
for _, t in ipairs({...}) do
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
if type(k) == "number" then
|
||||
|
|
|
@ -292,11 +292,11 @@ assert(not awful.rules.add_rule_source("invalid_source", function()
|
|||
end, {"awful.rules"}, {"awful.spawn"}))
|
||||
gears.debug.print_warning = temp
|
||||
|
||||
-- Test tag and switchtotag
|
||||
-- Test tag and switch_to_tags
|
||||
test_rule {
|
||||
properties = {
|
||||
tag = "9",
|
||||
switchtotag = true
|
||||
switch_to_tags = true
|
||||
},
|
||||
test = function(class)
|
||||
local c = get_client_by_class(class)
|
||||
|
@ -312,7 +312,7 @@ test_rule {
|
|||
test_rule {
|
||||
properties = {
|
||||
tag = "8",
|
||||
switchtotag = false
|
||||
switch_to_tags = false
|
||||
},
|
||||
test = function(class)
|
||||
local c = get_client_by_class(class)
|
||||
|
|
|
@ -10,6 +10,35 @@ local exit_yay, exit_snd = nil, nil
|
|||
-- * Using spawn with array is already covered by the test client.
|
||||
-- * spawn with startup notification is covered by test-spawn-snid.lua
|
||||
|
||||
local tiny_client = function(class)
|
||||
return {"lua", "-e", [[
|
||||
local lgi = require 'lgi'
|
||||
local Gtk = lgi.require('Gtk')
|
||||
local class = ']]..class..[['
|
||||
|
||||
Gtk.init()
|
||||
|
||||
window = Gtk.Window {
|
||||
default_width = 100,
|
||||
default_height = 100,
|
||||
title = 'title',
|
||||
}
|
||||
|
||||
window:set_wmclass(class, class)
|
||||
|
||||
local app = Gtk.Application {}
|
||||
|
||||
function app:on_activate()
|
||||
window.application = self
|
||||
window:show_all()
|
||||
end
|
||||
|
||||
app:run {''}
|
||||
]]}
|
||||
end
|
||||
|
||||
local matcher_called = false
|
||||
|
||||
local steps = {
|
||||
function()
|
||||
-- Test various error conditions. There are quite a number of them...
|
||||
|
@ -119,6 +148,8 @@ local steps = {
|
|||
exit_snd = code
|
||||
end
|
||||
})
|
||||
|
||||
spawn.once(tiny_client("client1"), {tag=screen[1].tags[2]})
|
||||
end
|
||||
if spawns_done == 3 then
|
||||
assert(exit_yay == 0)
|
||||
|
@ -126,7 +157,174 @@ local steps = {
|
|||
assert(async_spawns_done == 2)
|
||||
return true
|
||||
end
|
||||
end,
|
||||
-- Test spawn_once
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
|
||||
assert(client.get()[1].class == "client1")
|
||||
assert(client.get()[1]:tags()[1] == screen[1].tags[2])
|
||||
|
||||
spawn.once(tiny_client("client1"), {tag=screen[1].tags[2]})
|
||||
spawn.once(tiny_client("client1"), {tag=screen[1].tags[2]})
|
||||
return true
|
||||
end,
|
||||
function(count)
|
||||
-- Limit the odds of a race condition
|
||||
if count ~= 3 then return end
|
||||
|
||||
assert(#client.get() == 1)
|
||||
assert(client.get()[1].class == "client1")
|
||||
client.get()[1]:kill()
|
||||
return true
|
||||
end,
|
||||
-- Test single_instance
|
||||
function()
|
||||
if #client.get() ~= 0 then return end
|
||||
|
||||
-- This should do nothing
|
||||
spawn.once(tiny_client("client1"), {tag=screen[1].tags[2]})
|
||||
|
||||
spawn.single_instance(tiny_client("client2"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test that no extra clients are created
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
|
||||
assert(client.get()[1].class == "client2")
|
||||
assert(client.get()[1]:tags()[1] == screen[1].tags[3])
|
||||
|
||||
-- This should do nothing
|
||||
spawn.single_instance(tiny_client("client2"), {tag=screen[1].tags[3]})
|
||||
spawn.single_instance(tiny_client("client2"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
|
||||
assert(client.get()[1].class == "client2")
|
||||
assert(client.get()[1]:tags()[1] == screen[1].tags[3])
|
||||
|
||||
client.get()[1]:kill()
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test that new instances can be spawned
|
||||
function()
|
||||
if #client.get() ~= 0 then return end
|
||||
|
||||
spawn.single_instance(tiny_client("client2"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test raise_or_spawn
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
|
||||
assert(client.get()[1].class == "client2")
|
||||
assert(client.get()[1]:tags()[1] == screen[1].tags[3])
|
||||
client.get()[1]:kill()
|
||||
|
||||
spawn.raise_or_spawn(tiny_client("client3"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Add more clients to test the focus
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
|
||||
-- In another iteration to make sure client4 has no focus
|
||||
spawn(tiny_client("client4"), {tag = screen[1].tags[4]})
|
||||
spawn(tiny_client("client4"), {tag = screen[1].tags[4]})
|
||||
spawn(tiny_client("client4"), {
|
||||
tag = screen[1].tags[4], switch_to_tags= true, focus = true,
|
||||
})
|
||||
|
||||
return true
|
||||
end,
|
||||
function()
|
||||
if #client.get() ~= 4 then return end
|
||||
|
||||
assert(screen[1].tags[3].selected == false)
|
||||
|
||||
for _, c in ipairs(client.get()) do
|
||||
if c.class == "client4" then
|
||||
assert(#c:tags() == 1)
|
||||
assert(c:tags()[1] == screen[1].tags[4])
|
||||
end
|
||||
end
|
||||
|
||||
assert(screen[1].tags[4].selected == true)
|
||||
|
||||
spawn.raise_or_spawn(tiny_client("client3"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test that the client can be raised
|
||||
function()
|
||||
if #client.get() ~= 4 then return false end
|
||||
|
||||
assert(client.focus.class == "client3")
|
||||
assert(screen[1].tags[3].selected == true)
|
||||
assert(screen[1].tags[4].selected == false)
|
||||
|
||||
|
||||
for _, c in ipairs(client.get()) do
|
||||
if c.class == "client4" then
|
||||
c:tags()[1]:view_only()
|
||||
client.focus = c
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert(screen[1].tags[3].selected == false)
|
||||
assert(screen[1].tags[4].selected == true )
|
||||
|
||||
for _, c in ipairs(client.get()) do
|
||||
if c.class == "client3" then
|
||||
c:kill()
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test that a new instance can be spawned
|
||||
function()
|
||||
if #client.get() ~= 3 then return end
|
||||
|
||||
spawn.raise_or_spawn(tiny_client("client3"), {tag=screen[1].tags[3]})
|
||||
|
||||
return true
|
||||
end,
|
||||
function()
|
||||
if #client.get() ~= 4 then return end
|
||||
|
||||
-- Cleanup
|
||||
for _, c in ipairs(client.get()) do
|
||||
c:kill()
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
-- Test the matcher
|
||||
function()
|
||||
if #client.get() ~= 0 then return end
|
||||
|
||||
spawn.single_instance("xterm", {tag=screen[1].tags[5]}, function(c)
|
||||
matcher_called = true
|
||||
return c.class == "xterm"
|
||||
end)
|
||||
|
||||
return true
|
||||
end,
|
||||
function()
|
||||
if #client.get() ~= 1 then return end
|
||||
assert(matcher_called)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
runner.run_steps(steps)
|
||||
|
|
|
@ -72,7 +72,7 @@ local steps = {
|
|||
awful.screen.focused().tags[1]:view_only()
|
||||
|
||||
runner.add_to_default_rules({ rule = { class = "XTerm" },
|
||||
properties = { tag = "2", focus = true, switchtotag = true }})
|
||||
properties = { tag = "2", focus = true, switch_to_tags = true }})
|
||||
|
||||
awful.spawn("xterm")
|
||||
|
||||
|
|
Loading…
Reference in New Issue