Different view/tag API
Added by Sae Hirak over 12 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 12 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 12 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 12 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:
{ "ul" => "xfce4-taskmanager", "ll" => "transmission-gtk", "lr" => "pavucontrol" }.each do |k, v|
tag k do
match instance: v
gravity "g50#{k}".to_sym
end
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 12 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 12 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 12 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 12 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 12 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 12 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 12 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 12 years ago
RE: Different view/tag API - Added by Sae Hirak over 12 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 12 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:
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:
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:
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 12 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 12 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 12 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 12 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 12 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 12 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 12 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.
tag "urxvt", "urxvt"
tag "special" do
geometry [ 10, 10, 100, 100 ]
stick true
end
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 12 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:
view "foo" do client "foo" # displays the client "foo" on the view "foo" end
- Clients can have properties assigned to them:
client "foo" do gravity :foo end
- Shorthand syntax:
client "foo", :foo # gives the client "foo" the gravity :foo
- Shorthand syntax:
- Tags bundle up multiple properties and can be assigned to a client:
tag "foo" do gravity :foo borderless true float true end client "foo" do tag "foo" # gives the client "foo" all the properties in the tag "foo" # in this case that means giving it a gravity of :foo, and # making it borderless and floating end
- Shorthand syntax:
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".
- Shorthand syntax:
All together, it looks like this:
- Old
tag "foo" do match "foo" gravity :foo borderless true float true end view "foo" do match "foo" end
- New (using a tag)
tag "foo" do gravity :foo borderless true float true end view "foo" do client "foo", "foo" # gives the client "foo" the tag "foo" end
- New (without using a tag)
view "foo" do client "foo" do gravity :foo borderless true float true end 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:
on :client_create do |c| c.gravity = :foo # don't need a tag to assign a gravity... c.tag "foo" # ...but you can assign a tag if you want to end