TimUntersberger / nog

A tiling window manager for Windows
MIT License
697 stars 20 forks source link

Pinning Feature? #255

Closed ramirezmike closed 3 years ago

ramirezmike commented 3 years ago

Sometimes I find myself wanting a way to "pin" an application to a spot on one of my screens while I continue using nog to change workspaces "behind" it. Something like a way to size a program and then pin it in place and force it to behave as "always-on-top." I imagine this wouldn't be too difficult to do, maybe have a keybinding that triggers a program to go in and out of "pin" mode. This could be useful if you want to have a corner of your screen overlay video or a meeting while switching between workspaces.

I also thought of a secondary "pin" mode that would let you pin an application to a side of the screen and then have the nog workspace fill the rest of the screen. This would let you do something like pin a browser window/pdf on the right side of the screen but be able to switch nog workspaces on the left side.

Would something like this be a desirable feature?

keepitsane commented 3 years ago

Would this be something similar to a picture-in-picture mode? Where the "pinned" application isn't actually pushed onto the grid, but rather is kind of like a free floating window that always stays on top. Basically it would be like having an unmanaged window that stays on top when switching workspaces.

If my understanding is correct I can definitely see a use case for such a feature for my current workload.

ramirezmike commented 3 years ago

Yes, it's exactly that. I think I would even apply the manage styles onto whatever gets pinned. I might make a proof of concept branch for this because I imagine how you interact with the pinned window will be a bit of trial and error from a user experience perspective.

TimUntersberger commented 3 years ago

I might make a proof of concept branch for this because I imagine how you interact with the pinned window will be a bit of trial and error from a user experience perspective.

👍 . Can't really imagine any usecases for this atm, so having a PoC branch might be useful.

Edit:

This could be useful if you want to have a corner of your screen overlay video or a meeting while switching between workspaces.

Didn't see this line 😆

TimUntersberger commented 3 years ago

Would something like this be a desirable feature?

To be honest this only requires a few lines of code, so why not implement it even if only a few people use this?

ramirezmike commented 3 years ago

Yeah, I'm hoping to find some time this week to do it. Shouldn't be too bad.

ramirezmike commented 3 years ago

I made a pinning branch with a WIP pull request #257 that adds two keybindings: toggle_pin and toggle_view_pinned. Here's my config for it

nog.bind("Alt+Shift+P", nog.window.toggle_pin) nog.bind("Alt+P", nog.window.toggle_view_pinned)

and so, the way it works is you focus an application window (not one that is managed by nog) and hit Alt+Shift+P and it'll pin the window right where it is and it'll stay there in "always-on-top" mode while you can switch workspaces behind it. I currently have it set to always show the program's title bar so you can use that to move the pinned window. And then, the same keybinding unpins it.

The other keybinding, toggle_view_pinned, shows/hides all currently pinned windows.

I have it setup to work with work_mode and quitting nog so that you're not left with weird pinned stuff. I'm planning on adding it to the workspace.grid file too so that if nog crashes you can re-launch and be able to then unpin the apps you've pinned.

Code-wise there's a little repetition I want to address still too but it's not too bad.

Branch build here @keepitsane I'm curious what you think haha

keepitsane commented 3 years ago

@ramirezmike This is exactly how I expected this feature to work. Really great job!

The toggle all pinned windows is also a really good idea!

The only other feature I would want is to be able to auto pin certain windows when opened. Maybe this is already possible with match? I can't remember if match allows to call other functions. If it does an example of what to include in the config file would be extremely helpful.

TimUntersberger commented 3 years ago

@keepitsane omg. You are right! What if we replace rules with an event handler system.

So instead of writing the following

nog.config.rules = {
  ["notepad.exe"] = {
    ignore: true
  }
}

we could do this

nog.events.on("win_detected", function (event)
  -- event has the settings as payload so it would look like this:
  -- {
  --   type: "win_detected",
  --   payload: {
  --     ignore: false,
  --     workspace: nil,
  --     ...
  --   }
  -- }
  event.payload.ignore = true
end)

I am not too sure about how we could implement rules with such a system. The things I mentioned above are my first ideas.

We could then have more events for different things. A few examples:

Maybe a plugin could even emit custom events through here? Not too sure about this one as they could provide their own event management (Having one central place for events could be cleaner I guess)

Edit:

I will create a separate issue for this if you guys agree with the basic idea.

ramirezmike commented 3 years ago

