alex-courtis / way-displays

way-displays: Auto Manage Your Wayland Displays
MIT License
252 stars 13 forks source link

Feature: Profiles #83

Open edrex opened 1 year ago

edrex commented 1 year ago

As a floating laptop user, I would like have different config blocks dynamically applied depending on which outputs are connected.


Design constraints:

IMO what is needed is a kanshi-style "profiles" mechanism where each profile has a set of matching rules and a config block to apply. The profiles could be mutually-exclusive (first match wins) or layered (which would allow flexibly specifying defaults). This way, all config keys are subject to matching, and new matching rules can be added to introduce new axes of conditionality.

Workarounds

Currently, I have multiple commented blocks and whenever I switch stations, I open the config in my editor and switch the active one. It's not a huge deal, and I like it way better than the kanshi UX. This could also be done via the IPC mechanism, but that just kicks the ball down the road.

Anyway, I'm happy with things as they are now but I see the focus making ORDER super flexible as a distraction.

alex-courtis commented 1 year ago

IMO what is needed is a kanshi-style "profiles" mechanism where each profile has a set of matching rules and a config block to apply.

I think that's the key part here. The story will be achieved by setting the explicit configuration per-profile. Falling back to a default will still be useful. The behaviour of new displays can be best effort configured by profile or default, adding a new profile if needed.

My workarounds:

alex-courtis commented 1 year ago

Currently, I have multiple commented blocks and whenever I switch stations, I open the config in my editor and switch the active one.

Keep in mind the CLI. You can configure persistently or ephemerally.

You can also use the IPC API if you're sufficiently motivated.

See #65 which will add user hooks.

Regardless, profiles are a desirable solution.

alex-courtis commented 1 year ago

IMO what is needed is a kanshi-style "profiles" mechanism where each profile has a set of matching rules and a config block to apply.

Can you please draft some rules that we may add to cfg.yaml? My imagination is poor and can't see much beyond hostname or display name.

edrex commented 1 year ago

... separate config files per machine, configured via my dotfile manager

I use YADM - its per-host files feature can do this. I plan to add a Nix Home-Manager module for way-displays, which can differentiate per-host. Still, one config across machines seems like a nice convenience.

CLI ... persistently or ephemerally.

Oh, a comp/shell keybind to toggle the arrange/align would be nicer than editing the config file. Thanks!

I just set the order ... matches the 3 machines I use ... all left-to-right

I'd guess for a majority of users one arrange/align value will work across all their stations. I don't want to nudge you into adding features YAGNI!

edrex commented 1 year ago

Can you please draft some rules that we may add to cfg.yaml?

It seems like the only really necessary dynamic profile matching criteria is that a given set of outputs are present.

