diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c index 1734ff729..58a2d81bb 100644 --- a/src/compositor/meta-window-actor.c +++ b/src/compositor/meta-window-actor.c @@ -817,6 +817,13 @@ meta_window_actor_has_shadow (MetaWindowActor *self) meta_window_is_fullscreen (priv->window)) return FALSE; + /* + * If we have two snap-tiled windows, we don't want the shadow to obstruct + * the other window. + */ + if (meta_window_get_tile_match (priv->window)) + return FALSE; + /* * Always put a shadow around windows with a frame - This should override * the restriction about not putting a shadow around ARGB windows. diff --git a/src/core/constraints.c b/src/core/constraints.c index 9247ce6cd..7b237976d 100644 --- a/src/core/constraints.c +++ b/src/core/constraints.c @@ -152,6 +152,10 @@ static gboolean constrain_maximization (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, gboolean check_only); +static gboolean constrain_tiling (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); static gboolean constrain_fullscreen (MetaWindow *window, ConstraintInfo *info, ConstraintPriority priority, @@ -209,6 +213,7 @@ typedef struct { static const Constraint all_constraints[] = { {constrain_modal_dialog, "constrain_modal_dialog"}, {constrain_maximization, "constrain_maximization"}, + {constrain_tiling, "constrain_tiling"}, {constrain_fullscreen, "constrain_fullscreen"}, {constrain_size_increments, "constrain_size_increments"}, {constrain_size_limits, "constrain_size_limits"}, @@ -696,11 +701,16 @@ constrain_maximization (MetaWindow *window, return TRUE; /* Determine whether constraint applies; exit if it doesn't */ - if (!window->maximized_horizontally && !window->maximized_vertically) + if ((!window->maximized_horizontally && !window->maximized_vertically) || + META_WINDOW_TILED_SIDE_BY_SIDE (window)) return TRUE; /* Calculate target_size = maximized size of (window + frame) */ - if (META_WINDOW_MAXIMIZED (window)) + if (META_WINDOW_TILED_MAXIMIZED (window)) + { + meta_window_get_current_tile_area (window, &target_size); + } + else if (META_WINDOW_MAXIMIZED (window)) { target_size = info->work_area_monitor; } @@ -763,6 +773,58 @@ constrain_maximization (MetaWindow *window, return TRUE; } +static gboolean +constrain_tiling (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle target_size; + MetaRectangle min_size, max_size; + gboolean hminbad, vminbad; + gboolean horiz_equal, vert_equal; + gboolean constraint_already_satisfied; + + if (priority > PRIORITY_TILING) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + return TRUE; + + /* Calculate target_size - as the tile previews need this as well, we + * use an external function for the actual calculation + */ + meta_window_get_current_tile_area (window, &target_size); + + /* Check min size constraints; max size constraints are ignored as for + * maximized windows. + */ + get_size_limits (window, &min_size, &max_size); + hminbad = target_size.width < min_size.width; + vminbad = target_size.height < min_size.height; + if (hminbad || vminbad) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + horiz_equal = target_size.x == info->current.x && + target_size.width == info->current.width; + vert_equal = target_size.y == info->current.y && + target_size.height == info->current.height; + constraint_already_satisfied = horiz_equal && vert_equal; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + info->current.x = target_size.x; + info->current.width = target_size.width; + info->current.y = target_size.y; + info->current.height = target_size.height; + + return TRUE; +} + + static gboolean constrain_fullscreen (MetaWindow *window, ConstraintInfo *info, @@ -815,6 +877,7 @@ constrain_size_increments (MetaWindow *window, /* Determine whether constraint applies; exit if it doesn't */ if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || + META_WINDOW_TILED_SIDE_BY_SIDE (window) || info->action_type == ACTION_MOVE) return TRUE; @@ -957,6 +1020,7 @@ constrain_aspect_ratio (MetaWindow *window, constraints_are_inconsistent = minr > maxr; if (constraints_are_inconsistent || META_WINDOW_MAXIMIZED (window) || window->fullscreen || + META_WINDOW_TILED_SIDE_BY_SIDE (window) || info->action_type == ACTION_MOVE) return TRUE; diff --git a/src/core/display-private.h b/src/core/display-private.h index a90ade254..29a3ccf10 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -73,6 +73,13 @@ typedef enum { */ #define N_IGNORED_CROSSING_SERIALS 10 +typedef enum { + META_TILE_NONE, + META_TILE_LEFT, + META_TILE_RIGHT, + META_TILE_MAXIMIZED +} MetaTileMode; + typedef enum { /* Normal interaction where you're interacting with windows. * Events go to windows normally. */ @@ -203,6 +210,8 @@ struct _MetaDisplay int grab_anchor_root_x; int grab_anchor_root_y; MetaRectangle grab_anchor_window_pos; + MetaTileMode grab_tile_mode; + int grab_tile_monitor_number; int grab_latest_motion_x; int grab_latest_motion_y; guint grab_have_pointer : 1; diff --git a/src/core/display.c b/src/core/display.c index 062a0c308..dbfbc68f0 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -663,6 +663,8 @@ meta_display_open (void) display->grab_op = META_GRAB_OP_NONE; display->grab_window = NULL; + display->grab_tile_mode = META_TILE_NONE; + display->grab_tile_monitor_number = -1; display->grab_edge_resistance_data = NULL; @@ -1909,6 +1911,8 @@ meta_display_begin_grab_op (MetaDisplay *display, display->grab_op = op; display->grab_window = grab_window; display->grab_button = button; + display->grab_tile_mode = grab_window->tile_mode; + display->grab_tile_monitor_number = grab_window->tile_monitor_number; display->grab_anchor_root_x = root_x; display->grab_anchor_root_y = root_y; display->grab_latest_motion_x = root_x; @@ -2006,6 +2010,8 @@ meta_display_end_grab_op (MetaDisplay *display, display->event_route = META_EVENT_ROUTE_NORMAL; display->grab_window = NULL; + display->grab_tile_mode = META_TILE_NONE; + display->grab_tile_monitor_number = -1; meta_display_update_cursor (display); diff --git a/src/core/frame.c b/src/core/frame.c index c4be91a89..9335fb629 100644 --- a/src/core/frame.c +++ b/src/core/frame.c @@ -274,6 +274,12 @@ meta_frame_get_flags (MetaFrame *frame) if (META_WINDOW_MAXIMIZED (frame->window)) flags |= META_FRAME_MAXIMIZED; + if (META_WINDOW_TILED_LEFT (frame->window)) + flags |= META_FRAME_TILED_LEFT; + + if (META_WINDOW_TILED_RIGHT (frame->window)) + flags |= META_FRAME_TILED_RIGHT; + if (frame->window->fullscreen) flags |= META_FRAME_FULLSCREEN; diff --git a/src/core/keybindings.c b/src/core/keybindings.c index adeb9bd8e..ddbab7043 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -2001,13 +2001,23 @@ process_mouse_move_resize_grab (MetaDisplay *display, if (event->keyval == CLUTTER_KEY_Escape) { + /* Hide the tiling preview if necessary */ + if (window->tile_mode != META_TILE_NONE) + meta_screen_hide_tile_preview (screen); + + /* Restore the original tile mode */ + window->tile_mode = display->grab_tile_mode; + window->tile_monitor_number = display->grab_tile_monitor_number; + /* End move or resize and restore to original state. If the * window was a maximized window that had been "shaken loose" we * need to remaximize it. In normal cases, we need to do a * moveresize now to get the position back to the original. */ - if (window->shaken_loose) + if (window->shaken_loose || window->tile_mode == META_TILE_MAXIMIZED) meta_window_maximize (window, META_MAXIMIZE_BOTH); + else if (window->tile_mode != META_TILE_NONE) + meta_window_tile (window); else meta_window_move_resize_frame (display->grab_window, TRUE, @@ -2929,23 +2939,41 @@ handle_toggle_above (MetaDisplay *display, } static void -handle_toggle_tiled_left (MetaDisplay *display, - MetaScreen *screen, - MetaWindow *window, - ClutterKeyEvent *event, - MetaKeyBinding *binding, - gpointer dummy) +handle_toggle_tiled (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + ClutterKeyEvent *event, + MetaKeyBinding *binding, + gpointer dummy) { -} + MetaTileMode mode = binding->handler->data; -static void -handle_toggle_tiled_right (MetaDisplay *display, - MetaScreen *screen, - MetaWindow *window, - ClutterKeyEvent *event, - MetaKeyBinding *binding, - gpointer dummy) -{ + if ((META_WINDOW_TILED_LEFT (window) && mode == META_TILE_LEFT) || + (META_WINDOW_TILED_RIGHT (window) && mode == META_TILE_RIGHT)) + { + window->tile_monitor_number = window->saved_maximize ? window->monitor->number + : -1; + window->tile_mode = window->saved_maximize ? META_TILE_MAXIMIZED + : META_TILE_NONE; + + if (window->saved_maximize) + meta_window_maximize (window, META_MAXIMIZE_BOTH); + else + meta_window_unmaximize (window, META_MAXIMIZE_BOTH); + } + else if (meta_window_can_tile_side_by_side (window)) + { + window->tile_monitor_number = window->monitor->number; + window->tile_mode = mode; + /* Maximization constraints beat tiling constraints, so if the window + * is maximized, tiling won't have any effect unless we unmaximize it + * horizontally first; rather than calling meta_window_unmaximize(), + * we just set the flag and rely on meta_window_tile() syncing it to + * save an additional roundtrip. + */ + window->maximized_horizontally = FALSE; + meta_window_tile (window); + } } static void @@ -3678,14 +3706,14 @@ init_builtin_key_bindings (MetaDisplay *display) mutter_keybindings, META_KEY_BINDING_PER_WINDOW, META_KEYBINDING_ACTION_TOGGLE_TILED_LEFT, - handle_toggle_tiled_left, 0); + handle_toggle_tiled, META_TILE_LEFT); add_builtin_keybinding (display, "toggle-tiled-right", mutter_keybindings, META_KEY_BINDING_PER_WINDOW, META_KEYBINDING_ACTION_TOGGLE_TILED_RIGHT, - handle_toggle_tiled_right, 0); + handle_toggle_tiled, META_TILE_RIGHT); add_builtin_keybinding (display, "toggle-above", diff --git a/src/core/screen.c b/src/core/screen.c index 14193d41b..19f7db5e2 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -1320,6 +1320,46 @@ meta_screen_set_cursor (MetaScreen *screen, static gboolean meta_screen_update_tile_preview_timeout (gpointer data) { + MetaScreen *screen = data; + MetaWindow *window = screen->display->grab_window; + gboolean needs_preview = FALSE; + + screen->tile_preview_timeout_id = 0; + + if (window) + { + switch (window->tile_mode) + { + case META_TILE_LEFT: + case META_TILE_RIGHT: + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + needs_preview = TRUE; + break; + + case META_TILE_MAXIMIZED: + if (!META_WINDOW_MAXIMIZED (window)) + needs_preview = TRUE; + break; + + default: + needs_preview = FALSE; + break; + } + } + + if (needs_preview) + { + MetaRectangle tile_rect; + int monitor; + + monitor = meta_window_get_current_tile_monitor_number (window); + meta_window_get_current_tile_area (window, &tile_rect); + meta_compositor_show_tile_preview (screen->display->compositor, + window, &tile_rect, monitor); + } + else + meta_compositor_hide_tile_preview (screen->display->compositor); + return FALSE; } diff --git a/src/core/stack.c b/src/core/stack.c index d8e4eb771..a3879f760 100644 --- a/src/core/stack.c +++ b/src/core/stack.c @@ -117,6 +117,7 @@ meta_stack_add (MetaStack *stack, window->desc, window->stack_position); stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } void @@ -152,6 +153,7 @@ meta_stack_remove (MetaStack *stack, GUINT_TO_POINTER (window->frame->xwindow)); stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } void @@ -161,6 +163,7 @@ meta_stack_update_layer (MetaStack *stack, stack->need_relayer = TRUE; stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } void @@ -170,6 +173,7 @@ meta_stack_update_transient (MetaStack *stack, stack->need_constrain = TRUE; stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } /* raise/lower within a layer */ @@ -198,6 +202,7 @@ meta_stack_raise (MetaStack *stack, meta_window_set_stack_position_no_sync (window, max_stack_position); stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } void @@ -225,6 +230,7 @@ meta_stack_lower (MetaStack *stack, meta_window_set_stack_position_no_sync (window, min_stack_position); stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, window->screen->active_workspace); } void @@ -240,6 +246,27 @@ meta_stack_thaw (MetaStack *stack) stack->freeze_count -= 1; stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, NULL); +} + +void +meta_stack_update_window_tile_matches (MetaStack *stack, + MetaWorkspace *workspace) +{ + GList *windows, *tmp; + + if (stack->freeze_count > 0) + return; + + windows = meta_stack_list_windows (stack, workspace); + tmp = windows; + while (tmp) + { + meta_window_compute_tile_match ((MetaWindow *) tmp->data); + tmp = tmp->next; + } + + g_list_free (windows); } static gboolean @@ -1421,6 +1448,7 @@ meta_stack_set_positions (MetaStack *stack, "Reset the stack positions of (nearly) all windows\n"); stack_sync_to_xserver (stack); + meta_stack_update_window_tile_matches (stack, NULL); } void @@ -1483,4 +1511,6 @@ meta_window_set_stack_position (MetaWindow *window, { meta_window_set_stack_position_no_sync (window, position); stack_sync_to_xserver (window->screen->stack); + meta_stack_update_window_tile_matches (window->screen->stack, + window->screen->active_workspace); } diff --git a/src/core/stack.h b/src/core/stack.h index b3596cc7a..e82be0abe 100644 --- a/src/core/stack.h +++ b/src/core/stack.h @@ -413,4 +413,6 @@ GList* meta_stack_get_positions (MetaStack *stack); void meta_stack_set_positions (MetaStack *stack, GList *windows); +void meta_stack_update_window_tile_matches (MetaStack *stack, + MetaWorkspace *workspace); #endif diff --git a/src/core/window-private.h b/src/core/window-private.h index d76aed897..eb89b4c4d 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -157,6 +157,14 @@ struct _MetaWindow guint maximize_vertically_after_placement : 1; guint minimize_after_placement : 1; + /* The current or requested tile mode. If maximized_vertically is true, + * this is the current mode. If not, it is the mode which will be + * requested after the window grab is released */ + guint tile_mode : 2; + /* The last "full" maximized/unmaximized state. We need to keep track of + * that to toggle between normal/tiled or maximized/tiled states. */ + guint saved_maximize : 1; + int tile_monitor_number; int preferred_output_winsys_id; /* Whether we're shaded */ @@ -433,6 +441,9 @@ struct _MetaWindow /* Focused window that is (directly or indirectly) attached to this one */ MetaWindow *attached_focus_window; + /* The currently complementary tiled window, if any */ + MetaWindow *tile_match; + /* Bypass compositor hints */ guint bypass_compositor; }; @@ -481,8 +492,17 @@ struct _MetaWindowClass (w)->maximized_vertically) #define META_WINDOW_MAXIMIZED_VERTICALLY(w) ((w)->maximized_vertically) #define META_WINDOW_MAXIMIZED_HORIZONTALLY(w) ((w)->maximized_horizontally) +#define META_WINDOW_TILED_SIDE_BY_SIDE(w) ((w)->maximized_vertically && \ + !(w)->maximized_horizontally && \ + (w)->tile_mode != META_TILE_NONE) +#define META_WINDOW_TILED_LEFT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_LEFT) +#define META_WINDOW_TILED_RIGHT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_RIGHT) +#define META_WINDOW_TILED_MAXIMIZED(w)(META_WINDOW_MAXIMIZED(w) && \ + (w)->tile_mode == META_TILE_MAXIMIZED) #define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen) -#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen && !(w)->shaded) +#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !META_WINDOW_TILED_SIDE_BY_SIDE(w) && !(w)->fullscreen && !(w)->shaded) #define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \ (((w)->size_hints.min_width < (w)->size_hints.max_width) || \ ((w)->size_hints.min_height < (w)->size_hints.max_height))) @@ -502,6 +522,7 @@ void meta_window_unmanage (MetaWindow *window, guint32 timestamp); void meta_window_queue (MetaWindow *window, guint queuebits); +void meta_window_tile (MetaWindow *window); void meta_window_maximize_internal (MetaWindow *window, MetaMaximizeFlags directions, MetaRectangle *saved_rect); @@ -563,6 +584,11 @@ gboolean meta_window_handle_mouse_grab_op_event (MetaWindow *window, GList* meta_window_get_workspaces (MetaWindow *window); +int meta_window_get_current_tile_monitor_number (MetaWindow *window); +void meta_window_get_current_tile_area (MetaWindow *window, + MetaRectangle *tile_area); + + gboolean meta_window_same_application (MetaWindow *window, MetaWindow *other_window); @@ -602,6 +628,8 @@ void meta_window_on_all_workspaces_changed (MetaWindow *window); gboolean meta_window_should_attach_to_parent (MetaWindow *window); gboolean meta_window_can_tile_side_by_side (MetaWindow *window); +void meta_window_compute_tile_match (MetaWindow *window); + gboolean meta_window_updates_are_frozen (MetaWindow *window); void meta_window_set_title (MetaWindow *window, diff --git a/src/core/window.c b/src/core/window.c index 3c458b074..30e7b9a42 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -898,6 +898,8 @@ _meta_window_shared_new (MetaDisplay *display, window->require_titlebar_visible = TRUE; window->on_all_workspaces = FALSE; window->on_all_workspaces_requested = FALSE; + window->tile_mode = META_TILE_NONE; + window->tile_monitor_number = -1; window->shaded = FALSE; window->initially_iconic = FALSE; window->minimized = FALSE; @@ -994,6 +996,8 @@ _meta_window_shared_new (MetaDisplay *display, window); window->preferred_output_winsys_id = window->monitor->winsys_id; + window->tile_match = NULL; + /* Assign this #MetaWindow a sequence number which can be used * for sorting. */ @@ -2609,7 +2613,7 @@ ensure_size_hints_satisfied (MetaRectangle *rect, static void meta_window_save_rect (MetaWindow *window) { - if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) + if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen)) { /* save size/pos as appropriate args for move_resize */ if (!window->maximized_horizontally) @@ -2648,6 +2652,9 @@ meta_window_maximize_internal (MetaWindow *window, else meta_window_save_rect (window); + if (maximize_horizontally && maximize_vertically) + window->saved_maximize = TRUE; + window->maximized_horizontally = window->maximized_horizontally || maximize_horizontally; window->maximized_vertically = @@ -2669,6 +2676,7 @@ void meta_window_maximize (MetaWindow *window, MetaMaximizeFlags directions) { + MetaRectangle *saved_rect = NULL; gboolean maximize_horizontally, maximize_vertically; g_return_if_fail (!window->override_redirect); @@ -2707,7 +2715,17 @@ meta_window_maximize (MetaWindow *window, return; } - meta_window_maximize_internal (window, directions, NULL); + if (window->tile_mode != META_TILE_NONE) + { + saved_rect = &window->saved_rect; + + window->maximized_vertically = FALSE; + window->tile_mode = META_TILE_NONE; + } + + meta_window_maximize_internal (window, + directions, + saved_rect); MetaRectangle old_frame_rect, old_buffer_rect, new_rect; @@ -2881,6 +2899,29 @@ meta_window_requested_dont_bypass_compositor (MetaWindow *window) return window->bypass_compositor == _NET_WM_BYPASS_COMPOSITOR_HINT_OFF; } +void +meta_window_tile (MetaWindow *window) +{ + MetaMaximizeFlags directions; + + /* Don't do anything if no tiling is requested */ + if (window->tile_mode == META_TILE_NONE) + return; + + if (window->tile_mode == META_TILE_MAXIMIZED) + directions = META_MAXIMIZE_BOTH; + else + directions = META_MAXIMIZE_VERTICAL; + + meta_window_maximize_internal (window, directions, NULL); + meta_screen_update_tile_preview (window->screen, FALSE); + + meta_window_move_resize_now (window); + + if (window->frame) + meta_frame_queue_draw (window->frame); +} + static gboolean meta_window_can_tile_maximized (MetaWindow *window) { @@ -2958,6 +2999,9 @@ meta_window_unmaximize (MetaWindow *window, unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL; g_assert (unmaximize_horizontally || unmaximize_vertically); + if (unmaximize_horizontally && unmaximize_vertically) + window->saved_maximize = FALSE; + /* Only do something if the window isn't already maximized in the * given direction(s). */ @@ -3511,6 +3555,9 @@ meta_window_update_for_monitors_changed (MetaWindow *window) if (!new) new = &window->screen->monitor_infos[window->screen->primary_monitor_index]; + if (window->tile_mode != META_TILE_NONE) + window->tile_monitor_number = new->number; + /* This will eventually reach meta_window_update_monitor that * will send leave/enter-monitor events. The old != new monitor * check will always fail (due to the new monitor_infos set) so @@ -3692,6 +3739,9 @@ meta_window_move_resize_internal (MetaWindow *window, } meta_window_foreach_transient (window, maybe_move_attached_dialog, NULL); + + meta_stack_update_window_tile_matches (window->screen->stack, + window->screen->active_workspace); } /** @@ -3796,6 +3846,9 @@ meta_window_move_to_monitor (MetaWindow *window, monitor, &new_area); + if (window->tile_mode != META_TILE_NONE) + window->tile_monitor_number = monitor; + meta_window_move_between_rects (window, &old_area, &new_area); window->preferred_output_winsys_id = window->monitor->winsys_id; @@ -5512,12 +5565,65 @@ update_move (MetaWindow *window, shake_threshold = meta_prefs_get_drag_threshold () * DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR; + if (snap) + { + /* We don't want to tile while snapping. Also, clear any previous tile + request. */ + window->tile_mode = META_TILE_NONE; + window->tile_monitor_number = -1; + } + else if (meta_prefs_get_edge_tiling () && + !META_WINDOW_MAXIMIZED (window) && + !META_WINDOW_TILED_SIDE_BY_SIDE (window)) + { + const MetaMonitorInfo *monitor; + MetaRectangle work_area; + + /* For side-by-side tiling we are interested in the inside vertical + * edges of the work area of the monitor where the pointer is located, + * and in the outside top edge for maximized tiling. + * + * For maximized tiling we use the outside edge instead of the + * inside edge, because we don't want to force users to maximize + * windows they are placing near the top of their screens. + * + * The "current" idea of meta_window_get_work_area_current_monitor() and + * meta_screen_get_current_monitor() is slightly different: the former + * refers to the monitor which contains the largest part of the window, + * the latter to the one where the pointer is located. + */ + monitor = meta_screen_get_current_monitor_info_for_pos (window->screen, x, y); + meta_window_get_work_area_for_monitor (window, + monitor->number, + &work_area); + + /* Check if the cursor is in a position which triggers tiling + * and set tile_mode accordingly. + */ + if (meta_window_can_tile_side_by_side (window) && + x >= monitor->rect.x && x < (work_area.x + shake_threshold)) + window->tile_mode = META_TILE_LEFT; + else if (meta_window_can_tile_side_by_side (window) && + x >= work_area.x + work_area.width - shake_threshold && + x < (monitor->rect.x + monitor->rect.width)) + window->tile_mode = META_TILE_RIGHT; + else if (meta_window_can_tile_maximized (window) && + y >= monitor->rect.y && y <= work_area.y) + window->tile_mode = META_TILE_MAXIMIZED; + else + window->tile_mode = META_TILE_NONE; + + if (window->tile_mode != META_TILE_NONE) + window->tile_monitor_number = monitor->number; + } + /* shake loose (unmaximize) maximized or tiled window if dragged beyond * the threshold in the Y direction. Tiled windows can also be pulled * loose via X motion. */ - if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold)) + if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) || + (META_WINDOW_TILED_SIDE_BY_SIDE (window) && (MAX (ABS (dx), ABS (dy)) >= shake_threshold))) { double prop; @@ -5526,6 +5632,7 @@ update_move (MetaWindow *window, * is enabled, as top edge tiling can be used in that case */ window->shaken_loose = !meta_prefs_get_edge_tiling (); + window->tile_mode = META_TILE_NONE; /* move the unmaximized window to the cursor */ prop = @@ -5554,12 +5661,14 @@ update_move (MetaWindow *window, /* remaximize window on another monitor if window has been shaken * loose or it is still maximized (then move straight) */ - else if (window->shaken_loose || META_WINDOW_MAXIMIZED (window)) + else if ((window->shaken_loose || META_WINDOW_MAXIMIZED (window)) && + window->tile_mode != META_TILE_LEFT && window->tile_mode != META_TILE_RIGHT) { const MetaMonitorInfo *wmonitor; MetaRectangle work_area; int monitor; + window->tile_mode = META_TILE_NONE; wmonitor = window->monitor; for (monitor = 0; monitor < window->screen->n_monitor_infos; monitor++) @@ -5604,10 +5713,17 @@ update_move (MetaWindow *window, } } + /* Delay showing the tile preview slightly to make it more unlikely to + * trigger it unwittingly, e.g. when shaking loose the window or moving + * it to another monitor. + */ + meta_screen_update_tile_preview (window->screen, + window->tile_mode != META_TILE_NONE); + meta_window_get_frame_rect (window, &old); - /* Don't allow movement in the maximized directions */ - if (window->maximized_horizontally) + /* Don't allow movement in the maximized directions or while tiled */ + if (window->maximized_horizontally || META_WINDOW_TILED_SIDE_BY_SIDE (window)) new_x = old.x; if (window->maximized_vertically) new_y = old.y; @@ -5768,6 +5884,23 @@ update_resize (MetaWindow *window, g_get_current_time (&window->display->grab_last_moveresize_time); } +static void +update_tile_mode (MetaWindow *window) +{ + switch (window->tile_mode) + { + case META_TILE_LEFT: + case META_TILE_RIGHT: + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + window->tile_mode = META_TILE_NONE; + break; + case META_TILE_MAXIMIZED: + if (!META_WINDOW_MAXIMIZED (window)) + window->tile_mode = META_TILE_NONE; + break; + } +} + void meta_window_update_resize (MetaWindow *window, gboolean snap, @@ -5798,9 +5931,12 @@ end_grab_op (MetaWindow *window, { if (meta_grab_op_is_moving (window->display->grab_op)) { - update_move (window, - modifiers & CLUTTER_SHIFT_MASK, - x, y); + if (window->tile_mode != META_TILE_NONE) + meta_window_tile (window); + else + update_move (window, + modifiers & CLUTTER_SHIFT_MASK, + x, y); } else if (meta_grab_op_is_resizing (window->display->grab_op)) { @@ -5808,6 +5944,15 @@ end_grab_op (MetaWindow *window, modifiers & CLUTTER_SHIFT_MASK, x, y, TRUE); + + /* If a tiled window has been dragged free with a + * mouse resize without snapping back to the tiled + * state, it will end up with an inconsistent tile + * mode on mouse release; cleaning the mode earlier + * would break the ability to snap back to the tiled + * state, so we wait until mouse release. + */ + update_tile_mode (window); } } meta_display_end_grab_op (window->display, clutter_event_get_time (event)); @@ -5992,6 +6137,40 @@ meta_window_get_work_area_all_monitors (MetaWindow *window, window->desc, area->x, area->y, area->width, area->height); } +int +meta_window_get_current_tile_monitor_number (MetaWindow *window) +{ + int tile_monitor_number = window->tile_monitor_number; + + if (tile_monitor_number < 0) + { + meta_warning ("%s called with an invalid monitor number; using 0 instead\n", G_STRFUNC); + tile_monitor_number = 0; + } + + return tile_monitor_number; +} + +void +meta_window_get_current_tile_area (MetaWindow *window, + MetaRectangle *tile_area) +{ + int tile_monitor_number; + + g_return_if_fail (window->tile_mode != META_TILE_NONE); + + tile_monitor_number = meta_window_get_current_tile_monitor_number (window); + + meta_window_get_work_area_for_monitor (window, tile_monitor_number, tile_area); + + if (window->tile_mode == META_TILE_LEFT || + window->tile_mode == META_TILE_RIGHT) + tile_area->width /= 2; + + if (window->tile_mode == META_TILE_RIGHT) + tile_area->x += tile_area->width; +} + gboolean meta_window_same_application (MetaWindow *window, MetaWindow *other_window) @@ -6995,7 +7174,8 @@ meta_window_get_frame_type (MetaWindow *window) return META_FRAME_TYPE_LAST; } else if (window->border_only || - (window->hide_titlebar_when_maximized && META_WINDOW_MAXIMIZED (window))) + (window->hide_titlebar_when_maximized && META_WINDOW_MAXIMIZED (window)) || + (window->hide_titlebar_when_maximized && META_WINDOW_TILED_SIDE_BY_SIDE (window))) { /* override base frame type */ return META_FRAME_TYPE_BORDER; @@ -7044,6 +7224,103 @@ meta_window_is_attached_dialog (MetaWindow *window) return window->attached; } +/** + * meta_window_get_tile_match: + * @window: a #MetaWindow + * + * Returns the matching tiled window on the same monitor as @window. This is + * the topmost tiled window in a complementary tile mode that is: + * + * - on the same monitor; + * - on the same workspace; + * - spanning the remaining monitor width; + * - there is no 3rd window stacked between both tiled windows that's + * partially visible in the common edge. + * + * Return value: (transfer none) (nullable): the matching tiled window or + * %NULL if it doesn't exist. + */ +MetaWindow * +meta_window_get_tile_match (MetaWindow *window) +{ + return window->tile_match; +} + +void +meta_window_compute_tile_match (MetaWindow *window) +{ + MetaWindow *match; + MetaStack *stack; + MetaTileMode match_tile_mode = META_TILE_NONE; + + window->tile_match = NULL; + + if (window->shaded || window->minimized) + return; + + if (META_WINDOW_TILED_LEFT (window)) + match_tile_mode = META_TILE_RIGHT; + else if (META_WINDOW_TILED_RIGHT (window)) + match_tile_mode = META_TILE_LEFT; + else + return; + + stack = window->screen->stack; + + for (match = meta_stack_get_top (stack); + match; + match = meta_stack_get_below (stack, match, FALSE)) + { + if (!match->shaded && + !match->minimized && + match->tile_mode == match_tile_mode && + match->monitor == window->monitor && + meta_window_get_workspace (match) == meta_window_get_workspace (window)) + break; + } + + if (match) + { + MetaWindow *above, *bottommost, *topmost; + MetaRectangle above_rect, bottommost_rect, topmost_rect; + + if (meta_stack_windows_cmp (window->screen->stack, match, window) > 0) + { + topmost = match; + bottommost = window; + } + else + { + topmost = window; + bottommost = match; + } + + meta_window_get_frame_rect (bottommost, &bottommost_rect); + meta_window_get_frame_rect (topmost, &topmost_rect); + /* + * If there's a window stacked in between which is partially visible + * behind the topmost tile we don't consider the tiles to match. + */ + for (above = meta_stack_get_above (stack, bottommost, FALSE); + above && above != topmost; + above = meta_stack_get_above (stack, above, FALSE)) + { + if (above->minimized || + above->monitor != window->monitor || + meta_window_get_workspace (above) != meta_window_get_workspace (window)) + continue; + + meta_window_get_frame_rect (above, &above_rect); + + if (meta_rectangle_overlap (&above_rect, &bottommost_rect) && + meta_rectangle_overlap (&above_rect, &topmost_rect)) + return; + } + + window->tile_match = match; + } +} + void meta_window_set_title (MetaWindow *window, const char *title) diff --git a/src/meta/window.h b/src/meta/window.h index a3a4b768a..60c9997da 100644 --- a/src/meta/window.h +++ b/src/meta/window.h @@ -214,6 +214,8 @@ MetaFrameType meta_window_get_frame_type (MetaWindow *window); cairo_region_t *meta_window_get_frame_bounds (MetaWindow *window); +MetaWindow *meta_window_get_tile_match (MetaWindow *window); + void meta_window_make_fullscreen (MetaWindow *window); void meta_window_unmake_fullscreen (MetaWindow *window); void meta_window_make_above (MetaWindow *window);