1927 lines
59 KiB
JavaScript
Executable File
1927 lines
59 KiB
JavaScript
Executable File
// src/gi.shared.ts
|
|
import Gio from "gi://Gio";
|
|
import GLib from "gi://GLib";
|
|
import GObject from "gi://GObject";
|
|
|
|
// src/gi.prefs.ts
|
|
import Gdk from "gi://Gdk";
|
|
import Gtk from "gi://Gtk";
|
|
import Adw from "gi://Adw";
|
|
|
|
// src/components/layout/Layout.ts
|
|
var Layout = class {
|
|
id;
|
|
tiles;
|
|
constructor(tiles, id) {
|
|
this.tiles = tiles;
|
|
this.id = id;
|
|
}
|
|
};
|
|
|
|
// src/components/layout/Tile.ts
|
|
var Tile2 = class {
|
|
// @ts-expect-error "GObject has TYPE_JSOBJECT"
|
|
static $gtype = GObject.TYPE_JSOBJECT;
|
|
x;
|
|
y;
|
|
width;
|
|
height;
|
|
groups;
|
|
constructor({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
groups
|
|
}) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.groups = groups;
|
|
}
|
|
};
|
|
|
|
// src/settings/settings.ts
|
|
var ActivationKey = /* @__PURE__ */ ((ActivationKey2) => {
|
|
ActivationKey2[ActivationKey2["NONE"] = -1] = "NONE";
|
|
ActivationKey2[ActivationKey2["CTRL"] = 0] = "CTRL";
|
|
ActivationKey2[ActivationKey2["ALT"] = 1] = "ALT";
|
|
ActivationKey2[ActivationKey2["SUPER"] = 2] = "SUPER";
|
|
return ActivationKey2;
|
|
})(ActivationKey || {});
|
|
function get_string(key) {
|
|
return Settings.gioSetting.get_string(key) ?? Settings.gioSetting.get_default_value(key)?.get_string()[0];
|
|
}
|
|
function set_string(key, val) {
|
|
return Settings.gioSetting.set_string(key, val);
|
|
}
|
|
function get_boolean(key) {
|
|
return Settings.gioSetting.get_boolean(key) ?? Settings.gioSetting.get_default_value(key)?.get_boolean();
|
|
}
|
|
function set_boolean(key, val) {
|
|
return Settings.gioSetting.set_boolean(key, val);
|
|
}
|
|
function get_number(key) {
|
|
return Settings.gioSetting.get_int(key) ?? Settings.gioSetting.get_default_value(key)?.get_int64();
|
|
}
|
|
function set_number(key, val) {
|
|
return Settings.gioSetting.set_int(key, val);
|
|
}
|
|
function get_unsigned_number(key) {
|
|
return Settings.gioSetting.get_uint(key) ?? Settings.gioSetting.get_default_value(key)?.get_uint64();
|
|
}
|
|
function set_unsigned_number(key, val) {
|
|
return Settings.gioSetting.set_uint(key, val);
|
|
}
|
|
function get_activationkey(key, defaultValue) {
|
|
let val = Settings.gioSetting.get_strv(key);
|
|
if (!val || val.length === 0) {
|
|
val = Settings.gioSetting.get_default_value(key)?.get_strv() ?? [
|
|
String(defaultValue)
|
|
];
|
|
if (val.length === 0)
|
|
val = [String(defaultValue)];
|
|
}
|
|
return Number(val[0]);
|
|
}
|
|
function set_activationkey(key, val) {
|
|
return Settings.gioSetting.set_strv(key, [String(val)]);
|
|
}
|
|
var Settings = class _Settings {
|
|
static _settings;
|
|
static _is_initialized = false;
|
|
static KEY_LAST_VERSION_NAME_INSTALLED = "last-version-name-installed";
|
|
static KEY_OVERRIDDEN_SETTINGS = "overridden-settings";
|
|
static KEY_WINDOW_BORDER_COLOR = "window-border-color";
|
|
static KEY_TILING_SYSTEM = "enable-tiling-system";
|
|
static KEY_SNAP_ASSIST = "enable-snap-assist";
|
|
static KEY_SHOW_INDICATOR = "show-indicator";
|
|
static KEY_TILING_SYSTEM_ACTIVATION_KEY = "tiling-system-activation-key";
|
|
static KEY_TILING_SYSTEM_DEACTIVATION_KEY = "tiling-system-deactivation-key";
|
|
static KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY = "span-multiple-tiles-activation-key";
|
|
static KEY_SPAN_MULTIPLE_TILES = "enable-span-multiple-tiles";
|
|
static KEY_RESTORE_WINDOW_ORIGINAL_SIZE = "restore-window-original-size";
|
|
static KEY_WRAPAROUND_FOCUS = "enable-wraparound-focus";
|
|
static KEY_RESIZE_COMPLEMENTING_WINDOWS = "resize-complementing-windows";
|
|
static KEY_ENABLE_BLUR_SNAP_ASSISTANT = "enable-blur-snap-assistant";
|
|
static KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW = "enable-blur-selected-tilepreview";
|
|
static KEY_ENABLE_MOVE_KEYBINDINGS = "enable-move-keybindings";
|
|
static KEY_ENABLE_AUTO_TILING = "enable-autotiling";
|
|
static KEY_ACTIVE_SCREEN_EDGES = "active-screen-edges";
|
|
static KEY_TOP_EDGE_MAXIMIZE = "top-edge-maximize";
|
|
static KEY_OVERRIDE_WINDOW_MENU = "override-window-menu";
|
|
static KEY_SNAP_ASSISTANT_THRESHOLD = "snap-assistant-threshold";
|
|
static KEY_ENABLE_WINDOW_BORDER = "enable-window-border";
|
|
static KEY_INNER_GAPS = "inner-gaps";
|
|
static KEY_OUTER_GAPS = "outer-gaps";
|
|
static KEY_SNAP_ASSISTANT_ANIMATION_TIME = "snap-assistant-animation-time";
|
|
static KEY_TILE_PREVIEW_ANIMATION_TIME = "tile-preview-animation-time";
|
|
static KEY_SETTING_LAYOUTS_JSON = "layouts-json";
|
|
static KEY_SETTING_SELECTED_LAYOUTS = "selected-layouts";
|
|
static KEY_WINDOW_BORDER_WIDTH = "window-border-width";
|
|
static KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS = "enable-smart-window-border-radius";
|
|
static KEY_QUARTER_TILING_THRESHOLD = "quarter-tiling-threshold";
|
|
static KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS = "enable-tiling-system-windows-suggestions";
|
|
static KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS = "enable-snap-assistant-windows-suggestions";
|
|
static KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS = "enable-screen-edges-windows-suggestions";
|
|
static SETTING_MOVE_WINDOW_RIGHT = "move-window-right";
|
|
static SETTING_MOVE_WINDOW_LEFT = "move-window-left";
|
|
static SETTING_MOVE_WINDOW_UP = "move-window-up";
|
|
static SETTING_MOVE_WINDOW_DOWN = "move-window-down";
|
|
static SETTING_SPAN_WINDOW_RIGHT = "span-window-right";
|
|
static SETTING_SPAN_WINDOW_LEFT = "span-window-left";
|
|
static SETTING_SPAN_WINDOW_UP = "span-window-up";
|
|
static SETTING_SPAN_WINDOW_DOWN = "span-window-down";
|
|
static SETTING_SPAN_WINDOW_ALL_TILES = "span-window-all-tiles";
|
|
static SETTING_UNTILE_WINDOW = "untile-window";
|
|
static SETTING_MOVE_WINDOW_CENTER = "move-window-center";
|
|
static SETTING_FOCUS_WINDOW_RIGHT = "focus-window-right";
|
|
static SETTING_FOCUS_WINDOW_LEFT = "focus-window-left";
|
|
static SETTING_FOCUS_WINDOW_UP = "focus-window-up";
|
|
static SETTING_FOCUS_WINDOW_DOWN = "focus-window-down";
|
|
static SETTING_FOCUS_WINDOW_NEXT = "focus-window-next";
|
|
static SETTING_FOCUS_WINDOW_PREV = "focus-window-prev";
|
|
static SETTING_HIGHLIGHT_CURRENT_WINDOW = "highlight-current-window";
|
|
static initialize(settings) {
|
|
if (this._is_initialized)
|
|
return;
|
|
this._is_initialized = true;
|
|
this._settings = settings;
|
|
}
|
|
static destroy() {
|
|
if (this._is_initialized) {
|
|
this._is_initialized = false;
|
|
this._settings = null;
|
|
}
|
|
}
|
|
static get gioSetting() {
|
|
return this._settings ?? new Gio.Settings();
|
|
}
|
|
static bind(key, object, property, flags = Gio.SettingsBindFlags.DEFAULT) {
|
|
this._settings?.bind(key, object, property, flags);
|
|
}
|
|
static get LAST_VERSION_NAME_INSTALLED() {
|
|
return get_string(_Settings.KEY_LAST_VERSION_NAME_INSTALLED);
|
|
}
|
|
static set LAST_VERSION_NAME_INSTALLED(val) {
|
|
set_string(_Settings.KEY_LAST_VERSION_NAME_INSTALLED, val);
|
|
}
|
|
static get OVERRIDDEN_SETTINGS() {
|
|
return get_string(_Settings.KEY_OVERRIDDEN_SETTINGS);
|
|
}
|
|
static set OVERRIDDEN_SETTINGS(val) {
|
|
set_string(_Settings.KEY_OVERRIDDEN_SETTINGS, val);
|
|
}
|
|
static get TILING_SYSTEM() {
|
|
return get_boolean(_Settings.KEY_TILING_SYSTEM);
|
|
}
|
|
static set TILING_SYSTEM(val) {
|
|
set_boolean(_Settings.KEY_TILING_SYSTEM, val);
|
|
}
|
|
static get SNAP_ASSIST() {
|
|
return get_boolean(_Settings.KEY_SNAP_ASSIST);
|
|
}
|
|
static set SNAP_ASSIST(val) {
|
|
set_boolean(_Settings.KEY_SNAP_ASSIST, val);
|
|
}
|
|
static get SHOW_INDICATOR() {
|
|
return get_boolean(_Settings.KEY_SHOW_INDICATOR);
|
|
}
|
|
static set SHOW_INDICATOR(val) {
|
|
set_boolean(_Settings.KEY_SHOW_INDICATOR, val);
|
|
}
|
|
static get TILING_SYSTEM_ACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_TILING_SYSTEM_ACTIVATION_KEY,
|
|
0 /* CTRL */
|
|
);
|
|
}
|
|
static set TILING_SYSTEM_ACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_TILING_SYSTEM_ACTIVATION_KEY, val);
|
|
}
|
|
static get TILING_SYSTEM_DEACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_TILING_SYSTEM_DEACTIVATION_KEY,
|
|
-1 /* NONE */
|
|
);
|
|
}
|
|
static set TILING_SYSTEM_DEACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_TILING_SYSTEM_DEACTIVATION_KEY, val);
|
|
}
|
|
static get INNER_GAPS() {
|
|
return get_unsigned_number(_Settings.KEY_INNER_GAPS);
|
|
}
|
|
static set INNER_GAPS(val) {
|
|
set_unsigned_number(_Settings.KEY_INNER_GAPS, val);
|
|
}
|
|
static get OUTER_GAPS() {
|
|
return get_unsigned_number(_Settings.KEY_OUTER_GAPS);
|
|
}
|
|
static set OUTER_GAPS(val) {
|
|
set_unsigned_number(_Settings.KEY_OUTER_GAPS, val);
|
|
}
|
|
static get SPAN_MULTIPLE_TILES() {
|
|
return get_boolean(_Settings.KEY_SPAN_MULTIPLE_TILES);
|
|
}
|
|
static set SPAN_MULTIPLE_TILES(val) {
|
|
set_boolean(_Settings.KEY_SPAN_MULTIPLE_TILES, val);
|
|
}
|
|
static get SPAN_MULTIPLE_TILES_ACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY,
|
|
1 /* ALT */
|
|
);
|
|
}
|
|
static set SPAN_MULTIPLE_TILES_ACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY, val);
|
|
}
|
|
static get RESTORE_WINDOW_ORIGINAL_SIZE() {
|
|
return get_boolean(_Settings.KEY_RESTORE_WINDOW_ORIGINAL_SIZE);
|
|
}
|
|
static set RESTORE_WINDOW_ORIGINAL_SIZE(val) {
|
|
set_boolean(_Settings.KEY_RESTORE_WINDOW_ORIGINAL_SIZE, val);
|
|
}
|
|
static get WRAPAROUND_FOCUS() {
|
|
return get_boolean(_Settings.KEY_WRAPAROUND_FOCUS);
|
|
}
|
|
static set WRAPAROUND_FOCUS(val) {
|
|
set_boolean(_Settings.KEY_WRAPAROUND_FOCUS, val);
|
|
}
|
|
static get RESIZE_COMPLEMENTING_WINDOWS() {
|
|
return get_boolean(_Settings.KEY_RESIZE_COMPLEMENTING_WINDOWS);
|
|
}
|
|
static set RESIZE_COMPLEMENTING_WINDOWS(val) {
|
|
set_boolean(_Settings.KEY_RESIZE_COMPLEMENTING_WINDOWS, val);
|
|
}
|
|
static get ENABLE_BLUR_SNAP_ASSISTANT() {
|
|
return get_boolean(_Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT);
|
|
}
|
|
static set ENABLE_BLUR_SNAP_ASSISTANT(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT, val);
|
|
}
|
|
static get ENABLE_BLUR_SELECTED_TILEPREVIEW() {
|
|
return get_boolean(_Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW);
|
|
}
|
|
static set ENABLE_BLUR_SELECTED_TILEPREVIEW(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW, val);
|
|
}
|
|
static get ENABLE_MOVE_KEYBINDINGS() {
|
|
return get_boolean(_Settings.KEY_ENABLE_MOVE_KEYBINDINGS);
|
|
}
|
|
static set ENABLE_MOVE_KEYBINDINGS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_MOVE_KEYBINDINGS, val);
|
|
}
|
|
static get ENABLE_AUTO_TILING() {
|
|
return get_boolean(_Settings.KEY_ENABLE_AUTO_TILING);
|
|
}
|
|
static set ENABLE_AUTO_TILING(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_AUTO_TILING, val);
|
|
}
|
|
static get ACTIVE_SCREEN_EDGES() {
|
|
return get_boolean(_Settings.KEY_ACTIVE_SCREEN_EDGES);
|
|
}
|
|
static set ACTIVE_SCREEN_EDGES(val) {
|
|
set_boolean(_Settings.KEY_ACTIVE_SCREEN_EDGES, val);
|
|
}
|
|
static get TOP_EDGE_MAXIMIZE() {
|
|
return get_boolean(_Settings.KEY_TOP_EDGE_MAXIMIZE);
|
|
}
|
|
static set TOP_EDGE_MAXIMIZE(val) {
|
|
set_boolean(_Settings.KEY_TOP_EDGE_MAXIMIZE, val);
|
|
}
|
|
static get OVERRIDE_WINDOW_MENU() {
|
|
return get_boolean(_Settings.KEY_OVERRIDE_WINDOW_MENU);
|
|
}
|
|
static set OVERRIDE_WINDOW_MENU(val) {
|
|
set_boolean(_Settings.KEY_OVERRIDE_WINDOW_MENU, val);
|
|
}
|
|
static get SNAP_ASSISTANT_THRESHOLD() {
|
|
return get_number(_Settings.KEY_SNAP_ASSISTANT_THRESHOLD);
|
|
}
|
|
static set SNAP_ASSISTANT_THRESHOLD(val) {
|
|
set_number(_Settings.KEY_SNAP_ASSISTANT_THRESHOLD, val);
|
|
}
|
|
static get QUARTER_TILING_THRESHOLD() {
|
|
return get_unsigned_number(_Settings.KEY_QUARTER_TILING_THRESHOLD);
|
|
}
|
|
static set QUARTER_TILING_THRESHOLD(val) {
|
|
set_unsigned_number(_Settings.KEY_QUARTER_TILING_THRESHOLD, val);
|
|
}
|
|
static get WINDOW_BORDER_COLOR() {
|
|
return get_string(_Settings.KEY_WINDOW_BORDER_COLOR);
|
|
}
|
|
static set WINDOW_BORDER_COLOR(val) {
|
|
set_string(_Settings.KEY_WINDOW_BORDER_COLOR, val);
|
|
}
|
|
static get WINDOW_BORDER_WIDTH() {
|
|
return get_unsigned_number(_Settings.KEY_WINDOW_BORDER_WIDTH);
|
|
}
|
|
static set WINDOW_BORDER_WIDTH(val) {
|
|
set_unsigned_number(_Settings.KEY_WINDOW_BORDER_WIDTH, val);
|
|
}
|
|
static get ENABLE_SMART_WINDOW_BORDER_RADIUS() {
|
|
return get_boolean(_Settings.KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS);
|
|
}
|
|
static set ENABLE_SMART_WINDOW_BORDER_RADIUS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS, val);
|
|
}
|
|
static get ENABLE_WINDOW_BORDER() {
|
|
return get_boolean(_Settings.KEY_ENABLE_WINDOW_BORDER);
|
|
}
|
|
static set ENABLE_WINDOW_BORDER(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_WINDOW_BORDER, val);
|
|
}
|
|
static get SNAP_ASSISTANT_ANIMATION_TIME() {
|
|
return get_unsigned_number(_Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME);
|
|
}
|
|
static set SNAP_ASSISTANT_ANIMATION_TIME(val) {
|
|
set_unsigned_number(_Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME, val);
|
|
}
|
|
static get TILE_PREVIEW_ANIMATION_TIME() {
|
|
return get_unsigned_number(_Settings.KEY_TILE_PREVIEW_ANIMATION_TIME);
|
|
}
|
|
static set TILE_PREVIEW_ANIMATION_TIME(val) {
|
|
set_unsigned_number(_Settings.KEY_TILE_PREVIEW_ANIMATION_TIME, val);
|
|
}
|
|
static get ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS, val);
|
|
}
|
|
static get ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(
|
|
_Settings.KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS,
|
|
val
|
|
);
|
|
}
|
|
static get ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS, val);
|
|
}
|
|
static get_inner_gaps(scaleFactor = 1) {
|
|
const value = this.INNER_GAPS * scaleFactor;
|
|
return {
|
|
top: value,
|
|
bottom: value,
|
|
left: value,
|
|
right: value
|
|
};
|
|
}
|
|
static get_outer_gaps(scaleFactor = 1) {
|
|
const value = this.OUTER_GAPS * scaleFactor;
|
|
return {
|
|
top: value,
|
|
bottom: value,
|
|
left: value,
|
|
right: value
|
|
};
|
|
}
|
|
static get_layouts_json() {
|
|
try {
|
|
const layouts = JSON.parse(
|
|
this._settings?.get_string(this.KEY_SETTING_LAYOUTS_JSON) || "[]"
|
|
);
|
|
if (layouts.length === 0)
|
|
throw new Error("At least one layout is required");
|
|
return layouts.filter((layout) => layout.tiles.length > 0);
|
|
} catch (ex) {
|
|
this.reset_layouts_json();
|
|
return JSON.parse(
|
|
this._settings?.get_string(this.KEY_SETTING_LAYOUTS_JSON) || "[]"
|
|
);
|
|
}
|
|
}
|
|
static get_selected_layouts() {
|
|
const variant = this._settings?.get_value(
|
|
_Settings.KEY_SETTING_SELECTED_LAYOUTS
|
|
);
|
|
if (!variant)
|
|
return [];
|
|
const result = [];
|
|
for (let i = 0; i < variant.n_children(); i++) {
|
|
const monitor_variant = variant.get_child_value(i);
|
|
if (!monitor_variant)
|
|
continue;
|
|
const n_workspaces = monitor_variant.n_children();
|
|
const monitor_result = [];
|
|
for (let j = 0; j < n_workspaces; j++) {
|
|
const layout_variant = monitor_variant.get_child_value(j);
|
|
if (!layout_variant)
|
|
continue;
|
|
monitor_result.push(layout_variant.get_string()[0]);
|
|
}
|
|
result.push(monitor_result);
|
|
}
|
|
return result;
|
|
}
|
|
static reset_layouts_json() {
|
|
this.save_layouts_json([
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [1, 2]
|
|
}),
|
|
// top-left
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0.5,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [1, 2]
|
|
}),
|
|
// bottom-left
|
|
new Tile2({
|
|
x: 0.22,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.56,
|
|
groups: [2, 3]
|
|
}),
|
|
// center
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [3, 4]
|
|
}),
|
|
// top-right
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0.5,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [3, 4]
|
|
})
|
|
// bottom-right
|
|
],
|
|
"Layout 1"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.22,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.22,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.56,
|
|
groups: [1, 2]
|
|
}),
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.22,
|
|
groups: [2]
|
|
})
|
|
],
|
|
"Layout 2"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.33,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.33,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.67,
|
|
groups: [1]
|
|
})
|
|
],
|
|
"Layout 3"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.67,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.67,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.33,
|
|
groups: [1]
|
|
})
|
|
],
|
|
"Layout 4"
|
|
)
|
|
]);
|
|
}
|
|
static save_layouts_json(layouts) {
|
|
this._settings?.set_string(
|
|
this.KEY_SETTING_LAYOUTS_JSON,
|
|
JSON.stringify(layouts)
|
|
);
|
|
}
|
|
static save_selected_layouts(ids) {
|
|
if (ids.length === 0) {
|
|
this._settings?.reset(_Settings.KEY_SETTING_SELECTED_LAYOUTS);
|
|
return;
|
|
}
|
|
const variants = ids.map(
|
|
(monitor_ids) => GLib.Variant.new_strv(monitor_ids)
|
|
);
|
|
const result = GLib.Variant.new_array(null, variants);
|
|
this._settings?.set_value(
|
|
_Settings.KEY_SETTING_SELECTED_LAYOUTS,
|
|
// @ts-expect-error "'result' is of a correct variant type"
|
|
result
|
|
);
|
|
}
|
|
static connect(key, func) {
|
|
return this._settings?.connect(`changed::${key}`, func) || -1;
|
|
}
|
|
static disconnect(id) {
|
|
this._settings?.disconnect(id);
|
|
}
|
|
};
|
|
|
|
// src/utils/logger.ts
|
|
function rect_to_string(rect) {
|
|
return `{x: ${rect.x}, y: ${rect.y}, width: ${rect.width}, height: ${rect.height}}`;
|
|
}
|
|
var logger = (prefix) => (...content) => console.log("[tilingshell]", `[${prefix}]`, ...content);
|
|
|
|
// src/settings/settingsOverride.ts
|
|
var SettingsOverride = class _SettingsOverride {
|
|
// map schema_id with map of keys and old values
|
|
_overriddenKeys;
|
|
static _instance;
|
|
constructor() {
|
|
this._overriddenKeys = this._jsonToOverriddenKeys(
|
|
Settings.OVERRIDDEN_SETTINGS
|
|
);
|
|
}
|
|
static get() {
|
|
if (!this._instance)
|
|
this._instance = new _SettingsOverride();
|
|
return this._instance;
|
|
}
|
|
static destroy() {
|
|
if (!this._instance)
|
|
return;
|
|
this._instance.restoreAll();
|
|
this._instance = null;
|
|
}
|
|
/*
|
|
json will have the following structure
|
|
{
|
|
"schema.id": {
|
|
"overridden.key.one": oldvalue,
|
|
"overridden.key.two": oldvalue
|
|
...
|
|
},
|
|
...
|
|
}
|
|
*/
|
|
_overriddenKeysToJSON() {
|
|
const obj = {};
|
|
this._overriddenKeys.forEach((override, schemaId) => {
|
|
obj[schemaId] = {};
|
|
override.forEach((oldValue, key) => {
|
|
obj[schemaId][key] = oldValue.print(true);
|
|
});
|
|
});
|
|
return JSON.stringify(obj);
|
|
}
|
|
_jsonToOverriddenKeys(json) {
|
|
const result = /* @__PURE__ */ new Map();
|
|
const obj = JSON.parse(json);
|
|
for (const schemaId in obj) {
|
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
result.set(schemaId, schemaMap);
|
|
const overrideObj = obj[schemaId];
|
|
for (const key in overrideObj) {
|
|
schemaMap.set(
|
|
key,
|
|
GLib.Variant.parse(null, overrideObj[key], null, null)
|
|
);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
override(giosettings, keyToOverride, newValue) {
|
|
const schemaId = giosettings.schemaId;
|
|
const schemaMap = this._overriddenKeys.get(schemaId) || /* @__PURE__ */ new Map();
|
|
if (!this._overriddenKeys.has(schemaId))
|
|
this._overriddenKeys.set(schemaId, schemaMap);
|
|
const oldValue = schemaMap.has(keyToOverride) ? schemaMap.get(keyToOverride) : giosettings.get_value(keyToOverride);
|
|
const res = giosettings.set_value(keyToOverride, newValue);
|
|
if (!res)
|
|
return null;
|
|
if (!schemaMap.has(keyToOverride)) {
|
|
schemaMap.set(keyToOverride, oldValue);
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
return oldValue;
|
|
}
|
|
restoreKey(giosettings, keyToOverride) {
|
|
const overridden = this._overriddenKeys.get(giosettings.schemaId);
|
|
if (!overridden)
|
|
return null;
|
|
const oldValue = overridden.get(keyToOverride);
|
|
if (!oldValue)
|
|
return null;
|
|
const res = giosettings.set_value(keyToOverride, oldValue);
|
|
if (res) {
|
|
overridden.delete(keyToOverride);
|
|
if (overridden.size === 0)
|
|
this._overriddenKeys.delete(giosettings.schemaId);
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
return oldValue;
|
|
}
|
|
restoreAll() {
|
|
const schemaToDelete = [];
|
|
this._overriddenKeys.forEach(
|
|
(map, schemaId) => {
|
|
const giosettings = new Gio.Settings({ schemaId });
|
|
const overridden = this._overriddenKeys.get(
|
|
giosettings.schemaId
|
|
);
|
|
if (!overridden)
|
|
return;
|
|
const toDelete = [];
|
|
overridden.forEach((oldValue, key) => {
|
|
const done = giosettings.set_value(key, oldValue);
|
|
if (done)
|
|
toDelete.push(key);
|
|
});
|
|
toDelete.forEach((key) => overridden.delete(key));
|
|
if (overridden.size === 0)
|
|
schemaToDelete.push(schemaId);
|
|
}
|
|
);
|
|
schemaToDelete.forEach((schemaId) => {
|
|
this._overriddenKeys.delete(schemaId);
|
|
});
|
|
if (this._overriddenKeys.size === 0)
|
|
this._overriddenKeys = /* @__PURE__ */ new Map();
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
};
|
|
|
|
// src/settings/settingsExport.ts
|
|
var dconfPath = "/org/gnome/shell/extensions/tilingshell/";
|
|
var excludedKeys = [
|
|
Settings.KEY_SETTING_LAYOUTS_JSON,
|
|
Settings.KEY_LAST_VERSION_NAME_INSTALLED,
|
|
Settings.KEY_OVERRIDDEN_SETTINGS
|
|
];
|
|
var SettingsExport = class {
|
|
_gioSettings;
|
|
constructor(gioSettings) {
|
|
this._gioSettings = gioSettings;
|
|
}
|
|
exportToString() {
|
|
return this._excludeKeys(this._dumpDconf());
|
|
}
|
|
importFromString(content) {
|
|
this.restoreToDefault();
|
|
const proc = Gio.Subprocess.new(
|
|
["dconf", "load", dconfPath],
|
|
Gio.SubprocessFlags.STDIN_PIPE
|
|
);
|
|
proc.communicate_utf8(content, null);
|
|
if (!proc.get_successful()) {
|
|
this.restoreToDefault();
|
|
throw new Error(
|
|
"Failed to import dconf dump file. Restoring to default..."
|
|
);
|
|
}
|
|
}
|
|
restoreToDefault() {
|
|
Settings.ACTIVE_SCREEN_EDGES = false;
|
|
Settings.ENABLE_MOVE_KEYBINDINGS = false;
|
|
SettingsOverride.get().restoreAll();
|
|
this._gioSettings.list_keys().filter((key) => key.length > 0 && !excludedKeys.includes(key)).forEach((key) => this._gioSettings.reset(key));
|
|
}
|
|
_dumpDconf() {
|
|
const proc = Gio.Subprocess.new(
|
|
["dconf", "dump", dconfPath],
|
|
Gio.SubprocessFlags.STDOUT_PIPE
|
|
);
|
|
const [, dump] = proc.communicate_utf8(null, null);
|
|
if (proc.get_successful())
|
|
return dump;
|
|
else
|
|
throw new Error("Failed to dump dconf");
|
|
}
|
|
_excludeKeys(dconfDump) {
|
|
if (dconfDump.length === 0)
|
|
throw new Error("Empty dconf dump");
|
|
const keyFile = new GLib.KeyFile();
|
|
const length = new TextEncoder().encode(dconfDump).length;
|
|
if (!keyFile.load_from_data(dconfDump, length, GLib.KeyFileFlags.NONE))
|
|
throw new Error("Failed to load from dconf dump");
|
|
const [key_list] = keyFile.get_keys("/");
|
|
key_list.forEach((key) => {
|
|
if (excludedKeys.includes(key))
|
|
keyFile.remove_key("/", key);
|
|
});
|
|
const [data] = keyFile.to_data();
|
|
if (data)
|
|
return data;
|
|
else
|
|
throw new Error("Failed to exclude dconf keys");
|
|
}
|
|
};
|
|
|
|
// src/prefs.ts
|
|
var _a;
|
|
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
import * as Config from "resource:///org/gnome/Shell/Extensions/js/misc/config.js";
|
|
var debug = logger("prefs");
|
|
function buildPrefsWidget() {
|
|
return new Gtk.Label({
|
|
label: "Preferences"
|
|
});
|
|
}
|
|
var TilingShellExtensionPreferences = class extends ExtensionPreferences {
|
|
/**
|
|
* This function is called when the preferences window is first created to fill
|
|
* the `Adw.PreferencesWindow`.
|
|
*
|
|
* @param {Adw.PreferencesWindow} window - The preferences window
|
|
*/
|
|
fillPreferencesWindow(window) {
|
|
Settings.initialize(this.getSettings());
|
|
const prefsPage = new Adw.PreferencesPage({
|
|
name: "general",
|
|
title: _("General"),
|
|
iconName: "dialog-information-symbolic"
|
|
});
|
|
window.add(prefsPage);
|
|
const appearenceGroup = new Adw.PreferencesGroup({
|
|
title: _("Appearance"),
|
|
description: _("Configure the appearance of Tiling Shell")
|
|
});
|
|
prefsPage.add(appearenceGroup);
|
|
const showIndicatorRow = this._buildSwitchRow(
|
|
Settings.KEY_SHOW_INDICATOR,
|
|
_("Show Indicator"),
|
|
_("Whether to show the panel indicator")
|
|
);
|
|
appearenceGroup.add(showIndicatorRow);
|
|
const innerGapsRow = this._buildSpinButtonRow(
|
|
Settings.KEY_INNER_GAPS,
|
|
_("Inner gaps"),
|
|
_("Gaps between windows")
|
|
);
|
|
appearenceGroup.add(innerGapsRow);
|
|
const outerGapsRow = this._buildSpinButtonRow(
|
|
Settings.KEY_OUTER_GAPS,
|
|
_("Outer gaps"),
|
|
_("Gaps between a window and the monitor borders")
|
|
);
|
|
appearenceGroup.add(outerGapsRow);
|
|
const blurRow = new Adw.ExpanderRow({
|
|
title: _("Blur (experimental feature)"),
|
|
subtitle: _(
|
|
"Apply blur effect to Snap Assistant and tile previews"
|
|
)
|
|
});
|
|
appearenceGroup.add(blurRow);
|
|
const snapAssistantThresholdRow = this._buildSpinButtonRow(
|
|
Settings.KEY_SNAP_ASSISTANT_THRESHOLD,
|
|
_("Snap Assistant threshold"),
|
|
_(
|
|
"Minimum distance from the Snap Assistant to the pointer to open it"
|
|
),
|
|
0,
|
|
512
|
|
);
|
|
appearenceGroup.add(snapAssistantThresholdRow);
|
|
blurRow.add_row(
|
|
this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT,
|
|
_("Snap Assistant"),
|
|
_("Apply blur effect to Snap Assistant")
|
|
)
|
|
);
|
|
blurRow.add_row(
|
|
this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW,
|
|
_("Selected tile preview"),
|
|
_("Apply blur effect to selected tile preview")
|
|
)
|
|
);
|
|
const windowBorderRow = new Adw.ExpanderRow({
|
|
title: _("Window border"),
|
|
subtitle: _("Show a border around focused window")
|
|
});
|
|
appearenceGroup.add(windowBorderRow);
|
|
windowBorderRow.add_row(
|
|
this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_WINDOW_BORDER,
|
|
_("Enable"),
|
|
_("Show a border around focused window")
|
|
)
|
|
);
|
|
windowBorderRow.add_row(
|
|
this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS,
|
|
_("Smart border radius"),
|
|
_("Dynamically adapt to the window\u2019s actual border radius")
|
|
)
|
|
);
|
|
windowBorderRow.add_row(
|
|
this._buildSpinButtonRow(
|
|
Settings.KEY_WINDOW_BORDER_WIDTH,
|
|
_("Width"),
|
|
_("The size of the border"),
|
|
1
|
|
)
|
|
);
|
|
windowBorderRow.add_row(
|
|
this._buildColorRow(
|
|
_("Border color"),
|
|
_("Choose the color of the border"),
|
|
this._getRGBAFromString(Settings.WINDOW_BORDER_COLOR),
|
|
(val) => Settings.WINDOW_BORDER_COLOR = val
|
|
)
|
|
);
|
|
const animationsRow = new Adw.ExpanderRow({
|
|
title: _("Animations"),
|
|
subtitle: _("Customize animations")
|
|
});
|
|
appearenceGroup.add(animationsRow);
|
|
animationsRow.add_row(
|
|
this._buildSpinButtonRow(
|
|
Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME,
|
|
_("Snap assistant animation time"),
|
|
_("The snap assistant animation time in milliseconds"),
|
|
0,
|
|
2e3
|
|
)
|
|
);
|
|
animationsRow.add_row(
|
|
this._buildSpinButtonRow(
|
|
Settings.KEY_TILE_PREVIEW_ANIMATION_TIME,
|
|
_("Tiles animation time"),
|
|
_("The tiles animation time in milliseconds"),
|
|
0,
|
|
2e3
|
|
)
|
|
);
|
|
const behaviourGroup = new Adw.PreferencesGroup({
|
|
title: _("Behaviour"),
|
|
description: _("Configure the behaviour of Tiling Shell")
|
|
});
|
|
prefsPage.add(behaviourGroup);
|
|
const snapAssistRow = this._buildSwitchRow(
|
|
Settings.KEY_SNAP_ASSIST,
|
|
_("Enable Snap Assistant"),
|
|
_("Move the window on top of the screen to snap assist it")
|
|
);
|
|
behaviourGroup.add(snapAssistRow);
|
|
const enableTilingSystemRow = this._buildSwitchRow(
|
|
Settings.KEY_TILING_SYSTEM,
|
|
_("Enable Tiling System"),
|
|
_("Hold the activation key while moving a window to tile it"),
|
|
this._buildActivationKeysDropDown(
|
|
Settings.TILING_SYSTEM_ACTIVATION_KEY,
|
|
(val) => Settings.TILING_SYSTEM_ACTIVATION_KEY = val
|
|
)
|
|
);
|
|
behaviourGroup.add(enableTilingSystemRow);
|
|
const tilingSystemDeactivationRow = this._buildDropDownRow(
|
|
_("Tiling System deactivation key"),
|
|
_(
|
|
"Hold the deactivation key while moving a window to deactivate the tiling system"
|
|
),
|
|
Settings.TILING_SYSTEM_DEACTIVATION_KEY,
|
|
(val) => Settings.TILING_SYSTEM_DEACTIVATION_KEY = val
|
|
);
|
|
behaviourGroup.add(tilingSystemDeactivationRow);
|
|
const spanMultipleTilesRow = this._buildSwitchRow(
|
|
Settings.KEY_SPAN_MULTIPLE_TILES,
|
|
_("Span multiple tiles"),
|
|
_("Hold the activation key to span multiple tiles"),
|
|
this._buildActivationKeysDropDown(
|
|
Settings.SPAN_MULTIPLE_TILES_ACTIVATION_KEY,
|
|
(val) => Settings.SPAN_MULTIPLE_TILES_ACTIVATION_KEY = val
|
|
)
|
|
);
|
|
behaviourGroup.add(spanMultipleTilesRow);
|
|
const autoTilingRow = this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_AUTO_TILING,
|
|
_("Enable Auto Tiling"),
|
|
_("Automatically tile new windows to the best tile")
|
|
);
|
|
behaviourGroup.add(autoTilingRow);
|
|
const resizeComplementingRow = this._buildSwitchRow(
|
|
Settings.KEY_RESIZE_COMPLEMENTING_WINDOWS,
|
|
_("Enable auto-resize of the complementing tiled windows"),
|
|
_(
|
|
"When a tiled window is resized, auto-resize the other tiled windows near it"
|
|
)
|
|
);
|
|
behaviourGroup.add(resizeComplementingRow);
|
|
const restoreToOriginalSizeRow = this._buildSwitchRow(
|
|
Settings.KEY_RESTORE_WINDOW_ORIGINAL_SIZE,
|
|
_("Restore window size"),
|
|
_(
|
|
"Whether to restore the windows to their original size when untiled"
|
|
)
|
|
);
|
|
behaviourGroup.add(restoreToOriginalSizeRow);
|
|
const overrideWindowMenuRow = this._buildSwitchRow(
|
|
Settings.KEY_OVERRIDE_WINDOW_MENU,
|
|
_("Add snap assistant and auto-tile buttons to window menu"),
|
|
_(
|
|
"Add snap assistant and auto-tile buttons in the menu that shows up when you right click on a window title"
|
|
)
|
|
);
|
|
behaviourGroup.add(overrideWindowMenuRow);
|
|
const activeScreenEdgesGroup = new Adw.PreferencesGroup({
|
|
title: _("Screen Edges"),
|
|
description: _(
|
|
"Drag windows against the top, left and right screen edges to resize them"
|
|
),
|
|
headerSuffix: new Gtk.Switch({
|
|
vexpand: false,
|
|
valign: Gtk.Align.CENTER
|
|
})
|
|
});
|
|
Settings.bind(
|
|
Settings.KEY_ACTIVE_SCREEN_EDGES,
|
|
activeScreenEdgesGroup.headerSuffix,
|
|
"active"
|
|
);
|
|
const topEdgeMaximize = this._buildSwitchRow(
|
|
Settings.KEY_TOP_EDGE_MAXIMIZE,
|
|
_("Drag against top edge to maximize window"),
|
|
_("Drag windows against the top edge to maximize them")
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_ACTIVE_SCREEN_EDGES,
|
|
topEdgeMaximize,
|
|
"sensitive"
|
|
);
|
|
activeScreenEdgesGroup.add(topEdgeMaximize);
|
|
const quarterTiling = this._buildScaleRow(
|
|
_("Quarter tiling activation area"),
|
|
_("Activation area to trigger quarter tiling (%% of the screen)"),
|
|
(sc) => {
|
|
Settings.QUARTER_TILING_THRESHOLD = sc.get_value();
|
|
},
|
|
Settings.QUARTER_TILING_THRESHOLD,
|
|
1,
|
|
50,
|
|
1
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_ACTIVE_SCREEN_EDGES,
|
|
quarterTiling,
|
|
"sensitive"
|
|
);
|
|
activeScreenEdgesGroup.add(quarterTiling);
|
|
prefsPage.add(activeScreenEdgesGroup);
|
|
const windowsSuggestionsGroup = new Adw.PreferencesGroup({
|
|
title: _("Windows suggestions"),
|
|
description: _("Enable and disable windows suggestions")
|
|
});
|
|
prefsPage.add(behaviourGroup);
|
|
const tilingSystemWindowSuggestionRow = this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS,
|
|
_("Enable window suggestions for the tiling system"),
|
|
_(
|
|
"Provides smart suggestions to fill empty tiles when using the tiling system"
|
|
)
|
|
);
|
|
windowsSuggestionsGroup.add(tilingSystemWindowSuggestionRow);
|
|
const snapAssistWindowSuggestionRow = this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS,
|
|
_("Enable window suggestions for the snap assistant"),
|
|
_(
|
|
"Offers suggestions to populate empty tiles when using the snap assistant"
|
|
)
|
|
);
|
|
windowsSuggestionsGroup.add(snapAssistWindowSuggestionRow);
|
|
const screenEdgesWindowSuggestionRow = this._buildSwitchRow(
|
|
Settings.KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS,
|
|
_("Enable window suggestions for screen edge snapping"),
|
|
_(
|
|
"Suggests windows to occupy empty tiles when snapping to screen edges"
|
|
)
|
|
);
|
|
windowsSuggestionsGroup.add(screenEdgesWindowSuggestionRow);
|
|
prefsPage.add(windowsSuggestionsGroup);
|
|
const layoutsGroup = new Adw.PreferencesGroup({
|
|
title: _("Layouts"),
|
|
description: _("Configure the layouts of Tiling Shell")
|
|
});
|
|
prefsPage.add(layoutsGroup);
|
|
const editLayoutsBtn = this._buildButtonRow(
|
|
_("Edit layouts"),
|
|
_("Edit layouts"),
|
|
_("Open the layouts editor"),
|
|
() => this._openLayoutEditor()
|
|
);
|
|
layoutsGroup.add(editLayoutsBtn);
|
|
const exportLayoutsBtn = this._buildButtonRow(
|
|
_("Export layouts"),
|
|
_("Export layouts"),
|
|
_("Export layouts to a file"),
|
|
() => {
|
|
const fc = this._buildFileChooserDialog(
|
|
_("Export layouts"),
|
|
Gtk.FileChooserAction.SAVE,
|
|
window,
|
|
_("Save"),
|
|
_("Cancel"),
|
|
new Gtk.FileFilter({
|
|
suffixes: ["json"],
|
|
name: "JSON"
|
|
}),
|
|
(_source, response_id) => {
|
|
try {
|
|
if (response_id === Gtk.ResponseType.ACCEPT) {
|
|
const file = _source.get_file();
|
|
if (!file)
|
|
throw new Error("no file selected");
|
|
debug(
|
|
`Create file with path ${file.get_path()}`
|
|
);
|
|
const content = JSON.stringify(
|
|
Settings.get_layouts_json()
|
|
);
|
|
file.replace_contents_bytes_async(
|
|
new TextEncoder().encode(content),
|
|
null,
|
|
false,
|
|
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
|
null,
|
|
(thisFile, res) => {
|
|
try {
|
|
thisFile?.replace_contents_finish(
|
|
res
|
|
);
|
|
} catch (e) {
|
|
debug(e);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
debug(error);
|
|
}
|
|
_source.destroy();
|
|
}
|
|
);
|
|
fc.set_current_name("tilingshell-layouts.json");
|
|
fc.show();
|
|
}
|
|
);
|
|
layoutsGroup.add(exportLayoutsBtn);
|
|
const importLayoutsBtn = this._buildButtonRow(
|
|
_("Import layouts"),
|
|
_("Import layouts"),
|
|
_("Import layouts from a file"),
|
|
() => {
|
|
const fc = this._buildFileChooserDialog(
|
|
_("Select layouts file"),
|
|
Gtk.FileChooserAction.OPEN,
|
|
window,
|
|
_("Open"),
|
|
_("Cancel"),
|
|
new Gtk.FileFilter({
|
|
suffixes: ["json"],
|
|
name: "JSON"
|
|
}),
|
|
(_source, response_id) => {
|
|
try {
|
|
if (response_id === Gtk.ResponseType.ACCEPT) {
|
|
const file = _source.get_file();
|
|
if (!file) {
|
|
_source.destroy();
|
|
return;
|
|
}
|
|
debug(`Selected path ${file.get_path()}`);
|
|
const [success, content] = file.load_contents(null);
|
|
if (success) {
|
|
let importedLayouts = JSON.parse(
|
|
new TextDecoder("utf-8").decode(
|
|
content
|
|
)
|
|
);
|
|
if (importedLayouts.length === 0) {
|
|
throw new Error(
|
|
"At least one layout is required"
|
|
);
|
|
}
|
|
importedLayouts = importedLayouts.filter(
|
|
(layout) => layout.tiles.length > 0
|
|
);
|
|
const newLayouts = Settings.get_layouts_json();
|
|
newLayouts.push(...importedLayouts);
|
|
Settings.save_layouts_json(newLayouts);
|
|
} else {
|
|
debug("Error while opening file");
|
|
}
|
|
}
|
|
} catch (error) {
|
|
debug(error);
|
|
}
|
|
_source.destroy();
|
|
}
|
|
);
|
|
fc.show();
|
|
}
|
|
);
|
|
layoutsGroup.add(importLayoutsBtn);
|
|
const resetBtn = this._buildButtonRow(
|
|
_("Reset layouts"),
|
|
_("Reset layouts"),
|
|
_("Bring back the default layouts"),
|
|
() => {
|
|
Settings.reset_layouts_json();
|
|
const layouts = Settings.get_layouts_json();
|
|
const newSelectedLayouts = Settings.get_selected_layouts().map(
|
|
(monitors_selected) => monitors_selected.map(() => layouts[0].id)
|
|
);
|
|
Settings.save_selected_layouts(newSelectedLayouts);
|
|
},
|
|
"destructive-action"
|
|
);
|
|
layoutsGroup.add(resetBtn);
|
|
const keybindingsGroup = new Adw.PreferencesGroup({
|
|
title: _("Keybindings"),
|
|
description: _(
|
|
"Use hotkeys to perform actions on the focused window"
|
|
),
|
|
headerSuffix: new Gtk.Switch({
|
|
vexpand: false,
|
|
valign: Gtk.Align.CENTER
|
|
})
|
|
});
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_MOVE_KEYBINDINGS,
|
|
keybindingsGroup.headerSuffix,
|
|
"active"
|
|
);
|
|
prefsPage.add(keybindingsGroup);
|
|
const gioSettings = this.getSettings();
|
|
const keybindings = [
|
|
[
|
|
Settings.SETTING_MOVE_WINDOW_RIGHT,
|
|
// settings key
|
|
_("Move window to right tile"),
|
|
// title
|
|
_("Move the focused window to the tile on its right"),
|
|
// subtitle
|
|
false,
|
|
// is set
|
|
true
|
|
// is on main page
|
|
],
|
|
[
|
|
Settings.SETTING_MOVE_WINDOW_LEFT,
|
|
_("Move window to left tile"),
|
|
_("Move the focused window to the tile on its left"),
|
|
false,
|
|
true
|
|
],
|
|
[
|
|
Settings.SETTING_MOVE_WINDOW_UP,
|
|
_("Move window to tile above"),
|
|
_("Move the focused window to the tile above"),
|
|
false,
|
|
true
|
|
],
|
|
[
|
|
Settings.SETTING_MOVE_WINDOW_DOWN,
|
|
_("Move window to tile below"),
|
|
_("Move the focused window to the tile below"),
|
|
false,
|
|
true
|
|
],
|
|
[
|
|
Settings.SETTING_SPAN_WINDOW_RIGHT,
|
|
_("Span window to right tile"),
|
|
_("Span the focused window to the tile on its right"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_SPAN_WINDOW_LEFT,
|
|
_("Span window to left tile"),
|
|
_("Span the focused window to the tile on its left"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_SPAN_WINDOW_UP,
|
|
_("Span window above"),
|
|
_("Span the focused window to the tile above"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_SPAN_WINDOW_DOWN,
|
|
_("Span window down"),
|
|
_("Span the focused window to the tile below"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_SPAN_WINDOW_ALL_TILES,
|
|
_("Span window to all tiles"),
|
|
_("Span the focused window to all the tiles"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_UNTILE_WINDOW,
|
|
_("Untile focused window"),
|
|
void 0,
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_MOVE_WINDOW_CENTER,
|
|
// settings key
|
|
_("Move window to the center"),
|
|
// title
|
|
_("Move the focused window to the center of the screen"),
|
|
// subtitle
|
|
false,
|
|
// is set
|
|
false
|
|
// is on main page
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_RIGHT,
|
|
_("Focus window to the right"),
|
|
_(
|
|
"Focus the window to the right of the current focused window"
|
|
),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_LEFT,
|
|
_("Focus window to the left"),
|
|
_("Focus the window to the left of the current focused window"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_UP,
|
|
_("Focus window above"),
|
|
_("Focus the window above the current focused window"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_DOWN,
|
|
_("Focus window below"),
|
|
_("Focus the window below the current focused window"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_NEXT,
|
|
_("Focus next window"),
|
|
_("Focus the window next to the current focused window"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_FOCUS_WINDOW_PREV,
|
|
_("Focus previous window"),
|
|
_("Focus the window prior to the current focused window"),
|
|
false,
|
|
false
|
|
],
|
|
[
|
|
Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW,
|
|
_("Highlight focused window"),
|
|
_(
|
|
"Minimize all the other windows and show only the focused window"
|
|
),
|
|
false,
|
|
false
|
|
]
|
|
];
|
|
for (let i = 0; i < keybindings.length; i++) {
|
|
keybindings[i][3] = gioSettings.get_strv(keybindings[i][0])[0].length > 0;
|
|
}
|
|
keybindings.forEach(
|
|
([settingsKey, title, subtitle, isSet, isOnMainPage]) => {
|
|
if (!isSet && !isOnMainPage)
|
|
return;
|
|
const row = this._buildShortcutButtonRow(
|
|
settingsKey,
|
|
gioSettings,
|
|
title,
|
|
subtitle
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_MOVE_KEYBINDINGS,
|
|
row,
|
|
"sensitive"
|
|
);
|
|
keybindingsGroup.add(row);
|
|
}
|
|
);
|
|
const openKeybindingsDialogRow = new Adw.ActionRow({
|
|
title: _("View and Customize all the Shortcuts"),
|
|
activatable: true
|
|
});
|
|
openKeybindingsDialogRow.add_suffix(
|
|
new Gtk.Image({
|
|
icon_name: "go-next-symbolic",
|
|
valign: Gtk.Align.CENTER
|
|
})
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_MOVE_KEYBINDINGS,
|
|
openKeybindingsDialogRow,
|
|
"sensitive"
|
|
);
|
|
keybindingsGroup.add(openKeybindingsDialogRow);
|
|
const keybindingsDialog = new Adw.PreferencesWindow({
|
|
searchEnabled: true,
|
|
modal: true,
|
|
hide_on_close: true,
|
|
transient_for: window,
|
|
width_request: 480,
|
|
height_request: 320
|
|
});
|
|
openKeybindingsDialogRow.connect(
|
|
"activated",
|
|
() => keybindingsDialog.present()
|
|
);
|
|
const keybindingsPage = new Adw.PreferencesPage({
|
|
name: _("View and Customize Shortcuts"),
|
|
title: _("View and Customize Shortcuts"),
|
|
iconName: "dialog-information-symbolic"
|
|
});
|
|
keybindingsDialog.add(keybindingsPage);
|
|
const keybindingsDialogGroup = new Adw.PreferencesGroup();
|
|
keybindingsPage.add(keybindingsDialogGroup);
|
|
keybindings.forEach(([settingsKey, title, subtitle]) => {
|
|
const row = this._buildShortcutButtonRow(
|
|
settingsKey,
|
|
gioSettings,
|
|
title,
|
|
subtitle
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_MOVE_KEYBINDINGS,
|
|
row,
|
|
"sensitive"
|
|
);
|
|
keybindingsDialogGroup.add(row);
|
|
});
|
|
const wrapAroundRow = this._buildSwitchRow(
|
|
Settings.KEY_WRAPAROUND_FOCUS,
|
|
_("Enable next/previous window focus to wrap around"),
|
|
_(
|
|
"When focusing next or previous window, wrap around at the window edge"
|
|
)
|
|
);
|
|
keybindingsGroup.add(wrapAroundRow);
|
|
const importExportGroup = new Adw.PreferencesGroup({
|
|
title: _("Import, export and reset"),
|
|
description: _(
|
|
"Import, export and reset the settings of Tiling Shell"
|
|
)
|
|
});
|
|
prefsPage.add(importExportGroup);
|
|
const exportSettingsBtn = this._buildButtonRow(
|
|
_("Export settings"),
|
|
_("Export settings"),
|
|
_("Export settings to a file"),
|
|
() => {
|
|
const fc = this._buildFileChooserDialog(
|
|
_("Export settings to a text file"),
|
|
Gtk.FileChooserAction.SAVE,
|
|
window,
|
|
_("Save"),
|
|
_("Cancel"),
|
|
new Gtk.FileFilter({
|
|
suffixes: ["txt"],
|
|
name: _("Text file")
|
|
}),
|
|
(_source, response_id) => {
|
|
try {
|
|
if (response_id === Gtk.ResponseType.ACCEPT) {
|
|
const file = _source.get_file();
|
|
if (!file)
|
|
throw new Error("no file selected");
|
|
debug(
|
|
`Create file with path ${file.get_path()}`
|
|
);
|
|
const settingsExport = new SettingsExport(
|
|
this.getSettings()
|
|
);
|
|
const content = settingsExport.exportToString();
|
|
file.replace_contents_bytes_async(
|
|
new TextEncoder().encode(content),
|
|
null,
|
|
false,
|
|
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
|
null,
|
|
(thisFile, res) => {
|
|
try {
|
|
thisFile?.replace_contents_finish(
|
|
res
|
|
);
|
|
} catch (e) {
|
|
debug(e);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
debug(error);
|
|
}
|
|
_source.destroy();
|
|
}
|
|
);
|
|
fc.set_current_name("tilingshell-settings.txt");
|
|
fc.show();
|
|
}
|
|
);
|
|
importExportGroup.add(exportSettingsBtn);
|
|
const importSettingsBtn = this._buildButtonRow(
|
|
_("Import settings"),
|
|
_("Import settings"),
|
|
_("Import settings from a file"),
|
|
() => {
|
|
const fc = this._buildFileChooserDialog(
|
|
_("Select a text file to import from"),
|
|
Gtk.FileChooserAction.OPEN,
|
|
window,
|
|
_("Open"),
|
|
_("Cancel"),
|
|
new Gtk.FileFilter({
|
|
suffixes: ["txt"],
|
|
name: "Text file"
|
|
}),
|
|
(_source, response_id) => {
|
|
try {
|
|
if (response_id === Gtk.ResponseType.ACCEPT) {
|
|
const file = _source.get_file();
|
|
if (!file) {
|
|
_source.destroy();
|
|
return;
|
|
}
|
|
debug(`Selected path ${file.get_path()}`);
|
|
const [success, content] = file.load_contents(null);
|
|
if (success) {
|
|
const imported = new TextDecoder(
|
|
"utf-8"
|
|
).decode(content);
|
|
const settingsExport = new SettingsExport(
|
|
this.getSettings()
|
|
);
|
|
settingsExport.importFromString(imported);
|
|
} else {
|
|
debug("Error while opening file");
|
|
}
|
|
}
|
|
} catch (error) {
|
|
debug(error);
|
|
}
|
|
_source.destroy();
|
|
}
|
|
);
|
|
fc.show();
|
|
}
|
|
);
|
|
importExportGroup.add(importSettingsBtn);
|
|
const resetSettingsBtn = this._buildButtonRow(
|
|
_("Reset settings"),
|
|
_("Reset settings"),
|
|
_("Bring back the default settings"),
|
|
() => new SettingsExport(this.getSettings()).restoreToDefault(),
|
|
"destructive-action"
|
|
);
|
|
importExportGroup.add(resetSettingsBtn);
|
|
const footerGroup = new Adw.PreferencesGroup();
|
|
prefsPage.add(footerGroup);
|
|
const buttons = new Gtk.Box({
|
|
hexpand: false,
|
|
spacing: 8,
|
|
margin_bottom: 16,
|
|
halign: Gtk.Align.CENTER
|
|
});
|
|
buttons.append(
|
|
this._buildLinkButton(
|
|
`\u2665\uFE0E ${_("Donate on ko-fi")}`,
|
|
"https://ko-fi.com/domferr"
|
|
)
|
|
);
|
|
buttons.append(
|
|
this._buildLinkButton(
|
|
_("Report a bug"),
|
|
"https://github.com/domferr/tilingshell/issues/new?template=bug_report.md"
|
|
)
|
|
);
|
|
buttons.append(
|
|
this._buildLinkButton(
|
|
_("Request a feature"),
|
|
"https://github.com/domferr/tilingshell/issues/new?template=feature_request.md"
|
|
)
|
|
);
|
|
footerGroup.add(buttons);
|
|
footerGroup.add(
|
|
new Gtk.Label({
|
|
label: _(
|
|
"Have issues, you want to suggest a new feature or contribute?"
|
|
),
|
|
margin_bottom: 4
|
|
})
|
|
);
|
|
footerGroup.add(
|
|
new Gtk.Label({
|
|
label: `${_("Open a new issue on")} <a href="https://github.com/domferr/tilingshell">GitHub</a>!`,
|
|
useMarkup: true,
|
|
margin_bottom: 32
|
|
})
|
|
);
|
|
if (this.metadata["version-name"]) {
|
|
footerGroup.add(
|
|
new Gtk.Label({
|
|
label: `\xB7 Tiling Shell v${this.metadata["version-name"]} \xB7`
|
|
})
|
|
);
|
|
}
|
|
window.searchEnabled = true;
|
|
window.connect("close-request", () => {
|
|
Settings.destroy();
|
|
});
|
|
}
|
|
_buildSwitchRow(settingsKey, title, subtitle, suffix) {
|
|
const gtkSwitch = new Gtk.Switch({
|
|
vexpand: false,
|
|
valign: Gtk.Align.CENTER
|
|
});
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: gtkSwitch
|
|
});
|
|
if (suffix)
|
|
adwRow.add_suffix(suffix);
|
|
adwRow.add_suffix(gtkSwitch);
|
|
Settings.bind(settingsKey, gtkSwitch, "active");
|
|
return adwRow;
|
|
}
|
|
_buildDropDownRow(title, subtitle, initialValue, onChange, styleClass) {
|
|
const dropDown = this._buildActivationKeysDropDown(
|
|
initialValue,
|
|
onChange,
|
|
styleClass
|
|
);
|
|
dropDown.set_vexpand(false);
|
|
dropDown.set_valign(Gtk.Align.CENTER);
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: dropDown
|
|
});
|
|
adwRow.add_suffix(dropDown);
|
|
return adwRow;
|
|
}
|
|
_buildSpinButtonRow(settingsKey, title, subtitle, min = 0, max = 32) {
|
|
const spinBtn = Gtk.SpinButton.new_with_range(min, max, 1);
|
|
spinBtn.set_vexpand(false);
|
|
spinBtn.set_valign(Gtk.Align.CENTER);
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: spinBtn
|
|
});
|
|
adwRow.add_suffix(spinBtn);
|
|
Settings.bind(settingsKey, spinBtn, "value");
|
|
return adwRow;
|
|
}
|
|
_buildButtonRow(label, title, subtitle, onClick, styleClass) {
|
|
const btn = Gtk.Button.new_with_label(label);
|
|
if (styleClass)
|
|
btn.add_css_class(styleClass);
|
|
btn.connect("clicked", onClick);
|
|
btn.set_vexpand(false);
|
|
btn.set_valign(Gtk.Align.CENTER);
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: btn
|
|
});
|
|
adwRow.add_suffix(btn);
|
|
return adwRow;
|
|
}
|
|
_openLayoutEditor() {
|
|
try {
|
|
Gio.DBus.session.call_sync(
|
|
"org.gnome.Shell",
|
|
"/org/gnome/Shell/Extensions/TilingShell",
|
|
"org.gnome.Shell.Extensions.TilingShell",
|
|
"openLayoutEditor",
|
|
null,
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null
|
|
);
|
|
} catch (e) {
|
|
if (e instanceof Gio.DBusError)
|
|
Gio.DBusError.strip_remote_error(e);
|
|
console.error(e);
|
|
}
|
|
}
|
|
_buildActivationKeysDropDown(initialValue, onChange, styleClass) {
|
|
const options = new Gtk.StringList();
|
|
const activationKeys = [
|
|
0 /* CTRL */,
|
|
1 /* ALT */,
|
|
2 /* SUPER */
|
|
];
|
|
activationKeys.forEach((k) => options.append(ActivationKey[k]));
|
|
options.append("(None)");
|
|
const dropdown = new Gtk.DropDown({
|
|
model: options,
|
|
selected: initialValue
|
|
});
|
|
dropdown.connect("notify::selected-item", (dd) => {
|
|
const index = dd.get_selected();
|
|
const selected = index < 0 || index >= activationKeys.length ? -1 /* NONE */ : activationKeys[index];
|
|
onChange(selected);
|
|
});
|
|
if (styleClass)
|
|
dropdown.add_css_class(styleClass);
|
|
dropdown.set_vexpand(false);
|
|
dropdown.set_valign(Gtk.Align.CENTER);
|
|
return dropdown;
|
|
}
|
|
_buildLinkButton(label, uri) {
|
|
const btn = new Gtk.Button({
|
|
label,
|
|
hexpand: false
|
|
});
|
|
btn.connect("clicked", () => {
|
|
Gtk.show_uri(null, uri, Gdk.CURRENT_TIME);
|
|
});
|
|
return btn;
|
|
}
|
|
_buildShortcutButtonRow(settingsKey, gioSettings, title, subtitle, styleClass) {
|
|
const btn = new ShortcutSettingButton(settingsKey, gioSettings);
|
|
if (styleClass)
|
|
btn.add_css_class(styleClass);
|
|
btn.set_vexpand(false);
|
|
btn.set_valign(Gtk.Align.CENTER);
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
activatableWidget: btn
|
|
});
|
|
if (subtitle)
|
|
adwRow.set_subtitle(subtitle);
|
|
adwRow.add_suffix(btn);
|
|
return adwRow;
|
|
}
|
|
_buildScaleRow(title, subtitle, onChange, initialValue, min, max, step) {
|
|
const scale = Gtk.Scale.new_with_range(
|
|
Gtk.Orientation.HORIZONTAL,
|
|
min,
|
|
max,
|
|
step
|
|
);
|
|
scale.set_value(initialValue);
|
|
scale.set_vexpand(false);
|
|
scale.set_valign(Gtk.Align.CENTER);
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: scale
|
|
});
|
|
scale.connect("value-changed", onChange);
|
|
scale.set_size_request(150, -1);
|
|
scale.set_digits(0);
|
|
scale.set_draw_value(true);
|
|
adwRow.add_suffix(scale);
|
|
return adwRow;
|
|
}
|
|
_getRGBAFromString(str) {
|
|
const rgba = new Gdk.RGBA();
|
|
rgba.parse(str);
|
|
return rgba;
|
|
}
|
|
_buildColorRow(title, subtitle, rgba, onChange) {
|
|
const colorButton = new Gtk.ColorButton({
|
|
rgba,
|
|
use_alpha: true,
|
|
valign: Gtk.Align.CENTER
|
|
});
|
|
colorButton.connect("color-set", () => {
|
|
onChange(colorButton.get_rgba().to_string());
|
|
});
|
|
const adwRow = new Adw.ActionRow({
|
|
title,
|
|
subtitle,
|
|
activatableWidget: colorButton
|
|
});
|
|
adwRow.add_suffix(colorButton);
|
|
return adwRow;
|
|
}
|
|
_buildFileChooserDialog(title, action, window, accept, cancel, filter, onResponse) {
|
|
const fc = new Gtk.FileChooserNative({
|
|
title,
|
|
action,
|
|
select_multiple: false,
|
|
modal: true,
|
|
accept_label: accept,
|
|
cancel_label: cancel
|
|
});
|
|
window.connect("map", () => {
|
|
fc.set_transient_for(window);
|
|
});
|
|
const [major] = Config.PACKAGE_VERSION.split(".").map(
|
|
(s) => Number(s)
|
|
);
|
|
if (major >= 43)
|
|
fc.set_filter(filter);
|
|
fc.set_current_folder(Gio.File.new_for_path(GLib.get_home_dir()));
|
|
fc.connect("response", onResponse);
|
|
return fc;
|
|
}
|
|
};
|
|
var ShortcutSettingButton = (_a = class extends Gtk.Button {
|
|
_editor;
|
|
_label;
|
|
_shortcut;
|
|
_settingsKey;
|
|
_gioSettings;
|
|
constructor(settingsKey, gioSettings) {
|
|
super({
|
|
halign: Gtk.Align.CENTER,
|
|
hexpand: false,
|
|
vexpand: false,
|
|
has_frame: false
|
|
});
|
|
this._shortcut = "";
|
|
this._settingsKey = settingsKey;
|
|
this._gioSettings = gioSettings;
|
|
this._editor = null;
|
|
this._label = new Gtk.ShortcutLabel({
|
|
disabled_text: _("New accelerator\u2026"),
|
|
valign: Gtk.Align.CENTER,
|
|
hexpand: false,
|
|
vexpand: false
|
|
});
|
|
this.connect("clicked", this._onActivated.bind(this));
|
|
gioSettings.connect(`changed::${settingsKey}`, () => {
|
|
[this.shortcut] = gioSettings.get_strv(settingsKey);
|
|
this._label.set_accelerator(this.shortcut);
|
|
});
|
|
[this.shortcut] = gioSettings.get_strv(settingsKey);
|
|
this._label.set_accelerator(this.shortcut);
|
|
this.set_child(this._label);
|
|
}
|
|
set shortcut(value) {
|
|
this._shortcut = value;
|
|
}
|
|
get shortcut() {
|
|
return this._shortcut;
|
|
}
|
|
_onActivated(widget) {
|
|
const ctl = new Gtk.EventControllerKey();
|
|
const content = new Adw.StatusPage({
|
|
title: _("New accelerator\u2026"),
|
|
// description: this._description,
|
|
icon_name: "preferences-desktop-keyboard-shortcuts-symbolic",
|
|
description: _("Use Backspace to clear")
|
|
});
|
|
this._editor = new Adw.Window({
|
|
modal: true,
|
|
hide_on_close: true,
|
|
// @ts-expect-error "widget has get_root function"
|
|
transient_for: widget.get_root(),
|
|
width_request: 480,
|
|
height_request: 320,
|
|
content
|
|
});
|
|
this._editor.add_controller(ctl);
|
|
ctl.connect("key-pressed", this._onKeyPressed.bind(this));
|
|
this._editor.present();
|
|
}
|
|
_onKeyPressed(_widget, keyval, keycode, state) {
|
|
let mask = state & Gtk.accelerator_get_default_mod_mask();
|
|
mask &= ~Gdk.ModifierType.LOCK_MASK;
|
|
if (!mask && keyval === Gdk.KEY_Escape) {
|
|
this._editor?.close();
|
|
return Gdk.EVENT_STOP;
|
|
}
|
|
if (keyval === Gdk.KEY_BackSpace) {
|
|
this._updateShortcut("");
|
|
this._editor?.close();
|
|
return Gdk.EVENT_STOP;
|
|
}
|
|
if (!this.isValidBinding(mask, keycode, keyval) || !this.isValidAccel(mask, keyval))
|
|
return Gdk.EVENT_STOP;
|
|
if (!keyval && !keycode) {
|
|
this._editor?.destroy();
|
|
return Gdk.EVENT_STOP;
|
|
} else {
|
|
const val = Gtk.accelerator_name_with_keycode(
|
|
null,
|
|
keyval,
|
|
keycode,
|
|
mask
|
|
);
|
|
this._updateShortcut(val);
|
|
}
|
|
this._editor?.destroy();
|
|
return Gdk.EVENT_STOP;
|
|
}
|
|
_updateShortcut(val) {
|
|
this.shortcut = val;
|
|
this._label.set_accelerator(this.shortcut);
|
|
this._gioSettings.set_strv(this._settingsKey, [this.shortcut]);
|
|
this.emit("changed", this.shortcut);
|
|
}
|
|
// Functions from https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/panels/keyboard/keyboard-shortcuts.c
|
|
keyvalIsForbidden(keyval) {
|
|
return [
|
|
// Navigation keys
|
|
Gdk.KEY_Home,
|
|
Gdk.KEY_Left,
|
|
Gdk.KEY_Up,
|
|
Gdk.KEY_Right,
|
|
Gdk.KEY_Down,
|
|
Gdk.KEY_Page_Up,
|
|
Gdk.KEY_Page_Down,
|
|
Gdk.KEY_End,
|
|
Gdk.KEY_Tab,
|
|
// Return
|
|
Gdk.KEY_KP_Enter,
|
|
Gdk.KEY_Return,
|
|
Gdk.KEY_Mode_switch
|
|
].includes(keyval);
|
|
}
|
|
isValidBinding(mask, keycode, keyval) {
|
|
return !(mask === 0 || // @ts-expect-error "Gdk has SHIFT_MASK"
|
|
mask === Gdk.SHIFT_MASK && keycode !== 0 && (keyval >= Gdk.KEY_a && keyval <= Gdk.KEY_z || keyval >= Gdk.KEY_A && keyval <= Gdk.KEY_Z || keyval >= Gdk.KEY_0 && keyval <= Gdk.KEY_9 || keyval >= Gdk.KEY_kana_fullstop && keyval <= Gdk.KEY_semivoicedsound || keyval >= Gdk.KEY_Arabic_comma && keyval <= Gdk.KEY_Arabic_sukun || keyval >= Gdk.KEY_Serbian_dje && keyval <= Gdk.KEY_Cyrillic_HARDSIGN || keyval >= Gdk.KEY_Greek_ALPHAaccent && keyval <= Gdk.KEY_Greek_omega || keyval >= Gdk.KEY_hebrew_doublelowline && keyval <= Gdk.KEY_hebrew_taf || keyval >= Gdk.KEY_Thai_kokai && keyval <= Gdk.KEY_Thai_lekkao || keyval >= Gdk.KEY_Hangul_Kiyeog && keyval <= Gdk.KEY_Hangul_J_YeorinHieuh || keyval === Gdk.KEY_space && mask === 0 || this.keyvalIsForbidden(keyval)));
|
|
}
|
|
isValidAccel(mask, keyval) {
|
|
return Gtk.accelerator_valid(keyval, mask) || keyval === Gdk.KEY_Tab && mask !== 0;
|
|
}
|
|
}, GObject.registerClass(
|
|
{
|
|
Properties: {
|
|
shortcut: GObject.ParamSpec.string(
|
|
"shortcut",
|
|
"shortcut",
|
|
"The shortcut",
|
|
GObject.ParamFlags.READWRITE,
|
|
""
|
|
)
|
|
},
|
|
Signals: {
|
|
changed: { param_types: [GObject.TYPE_STRING] }
|
|
}
|
|
},
|
|
_a
|
|
), _a);
|
|
export {
|
|
TilingShellExtensionPreferences as default
|
|
};
|