I first thought overloading the ORDER field, so that all entries in the order field would have to have a match for the profile to be applied (similar to kanshi's approach). I'm not sure how that would work with the new regex support though. Some rules may match multiple outputs?

Maybe there's still a way to make profile matching work via the ORDER field, but another possibility would be to add a dedicated field for profile matching:

PROFILES:
  a-profile-with-no-match-rule-always-applies:
    ARRANGE: COL # by default, unknown displays go above the laptop screen, eg for projectors
    ALIGN: MIDDLE
    ORDER:
      - '!.*$'
      - 'eDP-1' # eDP-1 defaults to last
  horizontal-laptop-right:
    MATCH:
      - '!(^Sharp Corporation 0x148D$)|(^Dell)' # When either of these displays are present, apply this profile atop the previous one
    ARRANGE: ROW
    ALIGN: BOTTOM
  hackerspace:
    MATCH:
      - 'Sharp Corporation 0x148D'
      - 'Monitor Maker ABC123' # When both of these displays are present, apply this profile too
    ORDER:
      - 'eDP-1' # in the hackerspace, laptop is on left
      - '!^Sharp'
      - '!^Monitor Maker'

WDYT?

MATCH could be named something else like DISPLAYS. If we ever wanted to additionally match on eg host, a HOST field could be added.

@matthewwardrop also curious if you have feedback/ideas here.

alex-courtis commented 1 year ago
PROFILES:
  horizontal-laptop-right:
     MATCH:

That works. Keeping it simple, all params could go in each profile, overriding defaults. Merge methods are already present. LAPTOP_DISPLAY_PREFIX would be annoying to build but possible. LOG_THRESHOLD doesn't make much sense and would likely result in strange output.

PROFILES:
  a-profile-with-no-match-rule-always-applies:

We could just use the toplevel defaults instead of a default profile.

Bonus points for Martin Fowler reference.

alex-courtis commented 1 year ago

There will be a nice solution for MATCH, but I cannot imagine it.

Even if we don't implement host matching, we should leave the door open for it and other future matches.

@matthewwardrop ideas please!

alex-courtis commented 1 year ago

Some rules may match multiple outputs?

I think we can trust the user. If they get a match for each of the match displays, the (edit: first) profile applies.

It would be documented.

matthewwardrop commented 1 year ago

Hi! Sorry for the delay, with things busy at work and with four munchkins at home, I don't have as much time as I'd like to clear out my inbox.

I also like the idea of context specific overrides... But like @edrex I use YADM as a workaround for this on my machine. If support were added, I'd keep the config as is, and then have context overlays that are matched in... Something like:

# usual config
ARRANGE: ...
ALIGN: ...
ORDER: ...

OVERLAYS:
    WORK: # name
        # nested config, dedented and processed like normal config, and then merged into parent config if MATCH is satisfied
        MATCH:
            OUTPUTS: # nested just in case we want to support matches on other things
                - usual regex or strings
        ALIGN: ....
        ....

You could even have match at the top-level too, where if provided and match is not satisfied, way-displays disables itself. This simplifies parsing a bit too, since the syntax could be arbitrarily nested and always have the same schema.

alex-courtis commented 1 year ago

way-displays disables itself.

Interesting. That would need to be an explicit setting as there's no means to set options that will disable everything.

arbitrarily nested and always have the same schema.

Yes. Same schema at root or inside a profile. The above setting would just be another entry in the schema.

matthewwardrop commented 1 year ago

If we went with something like I suggested, an empty parsed config would be that configuration option.

Oh, and I guess I'm less a fan of "profiles" as context specific instructions or "overlays". Profiles makes it sound more top-level (all or nothing, take the work profile, or the home profile, etc). I guess for me a profile is the evaluated entire state that is derived from matches /etc. This is probably just semantics.

alex-courtis commented 1 year ago

If we went with something like I suggested, an empty parsed config would be that configuration option.

Thinking more, I'm not sure how "do nothing" would look; there are no "defaults" to go back to when the user instructs "do nothing". It should become more clear after profiles exist and the user switch between them via CLI or hotplug.

Oh, and I guess I'm less a fan of "profiles" as context specific instructions or "overlays".

That makes sense - it's the base overridden by the profile. Fortunately all that code exists.

edrex commented 1 year ago

We could just use the toplevel defaults instead of a default profile.

If support were added, I'd keep the config as is, and then have context overlays that are matched in

Okay, I see there are strong reasons to keep config backwards compatible by leaving toplevel options in place.

the syntax could be arbitrarily nested and always have the same schema.

I can't speak to ease of implementation, but allowing deeply nested blocks seems confusing for users. No strong opinion as long as the use case is satisfied.

Profiles makes it sound more top-level (all or nothing, take the work profile, or the home profile, etc). I guess for me a profile is the evaluated entire state that is derived from matches /etc. This is probably just semantics.

... and then have context overlays that are matched in ...

Good names are important, and worth thinking about. Overlays does tell you how they work, if you know what an overlay is, which is a nice property. I feel like it sounds like a compositor thing which might be confusing. What about CONTEXTS? Each block is a context scoped by match rules. Contexts don't imply exclusivity like profiles do. Again though, don't care much as long as the use case is satisfied. Bike shed.

You could even have match at the top-level too, where if provided and match is not satisfied, way-displays disables itself.

Thinking more, I'm not sure how "do nothing" would look; there are no "defaults" to go back to when the user instructs "do nothing". It should become more clear after profiles exist and the user switch between them via CLI or hotplug.

I feel like if neither any currently matching blocks nor the root block set a value for an option, it should act the same as when the option isn't set in the current implementation (ie, it should be reinitialized to the programmatically-defined default value). (update: see next comment)

I don't see a use case for having a matching rule at the top level, but maybe I'm missing something.

Sorry slow response, got dogpiled by life :)

edrex commented 1 year ago

it should be reinitialized to the programmatically-defined default value

I realized that keys like like ORDER probably don't actually have a default value in way-displays, and instead the compositor is providing a default (which might just come from the enumeration order from the kernel).

Thinking about this some more, I don't see a need for (programmatic) default values at all: when matched blocks change (default or no) all config from matching blocks (including default) applies, but keys that aren't explicitly set aren't applied (and the compositor decides what to do). Easy.

alex-courtis commented 1 year ago

don't actually have a default value in way-displays, and instead the compositor is providing a default (which might just come from the enumeration order from the kernel).

That's the issue. Wayland itself does little other than enable preferred mode on displays. River does nothing, sway applies a general left to right in discovered (unpredictable) order.

all config from matching blocks (including default) applies, but keys that aren't explicitly set aren't applied

That is all we can do.

alex-courtis commented 1 year ago

Okay, I see there are strong reasons to keep config backwards compatible by leaving toplevel options in place.

That's non-negotiable. Profiles/overlays are additions.

I can't speak to ease of implementation, but allowing deeply nested blocks seems confusing for users. No strong opinion as long as the use case is satisfied.

We don't have any need for nesting; one level is enough. The user can write match rules for each one. There may be a bit of repetition but that's Too Bad.

Good names are important, and worth thinking about. Overlays does tell you how they work, if you know what an overlay is, which is a nice property. I feel like it sounds like a compositor thing which might be confusing. What about CONTEXTS? Each block is a context scoped by match rules. Contexts don't imply exclusivity like profiles do. Again though, don't care much as long as the use case is satisfied. Bike shed.

Naming is always hard. Build it with a placeholder and things will become clearer during implementation. After a merge is implemented and demonstrated in a real situation it should give some insight.

I don't see a use case for having a matching rule at the top level, but maybe I'm missing something.

I don't think that adds anything but confusion. KISS: profile/context + top level or top level