Skip to content

Commit 0e1a054

Browse files
committed
feat: add preferences and merge tiles when selected
1 parent 76a3c22 commit 0e1a054

27 files changed

+664
-296
lines changed

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# ModernWindowManager #
22

3-
This is a [Gnome Shell](https://wiki.gnome.org/Projects/GnomeShell) extension which provides a windows tiling system, supporting multiple monitors and focused on providing the best UX. It is also the first and only extension which provides Windows 11's snap assistant!
3+
This is a Gnome Shell extension implementing modern windows tiling system by extending GNOME's default 2 columns to any layout you want! It has the same UI of your GNOME theme, it supports multiple monitors and spanning of multiple tiles. It is also the first and only extension that provides Windows 11's snap assistant and supports multi monitors with different scaling factors!
44

5-
Can be installed on Gnome Shells from 40 to 44 on X11 and Wayland.
5+
Can be installed on Gnome Shells from 40 to 44 on X11 and Wayland. This extension also provides all the functionalities of Windows 11 Snap Assistant and Windows PowerToys FancyZones.
66

77
Have issues, you want to suggest a new feature or contribute? Please open a new [issue](https://github.com/domferr/modernwindowmanager/issues)!
88

9-
If you're in search of a tiling manager for GNOME or if you are looking for features akin to Windows 11 Snap Assistant or Windows PowerToys FancyZones, this extension is the perfect solution for you!
10-
119
## Usage ##
1210

1311
### Tiling System ###
@@ -33,8 +31,13 @@ The layout is not strict. You can select multiple tiles too! Just press <kbd>SHI
3331

3432
## Installation
3533

34+
Download the latest [release](https://github.com/domferr/modernwindowmanager/releases). Extract the downloaded archive. Copy the folder to `~/.local/share/gnome-shell/extensions` directory. You need to reload GNOME Shell afterwards (e.g. by logging out). Then you can enable the extension:
35+
```bash
36+
/usr/bin/gnome-extensions enable modernwindowmanager@ferrarodomenico.com
37+
```
3638
Currently, this extension is not on [extensions.gnome.org](https://extensions.gnome.org/extension/6099/paperwm/). However, if you are interested we can publish it there. Let us know by opening a new [issue](https://github.com/domferr/modernwindowmanager/issues)!
3739

40+
3841
### Install via Source
3942

4043
Clone the repo then run
-13.7 KB
Binary file not shown.

package.json

+13-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,18 @@
1616
"dev:wayland": "npm run build && npm run install:extension && npm run wayland-session"
1717
},
1818
"devDependencies": {
19-
"typescript": "^4.7.4",
19+
"@gi-types/adw1": "^1.1.1",
20+
"@gi-types/clutter10": "^10.0.1",
21+
"@gi-types/gio2": "^2.72.1",
22+
"@gi-types/glib2": "^2.72.1",
23+
"@gi-types/gobject2": "^2.72.1",
24+
"@gi-types/gtk4": "^4.6.1",
25+
"@gi-types/meta10": "^10.0.1",
26+
"@gi-types/shell0": "^0.1.1",
27+
"@gi-types/st1": "^1.0.1",
28+
"@rollup/plugin-commonjs": "^22.0.2",
29+
"@rollup/plugin-node-resolve": "^13.1.3",
30+
"@rollup/plugin-typescript": "^8.3.4",
2031
"@typescript-eslint/eslint-plugin": "^5.33.1",
2132
"@typescript-eslint/parser": "^5.33.1",
2233
"eslint": "^8.22.0",
@@ -29,17 +40,7 @@
2940
"rollup-plugin-copy": "^3.4.0",
3041
"rollup-plugin-styles": "^4.0.0",
3142
"rollup-plugin-visualizer": "^5.8.0",
32-
"@rollup/plugin-commonjs": "^22.0.2",
33-
"@rollup/plugin-node-resolve": "^13.1.3",
34-
"@rollup/plugin-typescript": "^8.3.4",
3543
"sass": "^1.71.1",
36-
"@gi-types/clutter10": "^10.0.1",
37-
"@gi-types/gio2": "^2.72.1",
38-
"@gi-types/glib2": "^2.72.1",
39-
"@gi-types/gobject2": "^2.72.1",
40-
"@gi-types/gtk4": "^4.6.1",
41-
"@gi-types/meta10": "^10.0.1",
42-
"@gi-types/shell0": "^0.1.1",
43-
"@gi-types/st1": "^1.0.1"
44+
"typescript": "^4.7.4"
4445
}
4546
}

resources/metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"43",
1010
"44"
1111
],
12-
"version": 1,
12+
"version": 2,
1313
"url": "https://github.com/domferr/Linux-PowerToys",
1414
"settings-schema": "org.gnome.shell.extensions.modernwindowmanager"
1515
}

