File: oswitch.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 (262 lines) | stat: -rw-r--r-- 8,357 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#include "wayfire/plugin.hpp"
#include "wayfire/toplevel-view.hpp"
#include "wayfire/util.hpp"
#include "wayfire/view-helpers.hpp"
#include <wayfire/output.hpp>
#include <wayfire/core.hpp>
#include <wayfire/view.hpp>
#include <wayfire/output-layout.hpp>
#include <wayfire/bindings-repository.hpp>
#include <wayfire/seat.hpp>

class wayfire_oswitch : public wf::plugin_interface_t
{
    wf::wl_idle_call idle_switch_output;

    wf::output_t *get_left_output()
    {
        return get_output_in_direction(-1, 0);
    }

    wf::output_t *get_right_output()
    {
        return get_output_in_direction(1, 0);
    }

    wf::output_t *get_above_output()
    {
        return get_output_in_direction(0, -1);
    }

    wf::output_t *get_below_output()
    {
        return get_output_in_direction(0, 1);
    }

    wf::output_t *get_output_relative(int step)
    {
        /* get the target output n steps after current output
         * if current output's index is i, and if there're n monitors
         * then return the (i + step) mod n th monitor */
        auto current_output = wf::get_core().seat->get_active_output();
        auto os = wf::get_core().output_layout->get_outputs();
        auto it = std::find(os.begin(), os.end(), current_output);
        if (it == os.end())
        {
            LOGI("Current output not found in output list");
            return current_output;
        }

        int size = os.size();
        int current_index = it - os.begin();
        int target_index  = ((current_index + step) % size + size) % size;
        return os[target_index];
    }

    wf::output_t *get_output_in_direction(int dir_x, int dir_y)
    {
        auto current_output = wf::get_core().seat->get_active_output();
        if (!current_output)
        {
            return nullptr;
        }

        auto current_geo = current_output->get_layout_geometry();
        wf::point_t current_center = {
            current_geo.x + current_geo.width / 2,
            current_geo.y + current_geo.height / 2
        };

        wf::output_t *best_output = nullptr;
        double best_score = -INFINITY;

        const int MIN_OVERLAP = 20;

        for (auto& output : wf::get_core().output_layout->get_outputs())
        {
            if (output == current_output)
            {
                continue;
            }

            auto geo = output->get_layout_geometry();
            wf::point_t center = {
                geo.x + geo.width / 2,
                geo.y + geo.height / 2
            };

            double dx = center.x - current_center.x;
            double dy = center.y - current_center.y;

            if (((dir_x != 0) && (dx * dir_x <= 0)) ||
                ((dir_y != 0) && (dy * dir_y <= 0)))
            {
                continue;
            }

            double ortho_overlap = 1.0;
            if (dir_x != 0)
            {
                int current_top    = current_geo.y;
                int current_bottom = current_geo.y + current_geo.height;
                int other_top    = geo.y;
                int other_bottom = geo.y + geo.height;

                int overlap = std::min(current_bottom, other_bottom) -
                    std::max(current_top, other_top);
                ortho_overlap = (double)overlap / current_geo.height;
            } else if (dir_y != 0)
            {
                int current_left  = current_geo.x;
                int current_right = current_geo.x + current_geo.width;
                int other_left    = geo.x;
                int other_right   = geo.x + geo.width;

                int overlap = std::min(current_right, other_right) -
                    std::max(current_left, other_left);
                ortho_overlap = (double)overlap / current_geo.width;
            }

            if (ortho_overlap * 100 < MIN_OVERLAP)
            {
                continue;
            }

            double distance = sqrt(dx * dx + dy * dy);
            double score    = ortho_overlap / distance;

            if (score > best_score)
            {
                best_output = output;
                best_score  = score;
            }
        }

        return best_output ? best_output : current_output;
    }

    void switch_to_output(wf::output_t *target_output)
    {
        if (!target_output)
        {
            LOGI("No output found in requested direction. Cannot switch.");
            return;
        }

        /* when we switch the output, the oswitch keybinding
         * may be activated for the next output, which we don't want,
         * so we postpone the switch */
        idle_switch_output.run_once([=] ()
        {
            wf::get_core().seat->focus_output(target_output);
            target_output->ensure_pointer(true);
        });
    }

    void switch_to_output_with_window(wf::output_t *target_output)
    {
        auto current_output = wf::get_core().seat->get_active_output();
        auto view =
            wf::find_topmost_parent(wf::toplevel_cast(wf::get_active_view_for_output(current_output)));
        if (view)
        {
            move_view_to_output(view, target_output, true);
        }

        switch_to_output(target_output);
    }

    wf::activator_callback next_output = [=] (auto)
    {
        auto target_output = get_output_relative(1);
        switch_to_output(target_output);
        return true;
    };

    wf::activator_callback next_output_with_window = [=] (auto)
    {
        auto target_output = get_output_relative(1);
        switch_to_output_with_window(target_output);
        return true;
    };

    wf::activator_callback prev_output = [=] (auto)
    {
        auto target_output = get_output_relative(-1);
        switch_to_output(target_output);
        return true;
    };

    wf::activator_callback prev_output_with_window = [=] (auto)
    {
        auto target_output = get_output_relative(-1);
        switch_to_output_with_window(target_output);
        return true;
    };

    wf::activator_callback switch_left = [=] (auto)
    {
        auto target_output = get_left_output();
        switch_to_output(target_output);
        return true;
    };

    wf::activator_callback switch_right = [=] (auto)
    {
        auto target_output = get_right_output();
        switch_to_output(target_output);
        return true;
    };

    wf::activator_callback switch_up = [=] (auto)
    {
        auto target_output = get_above_output();
        switch_to_output(target_output);
        return true;
    };

    wf::activator_callback switch_down = [=] (auto)
    {
        auto target_output = get_below_output();
        switch_to_output(target_output);
        return true;
    };

  public:
    void init()
    {
        auto& bindings = wf::get_core().bindings;
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/next_output"},
            &next_output);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/next_output_with_win"},
            &next_output_with_window);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/prev_output"},
            &prev_output);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/prev_output_with_win"},
            &prev_output_with_window);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/left_output"},
            &switch_left);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/right_output"},
            &switch_right);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/above_output"},
            &switch_up);
        bindings->add_activator(wf::option_wrapper_t<wf::activatorbinding_t>{"oswitch/below_output"},
            &switch_down);
    }

    void fini()
    {
        auto& bindings = wf::get_core().bindings;
        bindings->rem_binding(&next_output);
        bindings->rem_binding(&next_output_with_window);
        bindings->rem_binding(&prev_output);
        bindings->rem_binding(&prev_output_with_window);
        bindings->rem_binding(&switch_left);
        bindings->rem_binding(&switch_right);
        bindings->rem_binding(&switch_up);
        bindings->rem_binding(&switch_down);
        idle_switch_output.disconnect();
    }
};

DECLARE_WAYFIRE_PLUGIN(wayfire_oswitch);