2025-05-09 09:48:24 +02:00

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
};