File: preserve-output.cpp

package info (click to toggle)
wayfire 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,764 kB
  • sloc: cpp: 52,464; xml: 2,987; ansic: 699; makefile: 161
file content (131 lines) | stat: -rw-r--r-- 4,201 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "wayfire/core.hpp"
#include "wayfire/debug.hpp"
#include "wayfire/plugin.hpp"
#include <wayfire/workspace-set.hpp>
#include <wayfire/signal-definitions.hpp>
#include <wayfire/output.hpp>
#include <wayfire/seat.hpp>
#include <wayfire/output-layout.hpp>
#include <wayfire/nonstd/wlroots-full.hpp>
#include <chrono>

namespace wf
{
namespace preserve_output
{
static std::string make_output_identifier(wf::output_t *output)
{
    std::string identifier = "";
    identifier += nonull(output->handle->make);
    identifier += "|";
    identifier += nonull(output->handle->model);
    identifier += "|";
    identifier += nonull(output->handle->serial);
    return identifier;
}

struct per_output_state_t
{
    std::shared_ptr<wf::workspace_set_t> workspace_set;
    std::chrono::time_point<std::chrono::steady_clock> destroy_timestamp;
    bool was_focused = false;
};

class preserve_output_t : public wf::plugin_interface_t
{
    wf::option_wrapper_t<int> last_output_focus_timeout{"preserve-output/last_output_focus_timeout"};
    std::map<std::string, per_output_state_t> saved_outputs;

    bool focused_output_expired(const per_output_state_t& state) const
    {
        using namespace std::chrono;
        const auto now = steady_clock::now();
        const auto elapsed_since_focus = duration_cast<milliseconds>(now - state.destroy_timestamp).count();
        return elapsed_since_focus > last_output_focus_timeout;
    }

    void save_output(wf::output_t *output)
    {
        auto ident = make_output_identifier(output);
        auto& data = saved_outputs[ident];

        data.was_focused = (output == wf::get_core().seat->get_active_output());
        data.destroy_timestamp = std::chrono::steady_clock::now();
        data.workspace_set     = output->wset();

        LOGD("Saving workspace set ", data.workspace_set->get_index(), " from output ", output->to_string(),
            " with identifier ", ident);

        // Set a dummy workspace set with no views at all.
        output->set_workspace_set(wf::workspace_set_t::create());

        // Detach workspace set from its old output
        data.workspace_set->attach_to_output(nullptr);
    }

    void try_restore_output(wf::output_t *output)
    {
        std::string ident = make_output_identifier(output);
        if (!saved_outputs.count(ident))
        {
            LOGD("No saved identifier for ", output->to_string());
            return;
        }

        auto& data = saved_outputs[ident];

        auto new_output = data.workspace_set->get_attached_output();
        if (new_output && (new_output->wset() == data.workspace_set))
        {
            // The wset was moved to a different output => We should leave it where it is
            LOGD("Saved workspace for ", output->to_string(), " has been remapped to another output.");
            return;
        }

        LOGD("Restoring workspace set ", data.workspace_set->get_index(), " to output ", output->to_string());
        output->set_workspace_set(data.workspace_set);
        if (data.was_focused && !focused_output_expired(data))
        {
            wf::get_core().seat->focus_output(output);
        }

        saved_outputs.erase(ident);
    }

    wf::signal::connection_t<output_pre_remove_signal> output_pre_remove = [=] (output_pre_remove_signal *ev)
    {
        if (wlr_output_is_headless(ev->output->handle))
        {
            // For example, NOOP-1
            return;
        }

        if (wf::get_core().get_current_state() == compositor_state_t::RUNNING)
        {
            LOGD("Received pre-remove event: ", ev->output->to_string());
            save_output(ev->output);
        }
    };

    wf::signal::connection_t<output_added_signal> on_new_output = [=] (output_added_signal *ev)
    {
        if (wlr_output_is_headless(ev->output->handle))
        {
            // For example, NOOP-1
            return;
        }

        try_restore_output(ev->output);
    };

  public:
    void init() override
    {
        wf::get_core().output_layout->connect(&on_new_output);
        wf::get_core().output_layout->connect(&output_pre_remove);
    }
};
}
}

DECLARE_WAYFIRE_PLUGIN(wf::preserve_output::preserve_output_t);