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
|
-- Grab environment we need
|
||||||
local gdebug = require("gears.debug")
|
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 set_shape = require("awful.client.shape").update.all
|
||||||
local object = require("gears.object")
|
local object = require("gears.object")
|
||||||
local grect = require("gears.geometry").rectangle
|
local grect = require("gears.geometry").rectangle
|
||||||
|
@ -1170,8 +1170,11 @@ end
|
||||||
-- @tparam bool|function merge If true then merge tags (select the client's
|
-- @tparam bool|function merge If true then merge tags (select the client's
|
||||||
-- first tag additionally) when the client is not visible.
|
-- first tag additionally) when the client is not visible.
|
||||||
-- If it is a function, it will be called with the client as argument.
|
-- 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
|
-- @usage -- run or raise urxvt (perhaps, with tabs) on modkey + semicolon
|
||||||
-- awful.key({ modkey, }, 'semicolon', function ()
|
-- awful.key({ modkey, }, 'semicolon', function ()
|
||||||
-- local matcher = function (c)
|
-- local matcher = function (c)
|
||||||
|
@ -1180,6 +1183,10 @@ end
|
||||||
-- awful.client.run_or_raise('urxvt', matcher)
|
-- awful.client.run_or_raise('urxvt', matcher)
|
||||||
-- end);
|
-- end);
|
||||||
function client.run_or_raise(cmd, matcher, merge)
|
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 clients = capi.client.get()
|
||||||
local findex = gtable.hasitem(clients, capi.client.focus) or 1
|
local findex = gtable.hasitem(clients, capi.client.focus) or 1
|
||||||
local start = gmath.cycle(#clients, findex + 1)
|
local start = gmath.cycle(#clients, findex + 1)
|
||||||
|
|
|
@ -16,6 +16,7 @@ local aplace = require("awful.placement")
|
||||||
local asuit = require("awful.layout.suit")
|
local asuit = require("awful.layout.suit")
|
||||||
local beautiful = require("beautiful")
|
local beautiful = require("beautiful")
|
||||||
local alayout = require("awful.layout")
|
local alayout = require("awful.layout")
|
||||||
|
local atag = require("awful.tag")
|
||||||
|
|
||||||
local ewmh = {
|
local ewmh = {
|
||||||
generic_activate_filters = {},
|
generic_activate_filters = {},
|
||||||
|
@ -83,6 +84,10 @@ end
|
||||||
-- @tparam string context The context where this signal was used.
|
-- @tparam string context The context where this signal was used.
|
||||||
-- @tparam[opt] table hints A table with additional hints:
|
-- @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.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
|
function ewmh.activate(c, context, hints) -- luacheck: no unused args
|
||||||
hints = hints or {}
|
hints = hints or {}
|
||||||
|
|
||||||
|
@ -121,12 +126,18 @@ function ewmh.activate(c, context, hints) -- luacheck: no unused args
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if hints and hints.raise then
|
if hints.raise then
|
||||||
c:raise()
|
c:raise()
|
||||||
if not awesome.startup and not c:isvisible() then
|
if not awesome.startup and not c:isvisible() then
|
||||||
c.urgent = true
|
c.urgent = true
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
--- Add an activate (focus stealing) filter function.
|
--- Add an activate (focus stealing) filter function.
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
-- * honor_workarea
|
-- * honor_workarea
|
||||||
-- * tag
|
-- * tag
|
||||||
-- * new_tag
|
-- * new_tag
|
||||||
-- * switchtotag
|
-- * switch_to_tags (also called switchtotag)
|
||||||
-- * focus
|
-- * focus
|
||||||
-- * titlebars_enabled
|
-- * titlebars_enabled
|
||||||
-- * callback
|
-- * 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:
|
to that tag you can add:
|
||||||
|
|
||||||
{ rule = { class = "Emacs" },
|
{ 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,
|
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
|
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"})
|
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.
|
--- Apply awful.rules.rules to a client.
|
||||||
-- @client c The client.
|
-- @client c The client.
|
||||||
function rules.apply(c)
|
function rules.apply(c)
|
||||||
|
@ -437,7 +477,7 @@ rules.high_priority_properties = {}
|
||||||
-- @tfield table awful.rules.delayed_properties
|
-- @tfield table awful.rules.delayed_properties
|
||||||
-- By default, the table has the following functions:
|
-- By default, the table has the following functions:
|
||||||
--
|
--
|
||||||
-- * switchtotag
|
-- * switch_to_tags
|
||||||
rules.delayed_properties = {}
|
rules.delayed_properties = {}
|
||||||
|
|
||||||
local force_ignore = {
|
local force_ignore = {
|
||||||
|
@ -471,11 +511,17 @@ function rules.high_priority_properties.tag(c, value, props)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function rules.delayed_properties.switchtotag(c, value)
|
function rules.delayed_properties.switch_to_tags(c, value)
|
||||||
if not value then return end
|
if not value then return end
|
||||||
atag.viewmore(c:tags(), c.screen)
|
atag.viewmore(c:tags(), c.screen)
|
||||||
end
|
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)
|
function rules.extra_properties.geometry(c, _, props)
|
||||||
local cur_geo = c:geometry()
|
local cur_geo = c:geometry()
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,9 @@ local lgi = require("lgi")
|
||||||
local Gio = lgi.Gio
|
local Gio = lgi.Gio
|
||||||
local GLib = lgi.GLib
|
local GLib = lgi.GLib
|
||||||
local util = require("awful.util")
|
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 protected_call = require("gears.protected_call")
|
||||||
|
|
||||||
local spawn = {}
|
local spawn = {}
|
||||||
|
@ -210,6 +212,39 @@ do
|
||||||
end
|
end
|
||||||
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 = {}
|
spawn.snid_buffer = {}
|
||||||
|
|
||||||
function spawn.on_snid_callback(c)
|
function spawn.on_snid_callback(c)
|
||||||
|
@ -220,7 +255,7 @@ function spawn.on_snid_callback(c)
|
||||||
--TODO v5: Remove this signal
|
--TODO v5: Remove this signal
|
||||||
c:emit_signal("spawn::completed_with_payload", props, callback)
|
c:emit_signal("spawn::completed_with_payload", props, callback)
|
||||||
|
|
||||||
timer.delayed_call(function()
|
gtimer.delayed_call(function()
|
||||||
spawn.snid_buffer[c.startup_id] = nil
|
spawn.snid_buffer[c.startup_id] = nil
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -435,6 +470,199 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close)
|
||||||
start_read()
|
start_read()
|
||||||
end
|
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::canceled" , spawn.on_snid_cancel )
|
||||||
capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel )
|
capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel )
|
||||||
capi.client.connect_signal ("manage" , spawn.on_snid_callback )
|
capi.client.connect_signal ("manage" , spawn.on_snid_callback )
|
||||||
|
|
|
@ -1281,19 +1281,39 @@ function tag.viewonly(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- View only a set of tags.
|
--- 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
|
-- @function awful.tag.viewmore
|
||||||
-- @param tags A table with tags to view only.
|
-- @param tags A table with tags to view only.
|
||||||
-- @param[opt] screen The screen of the tags.
|
-- @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())
|
screen = get_screen(screen or ascreen.focused())
|
||||||
local screen_tags = screen.tags
|
local screen_tags = screen.tags
|
||||||
for _, _tag in ipairs(screen_tags) do
|
for _, _tag in ipairs(screen_tags) do
|
||||||
if not gtable.hasitem(tags, _tag) then
|
if not gtable.hasitem(tags, _tag) then
|
||||||
_tag.selected = false
|
_tag.selected = false
|
||||||
|
elseif _tag.selected then
|
||||||
|
selected = selected + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, _tag in ipairs(tags) do
|
for _, _tag in ipairs(tags) do
|
||||||
_tag.selected = true
|
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
|
end
|
||||||
screen:emit_signal("tag::history::update")
|
screen:emit_signal("tag::history::update")
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ local gtable = {}
|
||||||
-- @return A new table containing all keys from the arguments.
|
-- @return A new table containing all keys from the arguments.
|
||||||
function gtable.join(...)
|
function gtable.join(...)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
for _, t in pairs({...}) do
|
for _, t in ipairs({...}) do
|
||||||
if t then
|
if t then
|
||||||
for k, v in pairs(t) do
|
for k, v in pairs(t) do
|
||||||
if type(k) == "number" then
|
if type(k) == "number" then
|
||||||
|
|
|
@ -292,11 +292,11 @@ assert(not awful.rules.add_rule_source("invalid_source", function()
|
||||||
end, {"awful.rules"}, {"awful.spawn"}))
|
end, {"awful.rules"}, {"awful.spawn"}))
|
||||||
gears.debug.print_warning = temp
|
gears.debug.print_warning = temp
|
||||||
|
|
||||||
-- Test tag and switchtotag
|
-- Test tag and switch_to_tags
|
||||||
test_rule {
|
test_rule {
|
||||||
properties = {
|
properties = {
|
||||||
tag = "9",
|
tag = "9",
|
||||||
switchtotag = true
|
switch_to_tags = true
|
||||||
},
|
},
|
||||||
test = function(class)
|
test = function(class)
|
||||||
local c = get_client_by_class(class)
|
local c = get_client_by_class(class)
|
||||||
|
@ -311,8 +311,8 @@ test_rule {
|
||||||
}
|
}
|
||||||
test_rule {
|
test_rule {
|
||||||
properties = {
|
properties = {
|
||||||
tag = "8",
|
tag = "8",
|
||||||
switchtotag = false
|
switch_to_tags = false
|
||||||
},
|
},
|
||||||
test = function(class)
|
test = function(class)
|
||||||
local c = get_client_by_class(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.
|
-- * Using spawn with array is already covered by the test client.
|
||||||
-- * spawn with startup notification is covered by test-spawn-snid.lua
|
-- * 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 = {
|
local steps = {
|
||||||
function()
|
function()
|
||||||
-- Test various error conditions. There are quite a number of them...
|
-- Test various error conditions. There are quite a number of them...
|
||||||
|
@ -119,6 +148,8 @@ local steps = {
|
||||||
exit_snd = code
|
exit_snd = code
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
spawn.once(tiny_client("client1"), {tag=screen[1].tags[2]})
|
||||||
end
|
end
|
||||||
if spawns_done == 3 then
|
if spawns_done == 3 then
|
||||||
assert(exit_yay == 0)
|
assert(exit_yay == 0)
|
||||||
|
@ -126,7 +157,174 @@ local steps = {
|
||||||
assert(async_spawns_done == 2)
|
assert(async_spawns_done == 2)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
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)
|
runner.run_steps(steps)
|
||||||
|
|
|
@ -72,7 +72,7 @@ local steps = {
|
||||||
awful.screen.focused().tags[1]:view_only()
|
awful.screen.focused().tags[1]:view_only()
|
||||||
|
|
||||||
runner.add_to_default_rules({ rule = { class = "XTerm" },
|
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")
|
awful.spawn("xterm")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue