Different view/tag API

Added by Sae Hirak over 5 years ago

Currently, my subtle.rb file looks something like this:

tag "foo" do
  ...
end

tag "foo:bar" do
  ...
end

tag "foo:qux" do
  ...
end
view "foo" do
  match "^foo:|$" 
  ...
end

I dislike how you have to define tags and views separately, and as you can see there's a lot of redundancy (in particular, "foo" is repeated a lot). So instead, I'd prefer to write something like this, which would be equivalent to the above:

view "foo" do
  ...

  tag "bar" do
    ...
  end

  tag "qux" do
    ...
  end
end

This is slightly less flexible, of course, but it's much more convenient.

Rather than trying to get this change into subtle itself, I would rather write it in Ruby. My question is, how would I go about doing that? Is there some Ruby class I can change/extend, or do I have to overwrite all the variables like this:

old_view = view
old_tag = tag
old_match = match
...
old_geometry = geometry

def view(x) ... end
def tag(x) ... end
def match(x) ... end
...
def geometry(x) ... end

Replies (21)

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

Overriding the classes has no effect, the DSL is just used to copy data from ruby to C and the data stays there.

I don't see the benefit of your suggestion, tags are a kind of n:m relationship between clients and views, whereas clients can have all tags and views, too. Adding a scope or context to a tag makes the thing pretty complex, more complex than it has to be.

What are you trying to do?

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

For me, the sole use of tags is to automatically place clients into a particular view, with a particular gravity. I only use one tag per client, and only one view per tag. As an example, here's a snippet copied from my subtle.rb:

tag "stat:ul" do
  match :instance => "xfce4-taskmanager" 
  gravity :g50_ul
end

tag "stat:ur" do
  gravity :g50_ur
end

tag "stat:ll" do
  match :instance => "transmission-gtk" 
  gravity :g50_ll
end

tag "stat:lr" do
  match :instance => "pavucontrol" 
  gravity :g50_lr
end

view "stat", "^stat:" 

I would rather write it like this:

view "stat" do
  tag "ul" do
    match :instance => "xfce4-taskmanager" 
    gravity :g50_ul
  end

  tag "ur" do
    gravity :g50_ur
  end

  tag "ll" do
    match :instance => "transmission-gtk" 
    gravity :g50_ll
  end

  tag "lr" do
    match :instance => "pavucontrol" 
    gravity :g50_lr
  end
end

Same meaning, just easier to write/change/configure. And another example:

tag "gimp" do
  match :instance => "^gimp-\d\.\d$" 
end

tag "gimp:l" do
  match :instance => "^gimp-\d\.\d$", :role => "gimp-toolbox" 
  gravity :g15_l
end

tag "gimp:r" do
  match :instance => "^gimp-\d\.\d$", :role => "gimp-image-window" 
  gravity :g15_r
end

view "gimp" do
  match "^gimp:|$" 
  dynamic true
end

I would rather write that like this:

view "gimp" do
  match :instance => "^gimp-\d\.\d$" 
  dynamic true

  tag "l" do
    match :role => "gimp-toolbox" 
    gravity :g15_l
  end

  tag "r" do
    match :role => "gimp-image-window" 
    gravity :g15_r
  end
end

Once again, same meaning, just easier to write. I understand that this way of setting up views/tags may not work well with your particular workflow, but I'd still like to get it working for me personally, in which case I may need to do everything from scratch in Ruby, including listening to :client_create and setting up tags as appropriate. I was just hoping there was an easier way.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

Most of your examples are just a different syntax, you can wrap it your way if you like, it is ruby after all. You can even do the assignments in a loop if you feel better:

1 { "ul" => "xfce4-taskmanager", "ll" => "transmission-gtk", "lr" => "pavucontrol" }.each do |k, v|
2   tag k do
3     match instance: v
4     gravity "g50#{k}".to_sym
5   end
6 end

Still, adding a scope to tags makes it more complex and breaks some things. The only advantage is it might ease the problem with a tag limit.

Another way would be to allow to define a proc in a tag that is called whenever the tag matches.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

Most of your examples are just a different syntax

Yes, exactly. It's possible to do the same things now, but my proposed system is more convenient for certain things, that's all.

Still, adding a scope to tags makes it more complex and breaks some things.

I'm not actually proposing tag scoping, merely different syntax. This:

view "foo" do
  tag "bar" do
    ...
  end
end

Would be exactly the same as this, using the current system:

tag "foo:bar" do
  ...
end

view "foo" do
  match "^foo:|$" 