resources/schemas/org.gnome.shell.extensions.modernwindowmanager.gschema.xml

+25
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,36 @@
22
<schemalist gettext-domain="gnome-shell-extensions">
33

44
<schema path="/org/gnome/shell/extensions/modernwindowmanager/" id="org.gnome.shell.extensions.modernwindowmanager">
5+
<key name="enable-tiling-system" type="b">
6+
<default>true</default>
7+
<summary>Enable tiling system</summary>
8+
<description>Hold CTRL while moving a window to tile it.</description>
9+
</key>
10+
<key name="enable-snap-assist" type="b">
11+
<default>true</default>
12+
<summary>Enable snap assist</summary>
13+
<description>Move the window on top of the screen to snap assist it.</description>
14+
</key>
515
<key name="show-indicator" type="b">
616
<default>true</default>
717
<summary>Shows indicator</summary>
818
<description>Shows an indicator on top panel.</description>
919
</key>
20+
<key name="inner-gaps" type="u">
21+
<default>16</default>
22+
<summary>Inner gaps</summary>
23+
<description>Internal gaps between tiles in a layout</description>
24+
</key>
25+
<key name="outer-gaps" type="u">
26+
<default>16</default>
27+
<summary>Outer gaps</summary>
28+
<description>External gaps between the layout and the monitor borders</description>
29+
</key>
30+
<key name="enable-span-multiple-tiles" type="b">
31+
<default>true</default>
32+
<summary>Span multiple tiles</summary>
33+
<description>Hold ALT to span multiple tiles.</description>
34+
</key>
1035
</schema>
1136

1237
</schemalist>

rollup.config.js

+44-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ import visualizer from 'rollup-plugin-visualizer';
99
const buildPath = 'dist';
1010

1111
const globals = {
12+
'@gi-types/gdk4': 'imports.gi.Gdk',
1213
'@gi-types/gio2': 'imports.gi.Gio',
1314
'@gi-types/gtk4': 'imports.gi.Gtk',
15+
'@gi-types/gdkpixbuf2': 'imports.gi.GdkPixbuf',
1416
'@gi-types/glib2': 'imports.gi.GLib',
1517
'@gi-types/st1': 'imports.gi.St',
1618
'@gi-types/shell0': 'imports.gi.Shell',
1719
'@gi-types/meta10': 'imports.gi.Meta',
18-
'@gi-types/gobject2': 'imports.gi.GObject',
1920
'@gi-types/clutter10': 'imports.gi.Clutter',
20-
};
21+
'@gi-types/soup3': 'imports.gi.Soup',
22+
'@gi-types/gobject2': 'imports.gi.GObject',
23+
'@gi-types/pango1': 'imports.gi.Pango',
24+
'@gi-types/graphene1': 'imports.gi.Graphene',
25+
'@gi-types/adw1': 'imports.gi.Adw',
26+
};
2127

2228
const external = [...Object.keys(globals)/*, thirdParty*/];
2329