I like the event handling idea. Might be useful, although I agree it probably should be a separate issue. Would we keep the existing match feature? wondering if I should just finish up this feature without the "pin on launch" since it would just work without extra code with the event feature.

keepitsane commented 3 years ago

I think I found a bug. If you pin an application that Nog is not set to ignore in the rules, and pin it when it is unmanaged and toggle the pinning it will be forced managed after.

Lets say I have a specific browser window that I want to pin and that I don't already have a predefined rule for to be ignored by Nog. I open the window and Nog will start managing automatically by default (this is expected behavior). I then manually unmanage that specific window using my keybind, and then pin it. I then decided to toggle the pinned windows to be hidden, and after sometime I want to bring the pinned windows back so I toggle pin on. Now the window instead of remaining pinned will be picked up by Nog and put back into managed mode and snap it back on to the grid.

I know we initial discussed the pinning feature for applications that are set to not be managed by Nog in the config, but I can still see this use case being useful. Let me know what you think?

ramirezmike commented 3 years ago

yeah, I thought about this some.. like... should pinning be handled separately from "managing"? Should an application either be pinned or managed but not both?

For me... I think if I pin a managed window, I would want it to get unmanaged and now be solely pinned. Programs should either be handled via tiling, via pinned or handled by the operating system.

I can set it up that way so that if you pin something it "pop" it off the tiling graph and if you try to manage a pinned item then it "pops" it off the pinned set. Would that make sense?

also, sorry I haven't finished this sooner, hopefully this week I can work more on it.

TimUntersberger commented 3 years ago

@ramirezmike how hard would it be to implement pinning to a workspace instead of pinning globally?

I imagine this "pinning to a workspace" feature to feel like you are containing floating windows inside a workspace basically.

ramirezmike commented 3 years ago

@ramirezmike how hard would it be to implement pinning to a workspace instead of pinning globally?

I imagine this "pinning to a workspace" feature to feel like you are containing floating windows inside a workspace basically.

I don't think that should be that bad and I dig it. I think I'll break it up into a couple features

Stretch goal is to have something where you can like hold the Alt key and then move windows by clicking and holding with your mouse or something. That would be really cool but I may end up opening that up as a separate feature.

TimUntersberger commented 3 years ago

Stretch goal is to have something where you can like hold the Alt key and then move windows by clicking and holding with your mouse or something. That would be really cool but I may end up opening that up as a separate feature.

What do you mean exactly?

ramirezmike commented 3 years ago

hold the Alt key and then move windows by clicking and holding with your mouse or something

Holding the Alt key might not be necessary, that would require some input code that checks to see if Alt key is held. It's possible but not really a requirement, it could just be a keybinding that triggers a Pinned-Window-Move mode.

move windows by clicking and holding with your mouse

This was specifically for programs that are pinned. Right now, if you pin an app, if you want to move it you have to put your mouse over the app's menu bar thing and click and hold. Alternatively, it would be nice if you could hold or press a keybinding to turn the mouse into that move cursor and let you move the app by clicking and holding anywhere within it rather than just on its menubar.

The use case of this is if you have a meeting or something that you've pinned and you switch to another workspace and want to quickly move its position.

SEPARATELY

I thought more about your pinning-to-workspace idea and thought what if you had a set of tile-managed workspaces and separately a set of pinned-floating window workspaces? Would that be useful or confusing? Like, being able to put a set of programs in a floating workspace that you can overlay on a tiling workspace. It seems like a lot to track but also maybe could be useful?

TimUntersberger commented 3 years ago

Holding the Alt key might not be necessary, that would require some input code that checks to see if Alt key is held. It's possible but not really a requirement, it could just be a keybinding that triggers a Pinned-Window-Move mode.

I think I misunderstand the way you are implementing the pin mode. Could you elaborate?

I expected the pinning to be just toggling the always on top flag on windows.

I thought more about your pinning-to-workspace idea and thought what if you had a set of tile-managed workspaces and separately a set of pinned-floating window workspaces

I don't really see a use case for this.

ramirezmike commented 3 years ago

I think I misunderstand the way you are implementing the pin mode. Could you elaborate?

I expected the pinning to be just toggling the always on top flag on windows.

Yeah, that's definitely how it works. That text about holding the Alt key was like... if the user pins a window (making it always on top) but also wanted the window to display without a taskbar, they won't be able to reposition the window (like, click + hold to drag and drop the window to another position on their monitor). My thinking was it'd be cool if we had something that let us click and drag anywhere within a floating, pinned window to drag it around the screen.

