1 /* 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2020 Ilia Bozhinov 5 * Copyright (c) 2020 Scott Moreau 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a copy 8 * of this software and associated documentation files (the "Software"), to deal 9 * in the Software without restriction, including without limitation the rights 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 * copies of the Software, and to permit persons to whom the Software is 12 * furnished to do so, subject to the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included in all 15 * copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 * SOFTWARE. 24 */ 25 26 #include <map> 27 #include <wayfire/core.hpp> 28 #include <wayfire/plugin.hpp> 29 #include <wayfire/output.hpp> 30 #include <wayfire/view-transform.hpp> 31 #include <wayfire/compositor-view.hpp> 32 #include <wayfire/workspace-manager.hpp> 33 #include <wayfire/signal-definitions.hpp> 34 35 36 class fullscreen_subsurface : public wf::surface_interface_t, 37 public wf::compositor_surface_t 38 { 39 public: 40 bool _mapped = true; 41 fullscreen_subsurface(wayfire_view view)42 fullscreen_subsurface(wayfire_view view) : 43 wf::surface_interface_t(), wf::compositor_surface_t() 44 {} 45 ~fullscreen_subsurface()46 ~fullscreen_subsurface() 47 {} 48 on_pointer_enter(int x,int y)49 void on_pointer_enter(int x, int y) override 50 { 51 wf::get_core().set_cursor("default"); 52 } 53 accepts_input(int32_t sx,int32_t sy)54 bool accepts_input(int32_t sx, int32_t sy) override 55 { 56 return wlr_box{0, 0, 1, 1} & wf::point_t{sx, sy}; 57 } 58 is_mapped() const59 bool is_mapped() const override 60 { 61 return _mapped; 62 } 63 get_offset()64 wf::point_t get_offset() override 65 { 66 return {-1, 0}; 67 } 68 get_size() const69 wf::dimensions_t get_size() const override 70 { 71 return {1, 1}; 72 } 73 simple_render(const wf::framebuffer_t & fb,int x,int y,const wf::region_t & damage)74 void simple_render(const wf::framebuffer_t& fb, int x, int y, 75 const wf::region_t& damage) override 76 { 77 /* fully transparent */ } 78 }; 79 80 class fullscreen_transformer : public wf::view_2D 81 { 82 public: 83 wayfire_view view; 84 wlr_box transformed_view_box; 85 wf::option_wrapper_t<bool> transparent_behind_views{ 86 "force-fullscreen/transparent_behind_views"}; 87 fullscreen_transformer(wayfire_view view)88 fullscreen_transformer(wayfire_view view) : wf::view_2D(view) 89 { 90 this->view = view; 91 } 92 ~fullscreen_transformer()93 ~fullscreen_transformer() 94 {} 95 get_workspace(wlr_box og)96 wf::point_t get_workspace(wlr_box og) 97 { 98 wf::point_t ws; 99 auto vg = view->get_wm_geometry(); 100 auto tg = transformed_view_box; 101 wf::pointf_t center{vg.x + tg.width / 2.0, vg.y + tg.height / 2.0}; 102 103 ws.x = std::floor(center.x / og.width); 104 ws.y = std::floor(center.y / og.height); 105 106 return ws; 107 } 108 109 /* TODO: transform_point */ get_bounding_box(wf::geometry_t geometry,wf::geometry_t box)110 wf::geometry_t get_bounding_box( 111 wf::geometry_t geometry, wf::geometry_t box) override 112 { 113 auto output = view->get_output(); 114 auto bbox = wf::view_2D::get_bounding_box(geometry, box); 115 116 if (!output) 117 { 118 return bbox; 119 } 120 121 wf::geometry_t wm = view->get_wm_geometry(); 122 wf::point_t subsurface = {wm.x - 1, wm.y}; 123 auto og = output->get_relative_geometry(); 124 auto ws = get_workspace(og); 125 if (box & subsurface) 126 { 127 og.x += ws.x * og.width; 128 og.y += ws.y * og.height; 129 130 return og; 131 } else 132 { 133 bbox.x += ws.x * og.width; 134 bbox.y += ws.y * og.height; 135 136 return bbox; 137 } 138 } 139 get_relative_transformed_view(wlr_box & og)140 wlr_box get_relative_transformed_view(wlr_box& og) 141 { 142 auto ws = get_workspace(og); 143 auto bbox = transformed_view_box; 144 bbox.x += ws.x * og.width; 145 bbox.y += ws.y * og.height; 146 og.x += ws.x * og.width; 147 og.y += ws.y * og.height; 148 149 return bbox; 150 } 151 untransform_point(wf::geometry_t geometry,wf::pointf_t point)152 wf::pointf_t untransform_point( 153 wf::geometry_t geometry, wf::pointf_t point) override 154 { 155 auto output = view->get_output(); 156 auto default_point = wf::view_2D::untransform_point(geometry, point); 157 158 if (!output) 159 { 160 return default_point; 161 } 162 163 auto og = output->get_relative_geometry(); 164 auto bbox = get_relative_transformed_view(og); 165 166 if (!(bbox & point)) 167 { 168 auto region = ((wf::region_t{og} ^ wf::region_t{bbox}) & 169 wf::region_t{{(int)point.x, (int)point.y, 1, 1}}); 170 if (!region.empty()) 171 { 172 auto wm = view->get_wm_geometry(); 173 174 return wf::pointf_t{wm.x - 1.0, wm.y * 1.0}; 175 } 176 } 177 178 return default_point; 179 } 180 render_box(wf::texture_t src_tex,wlr_box src_box,wlr_box scissor_box,const wf::framebuffer_t & target_fb)181 void render_box(wf::texture_t src_tex, wlr_box src_box, 182 wlr_box scissor_box, const wf::framebuffer_t& target_fb) override 183 { 184 auto output = view->get_output(); 185 186 if (!output) 187 { 188 return; 189 } 190 191 auto og = output->get_relative_geometry(); 192 wf::region_t scissor_region{scissor_box}; 193 194 if (transparent_behind_views) 195 { 196 auto bbox = get_relative_transformed_view(og); 197 scissor_region ^= wf::region_t{bbox}; 198 } 199 200 for (auto& b : scissor_region) 201 { 202 OpenGL::render_begin(target_fb); 203 target_fb.logic_scissor(wlr_box_from_pixman_box(b)); 204 OpenGL::clear({0, 0, 0, 1}); 205 OpenGL::render_end(); 206 } 207 208 wf::view_2D::render_box(src_tex, src_box, scissor_box, target_fb); 209 } 210 }; 211 212 class fullscreen_background 213 { 214 public: 215 wf::geometry_t saved_geometry; 216 wf::geometry_t undecorated_geometry; 217 fullscreen_transformer *transformer; 218 fullscreen_subsurface *black_border = nullptr; 219 fullscreen_background(wayfire_view view)220 fullscreen_background(wayfire_view view) 221 {} 222 ~fullscreen_background()223 ~fullscreen_background() 224 {} 225 }; 226 227 class wayfire_force_fullscreen; 228 229 std::map<wf::output_t*, 230 wayfire_force_fullscreen*> wayfire_force_fullscreen_instances; 231 232 class wayfire_force_fullscreen : public wf::plugin_interface_t 233 { 234 std::string background_name; 235 bool motion_connected = false; 236 std::map<wayfire_view, std::unique_ptr<fullscreen_background>> backgrounds; 237 wf::option_wrapper_t<bool> preserve_aspect{"force-fullscreen/preserve_aspect"}; 238 wf::option_wrapper_t<bool> constrain_pointer{"force-fullscreen/constrain_pointer"}; 239 wf::option_wrapper_t<double> scale_x_factor{"force-fullscreen/x_skew"}; 240 wf::option_wrapper_t<double> scale_y_factor{"force-fullscreen/y_skew"}; 241 wf::option_wrapper_t<std::string> constraint_area{ 242 "force-fullscreen/constraint_area"}; 243 wf::option_wrapper_t<bool> transparent_behind_views{ 244 "force-fullscreen/transparent_behind_views"}; 245 wf::option_wrapper_t<wf::keybinding_t> key_toggle_fullscreen{ 246 "force-fullscreen/key_toggle_fullscreen"}; 247 248 public: init()249 void init() override 250 { 251 this->grab_interface->name = "force-fullscreen"; 252 this->grab_interface->capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR; 253 background_name = this->grab_interface->name; 254 255 output->add_key(key_toggle_fullscreen, &on_toggle_fullscreen); 256 transparent_behind_views.set_callback(option_changed); 257 wayfire_force_fullscreen_instances[output] = this; 258 constrain_pointer.set_callback(constrain_pointer_option_changed); 259 preserve_aspect.set_callback(option_changed); 260 scale_x_factor.set_callback(skew_changed); 261 scale_y_factor.set_callback(skew_changed); 262 } 263 ensure_subsurface(wayfire_view view)264 void ensure_subsurface(wayfire_view view) 265 { 266 auto pair = backgrounds.find(view); 267 268 if (pair == backgrounds.end()) 269 { 270 return; 271 } 272 273 auto& background = pair->second; 274 275 if (!background->black_border) 276 { 277 std::unique_ptr<fullscreen_subsurface> subsurface = 278 std::make_unique<fullscreen_subsurface>(view); 279 nonstd::observer_ptr<fullscreen_subsurface> ptr{subsurface}; 280 view->add_subsurface(std::move(subsurface), true); 281 background->black_border = ptr.get(); 282 } 283 } 284 destroy_subsurface(wayfire_view view)285 void destroy_subsurface(wayfire_view view) 286 { 287 auto pair = backgrounds.find(view); 288 289 if (pair == backgrounds.end()) 290 { 291 return; 292 } 293 294 auto& background = pair->second; 295 296 if (background->black_border) 297 { 298 wf::emit_map_state_change(background->black_border); 299 background->black_border->_mapped = false; 300 view->remove_subsurface(background->black_border); 301 background->black_border = nullptr; 302 } 303 } 304 setup_transform(wayfire_view view)305 void setup_transform(wayfire_view view) 306 { 307 auto og = output->get_relative_geometry(); 308 auto vg = view->get_wm_geometry(); 309 310 double scale_x = (double)og.width / vg.width; 311 double scale_y = (double)og.height / vg.height; 312 double translation_x = (og.width - vg.width) / 2.0; 313 double translation_y = (og.height - vg.height) / 2.0; 314 315 if (preserve_aspect) 316 { 317 scale_x = scale_y = std::min(scale_x, scale_y); 318 } 319 320 scale_x += (scale_x_factor * 0.01); 321 scale_y += (scale_y_factor * 0.01); 322 323 wlr_box box; 324 box.width = std::floor((vg.width - 2) * scale_x); 325 box.height = std::floor((vg.height - 2) * scale_y); 326 box.x = std::ceil((og.width - box.width) / 2.0); 327 box.y = std::ceil((og.height - box.height) / 2.0); 328 329 if (preserve_aspect) 330 { 331 ensure_subsurface(view); 332 scale_x += 1.0 / vg.width; 333 translation_x -= 1.0; 334 } else 335 { 336 destroy_subsurface(view); 337 } 338 339 backgrounds[view]->transformer->transformed_view_box = box; 340 backgrounds[view]->transformer->scale_x = scale_x; 341 backgrounds[view]->transformer->scale_y = scale_y; 342 backgrounds[view]->transformer->translation_x = translation_x; 343 backgrounds[view]->transformer->translation_y = translation_y; 344 345 view->damage(); 346 } 347 update_backgrounds()348 void update_backgrounds() 349 { 350 for (auto& b : backgrounds) 351 { 352 destroy_subsurface(b.first); 353 setup_transform(b.first); 354 } 355 } 356 toggle_fullscreen(wayfire_view view)357 bool toggle_fullscreen(wayfire_view view) 358 { 359 if (!output->can_activate_plugin(grab_interface)) 360 { 361 return false; 362 } 363 364 wlr_box saved_geometry = view->get_wm_geometry(); 365 366 auto background = backgrounds.find(view); 367 bool fullscreen = background == backgrounds.end() ? true : false; 368 369 view->set_fullscreen(fullscreen); 370 371 wlr_box undecorated_geometry = view->get_wm_geometry(); 372 373 if (!fullscreen) 374 { 375 deactivate(view); 376 377 return true; 378 } 379 380 activate(view); 381 382 background = backgrounds.find(view); 383 if (background == backgrounds.end()) 384 { 385 /* Should never happen */ 386 deactivate(view); 387 388 return true; 389 } 390 391 background->second->undecorated_geometry = undecorated_geometry; 392 background->second->saved_geometry = saved_geometry; 393 394 setup_transform(view); 395 396 return true; 397 } 398 399 wf::key_callback on_toggle_fullscreen = [=] (auto) __anon60bb74470102(auto) 400 { 401 auto view = output->get_active_view(); 402 403 if (!view || (view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT)) 404 { 405 return false; 406 } 407 408 return toggle_fullscreen(view); 409 }; 410 activate(wayfire_view view)411 void activate(wayfire_view view) 412 { 413 view->move(0, 0); 414 backgrounds[view] = std::make_unique<fullscreen_background>(view); 415 backgrounds[view]->transformer = new fullscreen_transformer(view); 416 view->add_transformer(std::unique_ptr<fullscreen_transformer>(backgrounds[ 417 view]->transformer), background_name); 418 output->connect_signal("output-configuration-changed", 419 &output_config_changed); 420 wf::get_core().connect_signal("view-pre-moved-to-output", 421 &view_output_changed); 422 output->connect_signal("view-fullscreen-request", &view_fullscreened); 423 view->connect_signal("geometry-changed", &view_geometry_changed); 424 output->connect_signal("view-unmapped", &view_unmapped); 425 output->connect_signal("view-focused", &view_focused); 426 if (constrain_pointer) 427 { 428 connect_motion_signal(); 429 } 430 } 431 deactivate(wayfire_view view)432 void deactivate(wayfire_view view) 433 { 434 auto background = backgrounds.find(view); 435 436 if (background == backgrounds.end()) 437 { 438 return; 439 } 440 441 if (backgrounds.size() == 1) 442 { 443 view_geometry_changed.disconnect(); 444 output_config_changed.disconnect(); 445 view_output_changed.disconnect(); 446 view_fullscreened.disconnect(); 447 view_unmapped.disconnect(); 448 disconnect_motion_signal(); 449 view_focused.disconnect(); 450 } 451 452 auto og = output->get_relative_geometry(); 453 auto ws = background->second->transformer->get_workspace(og); 454 view->move( 455 background->second->saved_geometry.x + ws.x * og.width, 456 background->second->saved_geometry.y + ws.y * og.height); 457 if (view->get_transformer(background_name)) 458 { 459 view->pop_transformer(background_name); 460 } 461 462 destroy_subsurface(view); 463 backgrounds.erase(view); 464 } 465 connect_motion_signal()466 void connect_motion_signal() 467 { 468 if (motion_connected) 469 { 470 return; 471 } 472 473 wf::get_core().connect_signal("pointer_motion", &on_motion_event); 474 motion_connected = true; 475 } 476 disconnect_motion_signal()477 void disconnect_motion_signal() 478 { 479 if (!motion_connected) 480 { 481 return; 482 } 483 484 wf::get_core().disconnect_signal("pointer_motion", &on_motion_event); 485 motion_connected = false; 486 } 487 update_motion_signal(wayfire_view view)488 void update_motion_signal(wayfire_view view) 489 { 490 if (view && (view->get_output() == output) && constrain_pointer && 491 (backgrounds.find(view) != backgrounds.end())) 492 { 493 connect_motion_signal(); 494 495 return; 496 } 497 498 disconnect_motion_signal(); 499 } 500 501 wf::config::option_base_t::updated_callback_t constrain_pointer_option_changed = 502 [=] () __anon60bb74470202() 503 { 504 auto view = output->get_active_view(); 505 506 update_motion_signal(view); 507 }; 508 509 wf::config::option_base_t::updated_callback_t option_changed = [=] () __anon60bb74470302() 510 { 511 update_backgrounds(); 512 }; 513 514 wf::config::option_base_t::updated_callback_t skew_changed = 515 [=] () __anon60bb74470402() 516 { 517 for (auto& b : backgrounds) 518 { 519 auto v = b.first; 520 setup_transform(v); 521 } 522 }; 523 524 wf::signal_callback_t on_motion_event = [=] (wf::signal_data_t *data) __anon60bb74470502(wf::signal_data_t *data) 525 { 526 auto ev = static_cast< 527 wf::input_event_signal<wlr_event_pointer_motion>*>(data); 528 529 if (wf::get_core().get_active_output() != output) 530 { 531 return; 532 } 533 534 if (!output->can_activate_plugin(grab_interface)) 535 { 536 return; 537 } 538 539 auto cursor = wf::get_core().get_cursor_position(); 540 auto last_cursor = cursor; 541 auto og = output->get_layout_geometry(); 542 543 cursor.x += ev->event->delta_x; 544 cursor.y += ev->event->delta_y; 545 546 for (auto& b : backgrounds) 547 { 548 auto view = output->get_active_view(); 549 wlr_box box; 550 551 box = b.second->transformer->transformed_view_box; 552 box.x += og.x; 553 box.y += og.y; 554 555 if (std::string(constraint_area) == "output") 556 { 557 box = og; 558 } 559 560 if ((b.first == view) && 561 !(box & wf::pointf_t{cursor.x, cursor.y})) 562 { 563 wlr_box_closest_point(&box, cursor.x, cursor.y, 564 &cursor.x, &cursor.y); 565 ev->event->delta_x = ev->event->unaccel_dx = 566 cursor.x - last_cursor.x; 567 ev->event->delta_y = ev->event->unaccel_dy = 568 cursor.y - last_cursor.y; 569 570 return; 571 } 572 } 573 }; 574 575 wf::signal_connection_t output_config_changed{[this] (wf::signal_data_t *data) __anon60bb74470602() 576 { 577 wf::output_configuration_changed_signal *signal = 578 static_cast<wf::output_configuration_changed_signal*>(data); 579 580 if (!signal->changed_fields) 581 { 582 return; 583 } 584 585 if (signal->changed_fields & wf::OUTPUT_SOURCE_CHANGE) 586 { 587 return; 588 } 589 590 update_backgrounds(); 591 } 592 }; 593 594 wf::signal_connection_t view_output_changed{[this] (wf::signal_data_t *data) __anon60bb74470702() 595 { 596 auto signal = static_cast<wf::view_pre_moved_to_output_signal*>(data); 597 auto view = signal->view; 598 auto background = backgrounds.find(view); 599 600 if (background == backgrounds.end()) 601 { 602 return; 603 } 604 605 toggle_fullscreen(view); 606 607 auto instance = wayfire_force_fullscreen_instances[signal->new_output]; 608 instance->toggle_fullscreen(view); 609 } 610 }; 611 612 wf::signal_connection_t view_focused{[this] (wf::signal_data_t *data) __anon60bb74470802() 613 { 614 auto view = get_signaled_view(data); 615 616 update_motion_signal(view); 617 } 618 }; 619 620 wf::signal_connection_t view_unmapped{[this] (wf::signal_data_t *data) __anon60bb74470902() 621 { 622 auto view = get_signaled_view(data); 623 auto background = backgrounds.find(view); 624 625 if (background == backgrounds.end()) 626 { 627 return; 628 } 629 630 toggle_fullscreen(view); 631 } 632 }; 633 634 wf::signal_connection_t view_fullscreened{[this] (wf::signal_data_t *data) __anon60bb74470a02() 635 { 636 auto signal = static_cast<wf::view_fullscreen_signal*>(data); 637 auto view = signal->view; 638 auto background = backgrounds.find(view); 639 640 if (background == backgrounds.end()) 641 { 642 return; 643 } 644 645 if (signal->state || signal->carried_out) 646 { 647 return; 648 } 649 650 toggle_fullscreen(view); 651 652 signal->carried_out = true; 653 } 654 }; 655 656 wf::signal_connection_t view_geometry_changed{[this] (wf::signal_data_t *data) __anon60bb74470b02() 657 { 658 auto view = get_signaled_view(data); 659 auto background = backgrounds.find(view); 660 661 if (background == backgrounds.end()) 662 { 663 return; 664 } 665 666 view->resize( 667 background->second->undecorated_geometry.width, 668 background->second->undecorated_geometry.height); 669 setup_transform(view); 670 } 671 }; 672 fini()673 void fini() override 674 { 675 output->rem_binding(&on_toggle_fullscreen); 676 wayfire_force_fullscreen_instances.erase(output); 677 678 for (auto& b : backgrounds) 679 { 680 toggle_fullscreen(b.first); 681 } 682 } 683 }; 684 685 DECLARE_WAYFIRE_PLUGIN(wayfire_force_fullscreen); 686