@@ -34,6 +40,14 @@ catch(err) {
3440
}
3541
`;
3642

43+
// inspired from Pano: https://github.com/oae/gnome-shell-pano/blob/cf871244814827cd5f6d03f35b2820674975ef62/rollup.config.js
44+
const prefsBanner = [
45+
"imports.gi.versions.Gtk = '4.0';",
46+
'const Me = imports.misc.extensionUtils.getCurrentExtension();',
47+
].join('\n');
48+
49+
const prefsFooter = ['var init = prefs.init;', 'var fillPreferencesWindow = prefs.fillPreferencesWindow;'].join('\n');
50+
3751
export default [
3852
/*...thirdPartyBuild,*/
3953
{
@@ -77,4 +91,32 @@ export default [
7791
visualizer(),
7892
],
7993
},
94+
{
95+
input: 'src/prefs/prefs.ts',
96+
output: {
97+
file: `${buildPath}/prefs.js`,
98+
format: 'iife',
99+
exports: 'default',
100+
name: 'prefs',
101+
banner: prefsBanner,
102+
footer: prefsFooter,
103+
globals,
104+
},
105+
treeshake: {
106+
moduleSideEffects: 'no-external',
107+
},
108+
external,
109+
plugins: [
110+
commonjs(),
111+
nodeResolve({
112+
preferBuiltins: false,
113+
}),
114+
typescript({
115+
tsconfig: './tsconfig.json',
116+
}),
117+
cleanup({
118+
comments: 'none',
119+
}),
120+
],
121+
},
80122
];

src/components/layout/Layout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const LAYOUT_HORIZONTAL_TYPE = 0;
22
export const LAYOUT_VERTICAL_TYPE = 1;
33

44
export interface Layout {
5-
type: number // 0 means horizontal, while 1 means vertical
5+
type: number // 0 means horizontal, 1 means vertical
66
length: number
77
items: Layout[]
88
}

src/components/layout/LayoutUtils.ts

+39-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {logger} from "@/utils/shell";
22
import GLib from "@gi-types/glib2";
3-
import { TileGroup } from "@/components/tileGroup";
3+
import { TileGroup } from "@/components/layout/tileGroup";
44
import { LAYOUT_HORIZONTAL_TYPE, Layout } from "./Layout";
55

66
const debug = logger('LayoutsUtils');
@@ -14,24 +14,46 @@ export class LayoutsUtils {
1414
return GLib.build_filenamev([this.configPath, 'layouts.json']);
1515
}
1616

17-
public static LoadLayouts(): TileGroup {
17+
public static LoadLayouts(): TileGroup[] {
18+
const availableLayouts = [
19+
new TileGroup({}),
20+
new TileGroup({
21+
tiles: [
22+
new TileGroup({ perc: 0.22 }),
23+
new TileGroup({ perc: 0.56 }),
24+
new TileGroup({ perc: 0.22 }),
25+
],
26+
}),
27+
new TileGroup({
28+
tiles: [
29+
new TileGroup({ perc: 0.33 }),
30+
new TileGroup({ perc: 0.67 }),
31+
],
32+
}),
33+
new TileGroup({
34+
tiles: [
35+
new TileGroup({ perc: 0.67 }),
36+
new TileGroup({ perc: 0.33 }),
37+
],
38+
})
39+
];
1840
const filePath = this.layoutsPath;
19-
if(!GLib.file_test(filePath, GLib.FileTest.EXISTS)) return this._getDefaultLayout();
20-
21-
try {
22-
let [ok, contents] = GLib.file_get_contents(filePath);
23-
if (!ok) return new TileGroup({});
24-
25-
const decoder = new TextDecoder('utf-8');
26-
let contentsString = decoder.decode(contents);
27-
const parsed = JSON.parse(contentsString);
28-
const layouts = parsed.definitions as Layout[];
29-
return this._layoutToTileGroup(layouts[0]);
30-
} catch (exception) {
31-
debug(`exception loading layout: ${JSON.stringify(exception)}`);
41+
if (GLib.file_test(filePath, GLib.FileTest.EXISTS)) {
42+
try {
43+
let [ok, contents] = GLib.file_get_contents(filePath);
44+
if (ok) {
45+
const decoder = new TextDecoder('utf-8');
46+
let contentsString = decoder.decode(contents);
47+
const parsed = JSON.parse(contentsString);
48+
const layouts = parsed.definitions as Layout[];
49+
availableLayouts[0] = this._layoutToTileGroup(layouts[0]);
50+
}
51+
} catch (exception) {
52+
debug(`exception loading layouts: ${JSON.stringify(exception)}`);
53+
}
3254
}
33-
34-
return this._getDefaultLayout();
55+
availableLayouts[0] = this._getDefaultLayout();
56+
return availableLayouts;
3557
}
3658

3759
private static _layoutToTileGroup(layout: Layout) : TileGroup {

src/components/layout/LayoutWidget.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Widget } from "@gi-types/st1";
22
import { TilePreview } from "../tilepreview/tilePreview";
33
import { Actor, Margin } from '@gi-types/clutter10';
4-
import { TileGroup } from "../tileGroup";
4+
import { TileGroup } from "./tileGroup";
55
import { Rectangle } from "@gi-types/meta10";
66
import { registerGObjectClass } from "@/utils/gjs";
77
import { buildTileMargin } from "@/utils/ui";
File renamed without changes.

src/components/snapassist/snapAssist.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { registerGObjectClass } from "@/utils/gjs";
22
import { Actor, AnimationMode, ActorAlign, Margin } from '@gi-types/clutter10';
33
import { Rectangle, Window } from "@gi-types/meta10";
44
import { BoxLayout, Side, ThemeContext, Widget } from "@gi-types/st1";
5-
import { TileGroup } from "../tileGroup";
5+
import { TileGroup } from "../layout/tileGroup";
66
import { logger } from "@/utils/shell";
77
import { MetaInfo, TYPE_DOUBLE } from "@gi-types/gobject2";
88
import { SnapAssistTile } from "./snapAssistTile";
@@ -56,15 +56,16 @@ export class SnapAssist extends BoxLayout {
5656

5757
this._shrinkHeight *= scaleFactor;
5858

59-
const layoutMargin = new Margin({
60-
top: margin.top === 0 ? 0:2,
61-
bottom: margin.bottom === 0 ? 0:2,
62-
left: margin.left === 0 ? 0:2,
63-
right: margin.right === 0 ? 0:2,
59+
const gap = 3;
60+
const layoutGaps = new Margin({
61+
top: margin.top === 0 ? 0:gap,
62+
bottom: margin.bottom === 0 ? 0:gap,
63+
left: margin.left === 0 ? 0:gap,
64+
right: margin.right === 0 ? 0:gap,
6465
})
6566
// build the layouts inside the snap assistant. Place a spacer between each layout
6667
this._snapAssistLayouts = layouts.map((lay, ind) => {
67-
const saLay = new SnapAssistLayout(this, lay, layoutMargin, scaleFactor);
68+
const saLay = new SnapAssistLayout(this, lay, layoutGaps, scaleFactor);
6869
// build and place a spacer
6970
if (ind < layouts.length -1) {
7071
this.add_child(new Widget({width: scaleFactor * this._separatorSize, height: 1}));
@@ -95,6 +96,7 @@ export class SnapAssist extends BoxLayout {
9596
this.hide();
9697
this._showing = false;
9798
this.enlarged = false;
99+
this.set_position(this._rect.x, this._rect.y);
98100
}
99101

100102
public set workArea(newWorkArea: Rectangle) {

src/components/snapassist/snapAssistLayout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Actor, Margin, AnimationMode } from '@gi-types/clutter10';
33
import { getGlobalPosition } from "@/utils/ui";
44
import { Rectangle } from "@gi-types/meta10";
55
import { LayoutWidget } from "../layout/LayoutWidget";
6-
import { TileGroup } from "../tileGroup";
6+
import { TileGroup } from "../layout/tileGroup";
77
import { SnapAssistTile } from "./snapAssistTile";
88
import { logger } from "@/utils/shell";
99

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { registerGObjectClass } from "@/utils/gjs";
2-
import { Rectangle, Window } from "@gi-types/meta10";
2+
import { Rectangle } from "@gi-types/meta10";
33
import { Actor, Color, Margin } from '@gi-types/clutter10';
44
import { TilePreview } from "./tilePreview";
55
import { logger } from "@/utils/shell";
@@ -12,36 +12,32 @@ export class SelectionTilePreview extends TilePreview {
1212

1313
constructor(parent: Actor, rect?: Rectangle, margins?: Margin) {
1414
super(parent, rect, margins);
15+
this._recolor();
16+
this.connect("style-changed", () => {
17+
const { red, green, blue } = this.get_theme_node().get_background_color();
18+
19+
if (this._backgroundColor?.red !== red || this._backgroundColor?.green !== green || this._backgroundColor?.blue !== blue) {
20+
this._recolor();
21+
}
22+
});
1523
}
1624

17-
private _ensureBackgroundIsSet() {
18-
// delay setting up the background color so we are sure that the gnome shell theme was applied
19-
if (this._backgroundColor !== null) return;
20-
21-
this._backgroundColor = this.get_theme_node().get_background_color();
22-
//debug(`constructor, tile color is ${this._backgroundColor.red} ${this._backgroundColor.green} ${this._backgroundColor.blue} ${this._backgroundColor.alpha}`);
25+
_recolor() {
26+
this._backgroundColor = this.get_theme_node().get_background_color().copy();
2327

2428
// since an alpha value lower than 160 is not so much visible, enforce a minimum value of 160
2529
let newAlpha = Math.max(Math.min(this._backgroundColor.alpha + 35, 255), 160);
2630
// The final alpha value is divided by 255 since CSS needs a value from 0 to 1, but ClutterColor expresses alpha from 0 to 255
2731
this.set_style(`
2832
background-color: rgba(${this._backgroundColor.red}, ${this._backgroundColor.green}, ${this._backgroundColor.blue}, ${newAlpha / 255}) !important;
2933
`);
30-
this.remove_style_class_name("tile-preview");
31-
}
32-
33-
open(ease: boolean = false, position?: Rectangle) {
34-
this._ensureBackgroundIsSet();
35-
super.open(ease, position);
3634
}
3735

38-
openBelow(window: Window, ease: boolean = false, position?: Rectangle) {
39-
this._ensureBackgroundIsSet();
40-
super.openBelow(window, ease, position);
41-
}
36+
close() {
37+
if (!this._showing) return;
4238

43-
openAbove(window: Window, ease: boolean = false, position?: Rectangle) {
44-
this._ensureBackgroundIsSet();
45-
super.openAbove(window, ease, position);
39+
this._rect.width = 0;
40+
this._rect.height = 0;
41+
super.close();
4642
}
4743
}

0 commit comments

Comments
 (0)