But, again, that's somewhat unnecessary for an initial implementation.

ramirezmike commented 3 years ago

I updated the PR so it's on latest and added a couple more features

I haven't updated the docs yet but this is how you can configure it in your lua config.

nog.nbind("alt+p", nog.toggle_view_pinned)
nog.nbind("alt+shift+p", nog.win_toggle_pin)

I also update the PR with a checklist of the progress on this

keepitsane commented 3 years ago

Finally got to test your changes and it addresses my problem perfectly. Thank you very much!

ramirezmike commented 3 years ago

I finally updated the PR related to this and it's in a state where I think it's "feature complete"

257

the build can be found here

With these changes, you can either pin a window globally (above all workspaces) or to specific workspaces. The latter allows you to make a workspace of just floating windows that are pinned to it, or just have pinned windows only on one workspace. Please note if you're using multiple monitors what workspace is focused when you pin to a workspace and don't get super confused like I did.

Here's how I've currently setup my config as an example

nog.nbind("alt+p", nog.toggle_view_pinned)  -- show/hide globally pinned windows
nog.nbind("alt+shift+p", function ()
    local id = nog.get_focused_win()         -- returns the ID of the window that has focus according to the OS (not nog)
    nog.win_toggle_pin(id)                        -- globally pins the window that matches the passed in ID

    if nog.win_is_pinned(id) then              -- win_is_pinned returns true or false on whether the given window is pinned
        nog.win_hide_title_bar(id)              -- this is to hide the title bar of the window. There's also win_show_title_bar
    end                                                     -- and win_hide_border/win_show_border
end)
nog.nbind("alt+o", function () 
    local ws_id = nog.get_current_ws()     -- returns the ID of the currently focused workspace
    nog.ws_toggle_view_pinned(ws_id)    -- toggles visibility of pinned windows on the given workspace
end)
nog.nbind("alt+shift+o", function ()
    local id = nog.get_focused_win()        -- same as above
    nog.win_toggle_ws_pin(id)                 -- pins the window to the currently focused workspace

    if nog.win_is_pinned(id) then
        nog.win_hide_title_bar(id)
    end
end)

I highly recommend setting up an indicator to show if anything is pinned. I did this

nog.components.custom = function (cb)   -- just a helper function to make creating indicators easier
  return {
    name = "CustomIndicator",
    render = function(display_id)
      return {{
        text = cb(display_id),
      }}
    end
  }
end

nog.config.bar.components = {
    left = { nog.components.workspaces() },
    center = { nog.components.current_window() },
    right = {
        nog.components.active_mode(),
        nog.components.padding(4),
        nog.components.custom(function ()                          -- using the helper function I declared above
            if nog.has_pinned() then                                        -- returns true if there's a globally pinned window
                return "GL_PIN"                                                 -- this can be anything
            else 
                return ""                                                             -- just an empty string if nothing is pinned
            end
        end),
        nog.components.padding(1),                                                    -- little padding between the indicators
        nog.components.custom(function (display_id)                          -- same helper function as above
            local ws_id = nog.get_focused_ws_of_display(display_id)    -- returns the active workspace ID
            if nog.ws_has_pinned(ws_id) then                                        -- returns true if the current workspace has something pinned
                return "WS_PIN"                                                               -- again, this is arbitrary
            else 
                return ""                                                                            -- empty string if the workspace has nothing pinned
            end
        end),
        nog.components.fullscreen_indicator("[]"),                             -- the rest of this is just my normal bar configuration
        nog.components.padding(1),
        nog.components.split_direction({ "|", "-" }),
        nog.components.padding(1),
        nog.components.datetime("%e %b %T"),
    }
}

Also, you can configure the rules of a program to automatically global pin

nog.config.rules = {
  ["notepad.exe"] = { 
    action = "pin"
  },

Again, the build is here

@keepitsane let me know what you think!

ramirezmike commented 3 years ago

I updated the config example in my last message because I realized today that the custom_indicator helper function should use the display_id of the bar it's on

So, small tweak

nog.components.custom = function (cb)
  return {
    name = "CustomIndicator",
    render = function(display_id)
      return {{
        text = cb(display_id),
      }}
    end
  }
end

and in the rules config

        nog.components.custom(function (display_id)
            local ws_id = nog.get_focused_ws_of_display(display_id)
            if nog.ws_has_pinned(ws_id) then
                return "WS_PIN"
            else 
                return ""
            end
        end),
ramirezmike commented 3 years ago

implemented in #257