awesomeWM / awesome

awesome window manager
https://awesomewm.org/
GNU General Public License v2.0
6.39k stars 598 forks source link

Suggestion: allow easy customization of tasklist item label #3052

Open manuhabitela opened 4 years ago

manuhabitela commented 4 years ago

Hi,

In the tasklist widget, I'd like to customize what is rendered as the "client name". The code that handles that is here, in a local function of the tasklist widget code.

From what I understand it is not customizable as is. The only way for now to do what I want is to create another custom widget, that is 99% copy/paste of the tasklist widget.

What would you think about letting users customize the way the "task name" is generated? Maybe by letting the user pass a function as a "client name" getter, kinda like the current update_function, in the style table you can pass to the widget. Here is an example of a change in the tasklist_label function:

 if not disable_task_name then
-    if c.minimized then
-        name = name .. (gstring.xml_escape(c.icon_name) or gstring.xml_escape(c.name) or
-                        gstring.xml_escape("<untitled>"))
-    else
-        name = name .. (gstring.xml_escape(c.name) or gstring.xml_escape("<untitled>"))
-    end
+    local function default_client_name(c)
+        local name = c.name or "<untitled>"
+        if c.minimized then
+            name = c.icon_name or name
+        end
+        return gstring.xml_escape(name)
+    end
+    local client_name = args.client_name_function and args.client_name_function(c) or default_client_name(c)
+    name = name .. client_name
 end

Let me know if you think this would be OK to add something like that, I'd be happy to try to make a merge request. Thanks for awesome wm!

Now if you wanna know more about my use case:

I use a tasklist with text only (no icons), and I like to have class names in order to better identify opened windows. Browsers, text editors, terminals, and lots of other apps update their title according to what you do in them. In the end it's way easier to identify clients via their class names than their actual title. Another thing this feature would allow, would be to force capitalization of text or other things like it.

Here is an example of a args.client_name_function you could pass:

local function tasklist_client_name(c)
    local name = string.lower(c.class)
    if c.minimized then
        return "-" .. name
    end
    return gstring.xml_escape(name)
end
psychon commented 4 years ago

Can't you just override c.name in the way you want it to be? Something like

local ignore = false
client.connect_signal("property::name", function(c)
    if ignore then return end
    ignore = true
    c.name = c.name .. "foo"
    ignore = false
end)
manuhabitela commented 4 years ago

I quickly tried your code and it worked for setting random strings but I couldn't write c.name = c.class for some reason.

And I don't want to do that because I don't want to change the actual client name. It is still useful to have in a client titlebar for example.

Elv13 commented 4 years ago

It is possible to create a custom widget template for the task list. There is 2 ways to do what you want using a template. The first one is a callback and the second one is adding a set_client = function(self, client) self.text = 'whatever' end to a textbox in the template. IMHO, the second one is very clean.

Note that this requires version v4.3+, git-master has a couple bugfixes regarding that feature.

manuhabitela commented 4 years ago

Oh if I understand correctly, I could more or less do something like this for my usecase (searching for the client name in the textbox and replacing it by the client class):

set_client = function(self, client)
  self.text = self.text:gsub(client.name, client.class)
end

I'll try this soon, as my configuration relies on a few stuff only on master, I need to make it work again with 4.3 (I tried both the callback and "set_client" and it indeed doesn't work on the latest commit).

I'll get back to you, thanks!

manuhabitela commented 4 years ago

Hey, thanks, I finally managed to get what I want without copying 99% of the tasklist widget! It's a bit of a hack though in the end... Having a concrete API for this might be better still ;)

Here is an example of something working:

local tasklist_template = {
  {
    {
      id = "text_role",
      widget = wibox.widget.textbox
    },
    id = "text_margin_role",
    left = dpi(4),
    right = dpi(4),
    widget = wibox.container.margin,
  },
  id = "background_role",
  widget = wibox.container.background,
  create_callback = function(self, c)
    local tb = self:get_children_by_id('text_role')[1]
    local set_markup_silently = tb.set_markup_silently
    tb.set_markup_silently = function(slf, text)
      local new_text = helpers.string.replace(text, c.name, c.class:lower())
      new_text = helpers.string.replace(new_text, "_", "-")
      if c.minimized then new_text = "-" .. new_text end
      return set_markup_silently(tb, new_text)
    end
  end
}

Without recreating the template set_markup_silently function on the fly it would not work because the create and update callbacks are actually called before the label setup in common.list_update.

I think I didn't understand what you meant by set_client = [...] because I didn't manage to make it work that way.

edit: after using that for a while I noticed sometimes the text is not replaced correctly. I'm kinda giving up on doing something clean for now and will go back on my tasklist widget copy