end

That is, nested tags have the view's name prepended along with : So you could manually assign a client to tag "foo:bar" if you wanted to, in either system.

Another way would be to allow to define a proc in a tag that is called whenever the tag matches.

That might be useful for a variety of reasons, yes, though I'm not sure exactly what it has to do with my proposed syntax.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

I could use something like this:

add_view "gimp",
  :match   => { :instance => "^gimp-\d\.\d$" },
  :dynamic => true,
  :tags    => { "l" => { :match   => { :role => "gimp-toolbox" },
                         :gravity => :g15_l }
                "r" => { :match   => { :role => "gimp-image-window" },
                         :gravity => :g15_r } }

But as you can see, that has a lot more syntax than the example I gave above, which rather defeats the purpose of having a shorter/simpler syntax. Ah well, it's not a big deal, I'll just have to deal with the current system.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

I'm working on an alternate API for defining views/tags. It's going well so far, until I ran into a snag... I can't seem to get the following to work:

class Foo
  def initialize name
    view name do
      ...
    end
  end
end
<WARNING> NoMethodError: undefined method `view' for #<#<Class:0x00000002742280>::View:0x0000000279b790>

It appears that Ruby is looking up the view method on the class Foo rather than using the global view. Okay, so I tried all the following, and none of them worked:

Object.view
Subtle.view
Subtle::Config.view

So, how do I reference global things like view and tag from inside a class?

Alternatively, is there a way to dynamically create view and tag objects? I looked into Subtlext::View.new but it doesn't seem to support things like match, dynamic, icon, or icon_only. Am I missing something?


By the way, the API looks like this:

View.new "stat" do |v|
  v.client "xfce4-taskmanager" => :g50_ul
  v.client "transmission-gtk"  => :g50_ll
  v.client "pavucontrol"       => :g50_lr
end

That is equivalent in functionality to this code:

view "stat", "^stat:|$" 

tag "stat:ul" do
  match instance: "^xfce4-taskmanager$" 
  gravity :g50_ul
end

tag "stat:ur" do
  match instance: "^transmission-gtk$" 
  gravity :g50_ll
end

tag "stat:lr" do
  match instance: "^pavucontrol$" 
  gravity :g50_lr
end

It has many more features too, but I'd prefer to describe them after I get it working.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

The DSL methods are no globals, they belong to an anonymous class subtle uses for namespacing. The config is basically just instance_eval'd in that class to avoid several problems like redefinition of classes and constants.

FWIW: I still think that this is a horrible thing to do, wrapping classes around a DSL. I added a DSL, because it is a config that is not intended for rubyists only. Another reason why there are no real classes besides the ones from Subtlext is they just don't exist during config execution, subtle needs to completely parse the config first.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

Alright, is it possible in any way for me to make a new API like the one above? If not, is there any way to make it possible? I'm not asking for the subtle API itself to change, merely to provide the ability for people like me to write our own custom APIs, if we so wish.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

Okay, I figured it out. I can use this to make it work:

$self = self

class Foo
  def initialize name
    $self.view name do
      ...
    end
  end
end

But now I've run into another snag... I'm trying to extend clients with some additional methods, so I can do things like this:

on :client_create do |c|
  c.foo
end

I tried this, but it doesn't seem to work:

require "subtle/subtlext" 

module Subtlext
  class Client
    def foo
      ...
    end
  end
end

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

Okay, I worked around that by using mixins:

module ClientWrapper
  def foo
    ...
  end
end

tag "foo" do
  on_match do |c|
    c.extend ClientWrapper
    c.foo
  end
end

Not sure about the performance of calling extend repeatedly on the same instance, but at least it works.

Now the only thing left to fix is to get gravity assignment working... worst case scenario is that I create a new tag for every rule, but I'd really prefer to use on_match.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

subtle dynamically loads subtlext when required, I bet this is the point where your extension of the objects gets lost. Too bad you cannot declare a classes as final, otherwise I surely would do it to prevent hacks like that. ;)

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

I'm sorry, but I really don't understand that mentality.

If a user wants to change their configuration on their computer to do what they want, why not let them? If you're worried about a non-Ruby user mucking up stuff... well, they don't know Ruby, therefore they don't even know what a class is let alone how to break it.

Because of the inherent flexibility of Ruby I was able to hack up my own lovely API which (in my opinion at least) is far superior to the current one offered by subtle, and I am very happy with it. This would not be possible if you had your way and everything was locked up and inaccessible.

I kindly suggest to let the user do what they want to do; only they know best what their needs and desires are.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

In any case, here's the new API I have come up with. I'm rather proud of it, and think it does an excellent job.

First off, why would I feel the need to make a new API? Isn't the current one good enough? In my opinion, no, it isn't. Why? Because of the way that I work, I use tags solely as a way to set up client options like gravity or borderless or whatever. You can see my old subtle tags/views here:

http://pastebin.com/kbShR6Uq

114 lines of code. The tags are defined separately from the views (even though they're structurally related). There's a lot of excess boilerplate because every single gravity/client needs its own tag. I don't want to have to worry about tags. I only care about "what view should this client be on, and what should its options (gravity, borderless, etc.) be?"

Let's look at the same config, but this time using the new API that I wrote. This is exactly equivalent in functionality to the old API, but... well, see for yourself:

http://pastebin.com/g4ysVaYB

55 lines of code, almost half the amount of the old way. It uses indentation to signify structure: clients belong to views. This is very different from the old API where tags/views are defined separately. It does exactly the same thing as before, without using tags, but it's much more readable, much shorter, much easier to change, etc. Overall a drastic improvement.

But surely this improvement must come at a cost, right? Nope. The new API is just as flexible as the old one: everything you can do with the old API you can do with the new API. In fact, the new API is so flexible that it even supports the old-school style of tags, if you so wish:

tag "float" do
  client "foo" do
    float true
  end
end

view "bar" do
  tag "float" 
end

So... how do you use this new-fangled API? First off, copy+paste this code into your configuration file:

http://pastebin.com/teUfqtfF

Now I will describe the different methods that you can use with the new API. Let's start with view:

icon

Same as calling icon in the old API.

icon_only

Same as calling icon_only in the old API.

dynamic

Same as calling dynamic in the old API.

tag

Now we're getting somewhere. This method is the same as the match method in the old API:

  • Old
    view "foo" do
      match "foo" 
    end
    
  • New
    view "foo" do
      tag "foo" 
    end
    

In addition to that, view also has access to all the methods for tag, which are:

on

Right now this only supports the single argument :match and a code block. It's equivalent to on_match in the old API:

  • Old
    tag "foo" do
      on_match do |c|
        ...
      end
    end
    
  • New
    tag "foo" do
      on :match do
        ...
      end
    end
    

gravity

This lets you set a gravity for either the entire view or (optionally) a client:

  • Old
    tag "bar" do
      match "bar" 
      gravity :bar
    end
    
    view "foo" do
      match "bar" 
      gravity :foo
    end
    
  • New
    view "foo" do
      gravity :foo
      gravity :bar, "bar" 
    end
    

client

This assigns a client to the view and optionally calls a block:

  • Old
    tag "foo", "foo" 
    
    tag "bar" do
      match "bar" 
      on_match do |c|
        ...
      end
    end
    
    view "foo", "foo|bar" 
    
  • New
    view "foo" do
      client "foo" 
      client "bar" do
        ...
      end
    end
    

Okay, that's nice, but it gets even better. Any time a block has 0 arguments, it is instance_eval 'd with the object, meaning that these two are exactly the same:

view "foo" do |v|
  v.client "foo" do |c|
    c.geometry [0, 0, 0, 0]
  end
end
view "foo" do
  client "foo" do
    geometry [0, 0, 0, 0]
  end
end

Naturally the short form is preferred, because it looks cleaner and involves less typing, but you're free to use the longer form if you like.


In addition, some extra methods have been added to clients:

gravity=

Clients already have a gravity= method, but this one ensures that the gravity is set on all the views, rather than only the current one:

  • Old
    c.views.each { |v| c.gravity = { v => :foo } }
    
  • New
    c.gravity = :foo
    

You can still set the gravity for an individual view if you like, this is just a convenience when using a symbol.

flags, geometry, gravity, tags

These are used as a convenience in the shorthand form:

  • Old
    match "foo" 
    on_match do |c|
      c.gravity = :foo
      c.geometry = [0, 0, 0, 0]
    end
    
  • New
    client "foo" do
      gravity :foo
      geometry [0, 0, 0, 0]
    end
    

You can see this in my config written in the new API:

client instance: "phoenix", name: /^bsnes vd+$/ do
  geometry [ 508, 195, 584, 510 ]
  fixed true
end

borderless, fixed, float, full, resize, stick, urgent, zaphod

Subtle only provides things like is_float? and toggle_float, so these are convenience wrappers:

  • Old
    c.toggle_float unless c.is_float?
    
  • New
    c.float true
    

As you can see, the changes aren't that big, yet they make a huge difference when it comes to configuring subtle. The best part is that I was able to make this new API without actually changing subtle itself: it's written in Ruby and it's not just some theoretical idea; it's functional code that actually works right now.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

Well, I am happy that you're satisfied. Unlike your version I never intended to hide tags at all, tagging is just an abstraction, hiding it and you have the same crap as in xmonad or awesome. When you hide the concept of tags in the config users never understand what tagging really is and I have more time to try to explain it. From my experience the concept is really difficult for some people.

It is probally just about taste, I don't see the advantage.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

I don't mean to sound rude, but... did you even look at the two configs in my post? The difference between them is night and day. A system without tags is just as flexible as a system with tags, but it's much shorter and more readable and easier to change. I have provided evidence for that in my post.

My point is that you only need views/clients, tags don't add anything new. Tags are just unnecessary boilerplate and I don't see any reason to have them in subtle at all. Could you please provide a code snippet that shows where tags would be superior to the system I have devised?

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

Sure I did and still, I told you why I don't like to hide tags at all. I spent many hours trying to explain tagging and how it is used for placement. Try to explain that when there are no tags in the config, that is a key concept in subtle.

A higher level of abstraction is no evidence for superiority for me.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

...Okay, hold on. Tags...

  • Are very verbose
  • Are hard to explain to users
  • Create an additional layer of complexity
  • Don't add any more power/flexibility/whatever

...therefore, tags must have some benefit in order to make up for those problems, right? So, what are the benefits of tags?

I spent many hours trying to explain tagging and how it is used for placement.

Then get rid of tags and use my system which so dirt-simple for placement that you barely need to explain anything at all.

Users don't seem to have a problem with the workspace/window paradigm which is used in pretty much every other window manager. Therefore, removing tags would remove confusion from users, not create more.

Try to explain that when there are no tags in the config, that is a key concept in subtle.

Then perhaps it shouldn't be a key concept since it causes so many problems with no gain, correct?


I would love to see an example where tags are actually helpful.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

The difficulty to explain tagging was related to the odd way xmonad and awesome do it. It is not how I expect the whole thing to work. When users understand the workspace/window paradigm they are able to comprehend a view/tag/window paradigm, too.

Tags define placement and state of a window, you can e.g. create a tag to set a specific floating position and assign the tag per runtime to a window. Or you can use the launcher way and start clients with specific tags to get some properties that cannot be set with shortcuts like ^.

Tagging is the sole reason for placement in subtle, you can verify the placement of windows according to the tags with everything that can display tags and there are plenty of ways.

The current way is no accident, I chose to design it that way back then, because it think that is a way that works for me. And since I am not the only user of my window manager I can assume that other users see it in a similar way. From what I know, you're the first one who built a class concept on top of the DSL and you are free to do that, ofc.

To be able to customize the whole thing was a reason why I chose a scripting language over a plain config in the first place.

Edit: And sorry for the late reply, missed your answer and just read it just a few minutes ago.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

The difficulty to explain tagging was related to the odd way xmonad and awesome do it.

That's just it... xmonad and awesome don't have tags. They only have clients and views. No tags. What they call "tags" are actually what subtle calls "views".

It is not how I expect the whole thing to work.

That's fine, but I don't think the answer is to add tags. Just design a better system using views/clients.

Tags define placement and state of a window, you can e.g. create a tag to set a specific floating position and assign the tag per runtime to a window.

And you can do that without tags too... in a much shorter and more readable way, without the extra indirection:

client "foo" do
  geometry [0, 0, 100, 100]
  float true
end

The above says that the client "foo" should have this geometry and be floating.

What I'm saying is that it would be better if subtle had only views and clients. Views contain clients, and clients have options, like gravity, floating, geometry, etc. There's no need for tags in such a system, and in fact they would only get in the way.

And since I am not the only user of my window manager I can assume that other users see it in a similar way.

I agree that subtle is the best window manager by far out of all those I've tried, but (perhaps I'm alone in this) the reason for that is not tags.

To be able to customize the whole thing was a reason why I chose a scripting language over a plain config in the first place.

Everything you can do with tags, you can do with my system. Except my system is much less complex, has less indirection, is more structured, and is shorter. Tags only add extra indirection and boilerplate: they don't actually add any more functionality or flexibility.


I still want to see a code snippet showing that views/tags are better than views/clients.

RE: Different view/tag API - Added by Christoph Kappel over 5 years ago

Sae Hirak wrote:

And you can do that without tags too... in a much shorter and more readable way, without the extra indirection: snip The above says that the client "foo" should have this geometry and be floating.

You got me wrong, I didn't say that I want to define anything for a client, I just wanted to create a tag that I can assign per runtime.

1 tag "urxvt", "urxvt" 
2 
3 tag "special" do
4   geometry [ 10, 10, 100, 100 ]
5   stick    true
6 end
7 
8 view "terms", "urxvt"

And then:

subtler -cT urxvt special

There are more ways do add a tag like doing the same in a grab with a toggle-kind functionality. Please notice that I don't want to set every property/mode of the client manually, just add or remove a tag.

What I'm saying is that it would be better if subtle had only views and clients. Views contain clients, and clients have options, like gravity, floating, geometry, etc. There's no need for tags in such a system, and in fact they would only get in the way.

That is not how subtle works. Views have not the slightest knowledge about clients, they just know their tags and can check which client should be visible. Making this underlying system available to any higher level is a feature, at least for me. subtle is older than awesome and xmonad, I know their approach and I honestly don't like it, I don't want to make another copy of dwm, there already are aplenty and I prefer my view/tag/client concept.

I agree that subtle is the best window manager by far out of all those I've tried, but (perhaps I'm alone in this) the reason for that is not tags.

I didn't say tags are the reason people use subtle, but that the tagging concept isn't a burden so they have to create their own abstraction. There are some capable ruby guys in the project channel, so I am sure there would be something out there already.

Everything you can do with tags, you can do with my system. Except my system is much less complex, has less indirection, is more structured, and is shorter. Tags only add extra indirection and boilerplate: they don't actually add any more functionality or flexibility.

Nope, I don't agree here. See the example above.

I still want to see a code snippet showing that views/tags are better than views/clients.

q.e.d.

RE: Different view/tag API - Added by Sae Hirak over 5 years ago

I just wanted to create a tag that I can assign per runtime.

That's a good example. I, personally, have never wanted to use subtler to dynamically add/remove tags, but I can see how that would be useful. Thank you.

Please notice that I don't want to set every property/mode of the client manually, just add or remove a tag.

And I don't want to have to create a new tag just to set a couple properties on a client and assign it to a view.

That is not how subtle works.

Yes, I am aware. I've been talking about improving it.

subtle is older than awesome and xmonad

Neat, I didn't know that.

I don't want to make another copy of dwm, there already are aplenty and I prefer my view/tag/client concept.

I can respect that, but I still think views/clients/tags can be improved.


How about using view/client matching and then use tags to bundle up properties? Here's how I figure it could work...

  • Views match clients:
    1 view "foo" do
    2   client "foo"  # displays the client "foo" on the view "foo" 
    3 end
  • Clients can have properties assigned to them:
    1 client "foo" do
    2   gravity :foo
    3 end
    • Shorthand syntax:
      1 client "foo", :foo  # gives the client "foo" the gravity :foo
  • Tags bundle up multiple properties and can be assigned to a client:
     1 tag "foo" do
     2   gravity :foo
     3   borderless true
     4   float true
     5 end
     6 
     7 client "foo" do
     8   tag "foo"  # gives the client "foo" all the properties in the tag "foo" 
     9              # in this case that means giving it a gravity of :foo, and
    10              # making it borderless and floating
    11 end
    • Shorthand syntax:
      1 client "foo", "foo"  # gives the client "foo" the tag "foo"

    I think this fits in well with the idea of "dynamically assigning tags to a client at runtime".


All together, it looks like this:

  • Old
     1 tag "foo" do
     2   match "foo" 
     3   gravity :foo
     4   borderless true
     5   float true
     6 end
     7 
     8 view "foo" do
     9   match "foo" 
    10 end
  • New (using a tag)
     1 tag "foo" do
     2   gravity :foo
     3   borderless true
     4   float true
     5 end
     6 
     7 view "foo" do
     8   client "foo", "foo"  # gives the client "foo" the tag "foo" 
     9 end
  • New (without using a tag)
    1 view "foo" do
    2   client "foo" do
    3     gravity :foo
    4     borderless true
    5     float true
    6   end
    7 end

That's it. I think this system is easier to understand and explain than the current one.

It's also more consistent with the way clients already work:

1 on :client_create do |c|
2   c.gravity = :foo  # don't need a tag to assign a gravity...
3   c.tag "foo"       # ...but you can assign a tag if you want to
4 end

(1-21/21)