Привет всем. помогите разобраться с кастомизацией гнома.
Пробую менять ui в данном случае панели. Хочу сделать как у blure my shell чтобы слегка блюр срабатывал. Но не получается, то блюрится весь тектс то вообще ничего не происходит.
Вот мой код ~/.local/share/myshell-overlay/ui/main.js
import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GioUnix from 'gi://GioUnix';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as AccessDialog from './accessDialog.js';
import * as AudioDeviceSelection from './audioDeviceSelection.js';
import * as BreakManager from '../misc/breakManager.js';
import * as Config from '../misc/config.js';
import * as Components from './components.js';
import * as CtrlAltTab from './ctrlAltTab.js';
import * as EndSessionDialog from './endSessionDialog.js';
import * as ExtensionSystem from './extensionSystem.js';
import * as ExtensionDownloader from './extensionDownloader.js';
import * as InputMethod from '../misc/inputMethod.js';
import * as Introspect from '../misc/introspect.js';
import * as Keyboard from './keyboard.js';
import * as MessageTray from './messageTray.js';
import * as ModalDialog from './modalDialog.js';
import * as OsdWindow from './osdWindow.js';
import * as OsdMonitorLabeler from './osdMonitorLabeler.js';
import * as Overview from './overview.js';
import * as PadOsd from './padOsd.js';
import * as Panel from './panel.js';
import * as RunDialog from './runDialog.js';
import * as WelcomeDialog from './welcomeDialog.js';
import * as Layout from './layout.js';
import * as LoginManager from '../misc/loginManager.js';
import * as LookingGlass from './lookingGlass.js';
import * as NotificationDaemon from './notificationDaemon.js';
import * as WindowAttentionHandler from './windowAttentionHandler.js';
import * as Screenshot from './screenshot.js';
import * as ScreenShield from './screenShield.js';
import * as SessionMode from './sessionMode.js';
import * as ShellDBus from './shellDBus.js';
import * as ShellMountOperation from './shellMountOperation.js';
import * as TimeLimitsManager from '../misc/timeLimitsManager.js';
import * as WindowManager from './windowManager.js';
import * as Magnifier from './magnifier.js';
import * as XdndHandler from './xdndHandler.js';
import * as KbdA11yDialog from './kbdA11yDialog.js';
import * as LocatePointer from './locatePointer.js';
import * as PointerA11yTimeout from './pointerA11yTimeout.js';
import {formatError} from '../misc/errorUtils.js';
import * as ParentalControlsManager from '../misc/parentalControlsManager.js';
import * as Util from '../misc/util.js';
const WELCOME_DIALOG_LAST_SHOWN_VERSION = 'welcome-dialog-last-shown-version';
// Make sure to mention the point release, otherwise it will show every time
// until this version is current
const WELCOME_DIALOG_LAST_TOUR_CHANGE = '40.beta';
const LOG_DOMAIN = 'GNOME Shell';
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';
export let componentManager = null;
export let extensionManager = null;
export let panel = null;
export let overview = null;
export let runDialog = null;
export let lookingGlass = null;
export let welcomeDialog = null;
export let wm = null;
export let messageTray = null;
export let screenShield = null;
export let notificationDaemon = null;
export let windowAttentionHandler = null;
export let ctrlAltTabManager = null;
export let padOsdService = null;
export let osdWindowManager = null;
export let osdMonitorLabeler = null;
export let sessionMode = null;
export let screenshotUI = null;
export let shellAccessDialogDBusService = null;
export let shellAudioSelectionDBusService = null;
export let shellDBusService = null;
export let shellMountOpDBusService = null;
export let screenSaverDBus = null;
export let modalCount = 0;
export let actionMode = Shell.ActionMode.NONE;
export let modalActorFocusStack = [];
export let uiGroup = null;
export let magnifier = null;
export let xdndHandler = null;
export let keyboard = null;
export let layoutManager = null;
export let kbdA11yDialog = null;
export let inputMethod = null;
export let introspectService = null;
export let locatePointer = null;
export let endSessionDialog = null;
export let breakManager = null;
export let screenTimeDBus = null;
export let breakManagerDispatcher = null;
export let timeLimitsManager = null;
export let timeLimitsDispatcher = null;
let _startDate;
let _defaultCssStylesheet = null;
let _cssStylesheet = null;
let _themeResource = null;
let _oskResource = null;
let _iconResource = null;
let _workspacesAdjustment = null;
let _workspaceAdjustmentRegistry = null;
Gio._promisify(Gio.File.prototype, 'delete_async');
Gio._promisify(Gio.File.prototype, 'touch_async');
let _remoteAccessInhibited = false;
function _sessionUpdated() {
if (sessionMode.isPrimary)
_loadDefaultStylesheet();
wm.allowKeybinding('overlay-key',
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW);
wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL);
wm.setCustomKeybindingHandler('panel-run-dialog',
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
sessionMode.hasRunDialog ? openRunDialog : null);
if (!sessionMode.hasRunDialog) {
if (runDialog)
runDialog.close();
if (lookingGlass)
lookingGlass.close();
if (welcomeDialog)
welcomeDialog.close();
}
let remoteAccessController = global.backend.get_remote_access_controller();
if (remoteAccessController && !global.backend.is_headless()) {
if (sessionMode.allowScreencast && _remoteAccessInhibited) {
remoteAccessController.uninhibit_remote_access();
_remoteAccessInhibited = false;
} else if (!sessionMode.allowScreencast && !_remoteAccessInhibited) {
remoteAccessController.inhibit_remote_access();
_remoteAccessInhibited = true;
}
}
}
/** @returns {void} */
export async function start() {
globalThis.log = console.log;
globalThis.logError = function (err, msg) {
const args = [formatError(err)];
try {
// toString() can throw
if (msg)
args.unshift(`${msg}:`);
} catch {
}
console.error(...args);
};
// Chain up async errors reported from C
global.connect('notify-error', (global, msg, detail) => {
notifyError(msg, detail);
});
let currentDesktop = GLib.getenv('XDG_CURRENT_DESKTOP');
if (!currentDesktop || !currentDesktop.split(':').includes('GNOME'))
GioUnix.DesktopAppInfo.set_desktop_env('GNOME');
sessionMode = new SessionMode.SessionMode();
sessionMode.connect('updated', _sessionUpdated);
St.Settings.get().connect('notify::high-contrast', _loadDefaultStylesheet);
St.Settings.get().connect('notify::color-scheme', _loadDefaultStylesheet);
// Initialize ParentalControlsManager before the UI
ParentalControlsManager.getDefault();
await _initializeUI();
// --- MyShell: background blur под панелью (без размытия текста на панели) ---
log('MyShell overlay: main.js applied');
try {
const Shell = (await import('gi://Shell')).default;
const St = (await import('gi://St')).default;
const Clutter = (await import('gi://Clutter')).default;
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
try {
// panel в 48 — сам виджет; где-то это panel.actor, страхуемся:
const p = (panel && (panel.actor ?? panel)) || null;
if (!p) {
log('MyShell overlay: panel actor not ready');
return GLib.SOURCE_REMOVE;
}
// не плодим дубликаты
if (p.get_child_by_name?.('panel-bg-blur-container'))
return GLib.SOURCE_REMOVE;
// 1) Контейнер клонов (будем размазывать его, а не панель)
const blurContainer = new St.Widget({
name: 'panel-bg-blur-container',
reactive: false, x_expand: true, y_expand: true,
});
// клип по границам панели
blurContainer.set_clip_to_allocation?.(true);
// Привязываем к размерам/позиции панели
blurContainer.add_constraint(new Clutter.BindConstraint({
source: p, coordinate: Clutter.BindCoordinate.SIZE,
}));
blurContainer.add_constraint(new Clutter.BindConstraint({
source: p, coordinate: Clutter.BindCoordinate.POSITION,
}));
// 2) Клонируем фон и окна (это то, что "под" панелью)
// ВАЖНО: клоним ИМЕННО группы, а не всю stage — без рекурсий и «зеркал»
const bgClone = new Clutter.Clone({source: global.background_group});
const windowClone = new Clutter.Clone({source: global.window_group});
bgClone.reactive = false;
windowClone.reactive = false;
// Растягиваем клоны на всю сцену; клип ограничит до панели
bgClone.set_size(global.stage.width, global.stage.height);
windowClone.set_size(global.stage.width, global.stage.height);
blurContainer.add_child(bgClone);
blurContainer.add_child(windowClone);
// 3) Эффект blur на контейнер с клонами
let blur = null;
try {
blur = new Shell.BlurEffect({radius: 16});
} // 12–18 = лёгкий
catch (_) {
try {
blur = new Shell.BlurEffect({sigma: 12});
} catch (_) {
}
}
if (blur)
blurContainer.add_effect_with_name('myshell-panel-bg-blur', blur);
else
log('MyShell overlay: BlurEffect ctor failed');
// 4) Тонкий тинт поверх размытия для контраста текста
const tintOverlay = new St.Widget({
name: 'panel-tint-overlay',
reactive: false, x_expand: true, y_expand: true,
});
tintOverlay.set_clip_to_allocation?.(true);
tintOverlay.set_style(
'background: linear-gradient(rgba(9,17,27,0.22), rgba(9,17,27,0.22));'
);
tintOverlay.add_constraint(new Clutter.BindConstraint({
source: p, coordinate: Clutter.BindCoordinate.SIZE,
}));
tintOverlay.add_constraint(new Clutter.BindConstraint({
source: p, coordinate: Clutter.BindCoordinate.POSITION,
}));
// 5) Порядок детей панели: [0] blurContainer, [1] tint, дальше штатные кнопки
p.insert_child_at_index(blurContainer, 0);
p.insert_child_at_index(tintOverlay, 1);
// уборка на destroy
p.connect?.('destroy', () => {
tintOverlay.destroy?.();
blurContainer.destroy?.();
});
log('MyShell overlay: background blur added');
} catch (e) {
logError(e, 'MyShell overlay: setup failed');
}
return GLib.SOURCE_REMOVE;
});
} catch (e) {
logError(e, 'MyShell overlay: GI import failed');
}
// --- /MyShell ---
shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus();
shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus();
shellDBusService = new ShellDBus.GnomeShell();
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
const watchId = Gio.DBus.session.watch_name('org.gnome.Shell.Notifications',
Gio.BusNameWatcherFlags.AUTO_START,
bus => bus.unwatch_name(watchId),
bus => bus.unwatch_name(watchId));
_sessionUpdated();
}
/** @private */
async function _initializeUI() {
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
// also initialize ShellAppSystem first. ShellAppSystem
// needs to load all the .desktop files, and ShellWindowTracker
// will use those to associate with windows. Right now
// the Monitor doesn't listen for installed app changes
// and recalculate application associations, so to avoid
// races for now we initialize it here. It's better to
// be predictable anyways.
Shell.WindowTracker.get_default();
Shell.AppUsage.get_default();
reloadThemeResource();
_loadIcons();
_loadOskLayouts();
_loadDefaultStylesheet();
_loadWorkspacesAdjustment();
new AnimationsSettings();
// Setup the stage hierarchy early
layoutManager = new Layout.LayoutManager();
// Various parts of the codebase still refer to Main.uiGroup
// instead of using the layoutManager. This keeps that code
// working until it's updated.
uiGroup = layoutManager.uiGroup;
padOsdService = new PadOsd.PadOsdService();
xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
osdWindowManager = new OsdWindow.OsdWindowManager();
osdMonitorLabeler = new OsdMonitorLabeler.OsdMonitorLabeler();
overview = new Overview.Overview();
kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog();
wm = new WindowManager.WindowManager();
magnifier = new Magnifier.Magnifier();
locatePointer = new LocatePointer.LocatePointer();
if (LoginManager.canLock())
screenShield = new ScreenShield.ScreenShield();
inputMethod = new InputMethod.InputMethod();
global.stage.context.get_backend().set_input_method(inputMethod);
global.connect('shutdown',
() => global.stage.context.get_backend().set_input_method(null));
screenshotUI = new Screenshot.ScreenshotUI();
messageTray = new MessageTray.MessageTray();
panel = new Panel.Panel();
keyboard = new Keyboard.KeyboardManager();
notificationDaemon = new NotificationDaemon.NotificationDaemon();
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
componentManager = new Components.ComponentManager();
introspectService = new Introspect.IntrospectService();
// Set up the global default break reminder manager and its D-Bus interface
breakManager = new BreakManager.BreakManager();
timeLimitsManager = new TimeLimitsManager.TimeLimitsManager();
screenTimeDBus = new ShellDBus.ScreenTimeDBus(breakManager);
breakManagerDispatcher = new BreakManager.BreakDispatcher(breakManager);
timeLimitsDispatcher = new TimeLimitsManager.TimeLimitsDispatcher(timeLimitsManager);
global.connect('shutdown', () => {
// Block shutdown until the session history file has been written
const loop = new GLib.MainLoop(null, false);
const source = GLib.idle_source_new();
source.set_callback(() => {
timeLimitsManager.shutdown()
.catch(e => console.warn(`Failed to stop time limits manager: ${e.message}`))
.finally(() => loop.quit());
return GLib.SOURCE_REMOVE;
});
source.attach(loop.get_context());
loop.run();
});
layoutManager.init();
overview.init();
new PointerA11yTimeout.PointerA11yTimeout();
global.connect('locate-pointer', () => {
locatePointer.show();
});
global.display.connect('show-restart-message', (display, message) => {
showRestartMessage(message);
return true;
});
global.display.connect('restart', () => {
global.reexec_self();
return true;
});
global.display.connect('gl-video-memory-purged', loadTheme);
global.context.connect('notify::unsafe-mode', () => {
if (!global.context.unsafe_mode)
return; // we're safe
if (lookingGlass?.isOpen)
return; // assume user action
const source = MessageTray.getSystemSource();
const notification = new MessageTray.Notification({
source,
title: _('System was put in unsafe mode'),
body: _('Apps now have unrestricted access'),
isTransient: true,
});
notification.addAction(_('Undo'),
() => (global.context.unsafe_mode = false));
source.addNotification(notification);
});
// Provide the bus object for gnome-session to
// initiate logouts.
endSessionDialog = new EndSessionDialog.EndSessionDialog();
// We're ready for the session manager to move to the next phase
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Shell.util_sd_notify();
global.context.notify_ready();
return GLib.SOURCE_REMOVE;
});
_startDate = new Date();
ExtensionDownloader.init();
extensionManager = new ExtensionSystem.ExtensionManager();
extensionManager.init();
if (sessionMode.isGreeter && screenShield) {
layoutManager.connect('startup-prepared', () => {
screenShield.showDialog();
});
}
let Scripting;
let perfModule;
const {automationScript} = global;
if (automationScript) {
Scripting = await import('./scripting.js');
perfModule = await import(automationScript.get_uri());
if (perfModule.init)
perfModule.init();
}
layoutManager.connect('startup-complete', () => {
if (actionMode === Shell.ActionMode.NONE)
actionMode = Shell.ActionMode.NORMAL;
if (screenShield)
screenShield.lockIfWasLocked();
if (sessionMode.currentMode !== 'gdm' &&
sessionMode.currentMode !== 'initial-setup') {
GLib.log_structured(LOG_DOMAIN, GLib.LogLevelFlags.LEVEL_MESSAGE, {
'MESSAGE': `GNOME Shell started at ${_startDate}`,
'MESSAGE_ID': GNOMESHELL_STARTED_MESSAGE_ID,
});
}
if (!perfModule) {
let credentials = new Gio.Credentials();
if (credentials.get_unix_user() === 0) {
notify(
_('Logged in as a privileged user'),
_('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.'));
} else if (sessionMode.showWelcomeDialog) {
_handleShowWelcomeScreen();
}
}
if (sessionMode.currentMode !== 'gdm' &&
sessionMode.currentMode !== 'initial-setup')
_handleLockScreenWarning();
LoginManager.registerSessionWithGDM();
if (perfModule) {
let perfOutput = GLib.getenv('SHELL_PERF_OUTPUT');
Scripting.runPerfScript(perfModule, perfOutput);
}
});
}
function _handleShowWelcomeScreen() {
const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION);
if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) {
openWelcomeDialog();
global.settings.set_string(WELCOME_DIALOG_LAST_SHOWN_VERSION, Config.PACKAGE_VERSION);
}
}
async function _handleLockScreenWarning() {
const path = `${global.userdatadir}/lock-warning-shown`;
const file = Gio.File.new_for_path(path);
const hasLockScreen = screenShield !== null;
if (hasLockScreen) {
try {
await file.delete_async(0, null);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
logError(e);
}
} else {
try {
if (!await file.touch_async())
return;
} catch (e) {
logError(e);
}
notify(
_('Screen Lock disabled'),
_('Screen Locking requires the GNOME display manager'));
}
}
function _getStylesheet(name) {
let stylesheet;
stylesheet = Gio.File.new_for_uri(`resource:///org/gnome/shell/theme/${name}`);
if (stylesheet.query_exists(null))
return stylesheet;
let dataDirs = GLib.get_system_data_dirs();
for (let i = 0; i < dataDirs.length; i++) {
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'theme', name]);
stylesheet = Gio.file_new_for_path(path);
if (stylesheet.query_exists(null))
return stylesheet;
}
stylesheet = Gio.File.new_for_path(`${global.datadir}/theme/${name}`);
if (stylesheet.query_exists(null))
return stylesheet;
return null;
}
/** @returns {string} */
export function getStyleVariant() {
const {colorScheme} = St.Settings.get();
switch (sessionMode.colorScheme) {
case 'force-dark':
return 'dark';
case 'force-light':
return 'light';
case 'prefer-dark':
return colorScheme === St.SystemColorScheme.PREFER_LIGHT
? 'light' : 'dark';
case 'prefer-light':
return colorScheme === St.SystemColorScheme.PREFER_DARK
? 'dark' : 'light';
default:
return '';
}
}
function _getDefaultStylesheet() {
let stylesheet = null;
let name = sessionMode.stylesheetName;
// Look for a high-contrast variant first
if (St.Settings.get().high_contrast)
stylesheet = _getStylesheet(name.replace('.css', '-high-contrast.css'));
if (stylesheet === null)
stylesheet = _getStylesheet(name.replace('.css', `-${getStyleVariant()}.css`));
if (stylesheet == null)
stylesheet = _getStylesheet(name);
return stylesheet;
}
function _loadDefaultStylesheet() {
let stylesheet = _getDefaultStylesheet();
if (_defaultCssStylesheet && _defaultCssStylesheet.equal(stylesheet))
return;
_defaultCssStylesheet = stylesheet;
loadTheme();
}
class AdjustmentRegistry {
#count = 0;
#adjustments = new Map();
#registry = new FinalizationRegistry(key => {
this.#adjustments.delete(key);
});
register(adj) {
const key = this.#count++;
this.#adjustments.set(key, new WeakRef(adj));
this.#registry.register(adj, key);
}
forEach(callback) {
this.#adjustments.forEach((ref, key) => {
const adj = ref.deref();
if (adj)
callback(adj);
else
this.#adjustments.delete(key);
});
}
}
function _loadWorkspacesAdjustment() {
const {workspaceManager} = global;
const activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
_workspacesAdjustment = new St.Adjustment({
value: activeWorkspaceIndex,
lower: 0,
page_increment: 1,
page_size: 1,
step_increment: 0,
upper: workspaceManager.n_workspaces,
});
workspaceManager.bind_property('n-workspaces',
_workspacesAdjustment, 'upper',
GObject.BindingFlags.SYNC_CREATE);
_workspacesAdjustment.connect('notify::upper', () => {
const newActiveIndex = workspaceManager.get_active_workspace_index();
// A workspace might have been inserted or removed before the active
// one, causing the adjustment to go out of sync, so update the value
_workspaceAdjustmentRegistry.forEach(c => c.remove_transition('value'));
_workspacesAdjustment.remove_transition('value');
_workspacesAdjustment.value = newActiveIndex;
});
_workspaceAdjustmentRegistry = new AdjustmentRegistry();
}
/**
* Creates an adjustment that has its lower, upper, and value
* properties set for the number of available workspaces. Consumers
* of the returned adjustment must only change the 'value' property,
* and only that.
*
* @param {Clutter.Actor} actor
*
* @returns {St.Adjustment} - an adjustment representing the
* current workspaces layout
*/
export function createWorkspacesAdjustment(actor) {
const adjustment = new St.Adjustment({actor});
const properties = [
['lower', GObject.BindingFlags.SYNC_CREATE],
['page-increment', GObject.BindingFlags.SYNC_CREATE],
['page-size', GObject.BindingFlags.SYNC_CREATE],
['step-increment', GObject.BindingFlags.SYNC_CREATE],
['upper', GObject.BindingFlags.SYNC_CREATE],
['value', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL],
];
for (const [propName, flags] of properties)
_workspacesAdjustment.bind_property(propName, adjustment, propName, flags);
_workspaceAdjustmentRegistry.register(adjustment);
return adjustment;
}
/**
* Get the theme CSS file that the shell will load
*
* @returns {?Gio.File}: A #GFile that contains the theme CSS,
* null if using the default
*/
export function getThemeStylesheet() {
return _cssStylesheet;
}
/**
* Set the theme CSS file that the shell will load
*
* @param {string=} cssStylesheet - A file path that contains the theme CSS,
* set it to null to use the default
*/
export function setThemeStylesheet(cssStylesheet) {
_cssStylesheet = cssStylesheet ? Gio.File.new_for_path(cssStylesheet) : null;
}
export function reloadThemeResource() {
if (_themeResource)
_themeResource._unregister();
_themeResource = Gio.Resource.load(
`${global.datadir}/${sessionMode.themeResourceName}`);
_themeResource._register();
}
/** @private */
function _loadIcons() {
_iconResource = Gio.Resource.load(`${global.datadir}/gnome-shell-icons.gresource`);
_iconResource._register();
}
function _loadOskLayouts() {
_oskResource = Gio.Resource.load(`${global.datadir}/gnome-shell-osk-layouts.gresource`);
_oskResource._register();
}
/**
* loadTheme:
*
* Reloads the theme CSS file
*/
export function loadTheme() {
let themeContext = St.ThemeContext.get_for_stage(global.stage);
let previousTheme = themeContext.get_theme();
let theme = new St.Theme({
application_stylesheet: _cssStylesheet,
default_stylesheet: _defaultCssStylesheet,
});
if (theme.default_stylesheet == null)
throw new Error(`No valid stylesheet found for '${sessionMode.stylesheetName}'`);
if (previousTheme) {
let customStylesheets = previousTheme.get_custom_stylesheets();
for (let i = 0; i < customStylesheets.length; i++)
theme.load_stylesheet(customStylesheets[i]);
}
themeContext.set_theme(theme);
}
/**
* @param {string} msg A message
* @param {string=} details Additional information
*/
export function notify(msg, details = null) {
const source = MessageTray.getSystemSource();
const notification = new MessageTray.Notification({
source,
title: msg,
body: details,
isTransient: true,
});
source.addNotification(notification);
}
/**
* See shell_global_notify_problem().
*
* @param {string} msg - An error message
* @param {string} details - Additional information
*/
export function notifyError(msg, details) {
// Also print to stderr so it's logged somewhere
if (details)
console.warn(`error: ${msg}: ${details}`);
else
console.warn(`error: ${msg}`);
notify(msg, details);
}
/**
* @private
* @param {Clutter.Grab} grab - grab
*/
function _findModal(grab) {
for (let i = 0; i < modalActorFocusStack.length; i++) {
if (modalActorFocusStack[i].grab === grab)
return i;
}
return -1;
}
/**
* Ensure we are in a mode where all keyboard and mouse input goes to
* the stage, and focus @actor. Multiple calls to this function act in
* a stacking fashion; the effect will be undone when an equal number
* of popModal() invocations have been made.
*
* Next, record the current Clutter keyboard focus on a stack. If the
* modal stack returns to this actor, reset the focus to the actor
* which was focused at the time pushModal() was invoked.
*
* `params` may be used to provide the following parameters:
* - actionMode: used to set the current Shell.ActionMode to filter
* global keybindings; the default of NONE will filter
* out all keybindings
*
* @param {Clutter.Actor} actor - actor which will be given keyboard focus
* @param {object=} params - optional parameters
* @returns {Clutter.Grab} - the grab handle created
*/
export function pushModal(actor, params = {}) {
const {actionMode: newActionMode} = {
actionMode: Shell.ActionMode.NONE,
...params,
};
let grab = global.stage.grab(actor);
if (modalCount === 0)
global.compositor.disable_unredirect();
modalCount += 1;
let actorDestroyId = actor.connect('destroy', () => {
let index = _findModal(grab);
if (index >= 0)
popModal(grab);
});
let prevFocus = global.stage.get_key_focus();
let prevFocusDestroyId;
if (prevFocus != null) {
prevFocusDestroyId = prevFocus.connect('destroy', () => {
const index = modalActorFocusStack.findIndex(
record => record.prevFocus === prevFocus);
if (index >= 0)
modalActorFocusStack[index].prevFocus = null;
});
}
modalActorFocusStack.push({
actor,
grab,
destroyId: actorDestroyId,
prevFocus,
prevFocusDestroyId,
actionMode,
});
actionMode = newActionMode;
const newFocus = actor === global.stage ? null : actor;
global.stage.set_key_focus(newFocus);
return grab;
}
/**
* Reverse the effect of pushModal(). If this invocation is undoing
* the topmost invocation, then the focus will be restored to the
* previous focus at the time when pushModal() was invoked.
*
* @param {Clutter.Grab} grab - the grab given by pushModal()
*/
export function popModal(grab) {
let focusIndex = _findModal(grab);
if (focusIndex < 0) {
global.stage.set_key_focus(null);
actionMode = Shell.ActionMode.NORMAL;
throw new Error('incorrect pop');
}
modalCount -= 1;
let record = modalActorFocusStack[focusIndex];
record.actor.disconnect(record.destroyId);
record.grab.dismiss();
if (focusIndex === modalActorFocusStack.length - 1) {
if (record.prevFocus)
record.prevFocus.disconnect(record.prevFocusDestroyId);
actionMode = record.actionMode;
global.stage.set_key_focus(record.prevFocus);
} else {
// If we have:
// global.stage.set_focus(a);
// Main.pushModal(b);
// Main.pushModal(c);
// Main.pushModal(d);
//
// then we have the stack:
// [{ prevFocus: a, actor: b },
// { prevFocus: b, actor: c },
// { prevFocus: c, actor: d }]
//
// When actor c is destroyed/popped, if we only simply remove the
// record, then the focus stack will be [a, c], rather than the correct
// [a, b]. Shift the focus stack up before removing the record to ensure
// that we get the correct result.
let t = modalActorFocusStack[modalActorFocusStack.length - 1];
if (t.prevFocus)
t.prevFocus.disconnect(t.prevFocusDestroyId);
// Remove from the middle, shift the focus chain up
for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus;
modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId;
modalActorFocusStack[i].actionMode = modalActorFocusStack[i - 1].actionMode;
}
}
modalActorFocusStack.splice(focusIndex, 1);
if (modalCount > 0)
return;
layoutManager.modalEnded();
global.compositor.enable_unredirect();
actionMode = Shell.ActionMode.NORMAL;
}
/**
* Creates the looking glass panel
*
* @returns {LookingGlass.LookingGlass}
*/
export function createLookingGlass() {
if (lookingGlass == null)
lookingGlass = new LookingGlass.LookingGlass();
return lookingGlass;
}
/**
* Opens the run dialog
*/
export function openRunDialog() {
if (runDialog == null)
runDialog = new RunDialog.RunDialog();
runDialog.open();
}
export function openWelcomeDialog() {
if (welcomeDialog === null)
welcomeDialog = new WelcomeDialog.WelcomeDialog();
welcomeDialog.open();
}
/**
* activateWindow:
*
* @param {Meta.Window} window the window to activate
* @param {number=} time current event time
* @param {number=} workspaceNum window's workspace number
*
* Activates @window, switching to its workspace first if necessary,
* and switching out of the overview if it's currently active
*/
export function activateWindow(window, time, workspaceNum) {
let workspaceManager = global.workspace_manager;
let activeWorkspaceNum = workspaceManager.get_active_workspace_index();
let windowWorkspaceNum = workspaceNum !== undefined ? workspaceNum : window.get_workspace().index();
if (!time)
time = global.get_current_time();
if (windowWorkspaceNum !== activeWorkspaceNum) {
let workspace = workspaceManager.get_workspace_by_index(windowWorkspaceNum);
workspace.activate_with_focus(window, time);
} else {
window.activate(time);
}
overview.hide();
panel.closeCalendar();
}
/**
* Move @window to the specified monitor and workspace.
*
* @param {Meta.Window} window - the window to move
* @param {number} monitorIndex - the requested monitor
* @param {number} workspaceIndex - the requested workspace
* @param {bool} append - create workspace if it doesn't exist
*/
export function moveWindowToMonitorAndWorkspace(window, monitorIndex, workspaceIndex, append = false) {
// We need to move the window before changing the workspace, because
// the move itself could cause a workspace change if the window enters
// the primary monitor
if (window.get_monitor() !== monitorIndex) {
// Wait for the monitor change to take effect
const id = global.display.connect('window-entered-monitor',
(dsp, num, w) => {
if (w !== window)
return;
window.change_workspace_by_index(workspaceIndex, append);
global.display.disconnect(id);
});
window.move_to_monitor(monitorIndex);
} else {
window.change_workspace_by_index(workspaceIndex, append);
}
}
// TODO - replace this timeout with some system to guess when the user might
// be e.g. just reading the screen and not likely to interact.
const DEFERRED_TIMEOUT_SECONDS = 20;
let _deferredWorkData = {};
// Work scheduled for some point in the future
let _deferredWorkQueue = [];
// Work we need to process before the next redraw
let _beforeRedrawQueue = [];
// Counter to assign work ids
let _deferredWorkSequence = 0;
let _deferredTimeoutId = 0;
function _runDeferredWork(workId) {
if (!_deferredWorkData[workId])
return;
let index = _deferredWorkQueue.indexOf(workId);
if (index < 0)
return;
_deferredWorkQueue.splice(index, 1);
_deferredWorkData[workId].callback();
if (_deferredWorkQueue.length === 0 && _deferredTimeoutId > 0) {
GLib.source_remove(_deferredTimeoutId);
_deferredTimeoutId = 0;
}
}
function _runAllDeferredWork() {
while (_deferredWorkQueue.length > 0)
_runDeferredWork(_deferredWorkQueue[0]);
}
function _runBeforeRedrawQueue() {
for (let i = 0; i < _beforeRedrawQueue.length; i++) {
let workId = _beforeRedrawQueue[i];
_runDeferredWork(workId);
}
_beforeRedrawQueue = [];
}
function _queueBeforeRedraw(workId) {
_beforeRedrawQueue.push(workId);
if (_beforeRedrawQueue.length === 1) {
const laters = global.compositor.get_laters();
laters.add(Meta.LaterType.BEFORE_REDRAW, () => {
_runBeforeRedrawQueue();
return false;
});
}
}
/**
* This function sets up a callback to be invoked when either the
* given actor is mapped, or after some period of time when the machine
* is idle. This is useful if your actor isn't always visible on the
* screen (for example, all actors in the overview), and you don't want
* to consume resources updating if the actor isn't actually going to be
* displaying to the user.
*
* Note that queueDeferredWork is called by default immediately on
* initialization as well, under the assumption that new actors
* will need it.
*
* @param {Clutter.Actor} actor - an actor
* @param {callback} callback - Function to invoke to perform work
*
* @returns {string} - A string work identifier
*/
export function initializeDeferredWork(actor, callback) {
// Turn into a string so we can use as an object property
let workId = `${++_deferredWorkSequence}`;
_deferredWorkData[workId] = {
actor,
callback,
};
actor.connect('notify::mapped', () => {
if (!(actor.mapped && _deferredWorkQueue.includes(workId)))
return;
_queueBeforeRedraw(workId);
});
actor.connect('destroy', () => {
let index = _deferredWorkQueue.indexOf(workId);
if (index >= 0)
_deferredWorkQueue.splice(index, 1);
delete _deferredWorkData[workId];
});
queueDeferredWork(workId);
return workId;
}
/**
* queueDeferredWork:
*
* @param {string} workId work identifier
*
* Ensure that the work identified by @workId will be
* run on map or timeout. You should call this function
* for example when data being displayed by the actor has
* changed.
*/
export function queueDeferredWork(workId) {
let data = _deferredWorkData[workId];
if (!data) {
let message = `Invalid work id ${workId}`;
logError(new Error(message), message);
return;
}
if (!_deferredWorkQueue.includes(workId))
_deferredWorkQueue.push(workId);
if (data.actor.mapped) {
_queueBeforeRedraw(workId);
} else if (_deferredTimeoutId === 0) {
_deferredTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, DEFERRED_TIMEOUT_SECONDS, () => {
_runAllDeferredWork();
_deferredTimeoutId = 0;
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork');
}
}
const RestartMessage = GObject.registerClass(
class RestartMessage extends ModalDialog.ModalDialog {
_init(message) {
super._init({
shellReactive: true,
styleClass: 'restart-message',
shouldFadeIn: false,
destroyOnClose: true,
});
let label = new St.Label({
text: message,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.contentLayout.add_child(label);
this.buttonLayout.hide();
}
});
function showRestartMessage(message) {
let restartMessage = new RestartMessage(message);
restartMessage.open();
}
class AnimationsSettings {
constructor() {
this._animationsEnabled = true;
this._handles = new Set();
global.connect('notify::force-animations',
this._syncAnimationsEnabled.bind(this));
this._syncAnimationsEnabled();
const backend = global.backend;
const remoteAccessController = backend.get_remote_access_controller();
if (remoteAccessController) {
remoteAccessController.connect('new-handle',
(_, handle) => this._onNewRemoteAccessHandle(handle));
}
}
_shouldEnableAnimations() {
if (this._handles.size > 0)
return false;
if (global.force_animations)
return true;
const backend = global.backend;
if (!backend.is_rendering_hardware_accelerated())
return false;
if (Shell.util_has_x11_display_extension(
global.display, 'VNC-EXTENSION'))
return false;
return true;
}
_syncAnimationsEnabled() {
const shouldEnableAnimations = this._shouldEnableAnimations();
if (this._animationsEnabled === shouldEnableAnimations)
return;
this._animationsEnabled = shouldEnableAnimations;
const settings = St.Settings.get();
if (shouldEnableAnimations)
settings.uninhibit_animations();
else
settings.inhibit_animations();
}
_onRemoteAccessHandleStopped(handle) {
this._handles.delete(handle);
this._syncAnimationsEnabled();
}
_onNewRemoteAccessHandle(handle) {
if (!handle.get_disable_animations())
return;
this._handles.add(handle);
this._syncAnimationsEnabled();
handle.connect('stopped', this._onRemoteAccessHandleStopped.bind(this));
}
}
Вот стили моей темы(Не знаю нужно ли оно здесь, навсякий)
/******************** PANEL ********************/
/* панель без собственного фона — иначе blur не будет виден */
#panel { background: transparent !important; border: none !important; box-shadow: none !important; }
#panel .panel-button { color: #fff; }
/************* Quick Settings — контейнер *************/
.quick-settings {
width: 450px;
padding: 24px;
}
/* Поповеры (общие меню — чтобы не были серыми) */
.popup-menu .popup-menu-content {
border-radius: 24px;
background: linear-gradient(rgba(145, 201, 255, 0.10), rgba(145, 201, 255, 0.10)),
rgba(9, 17, 27, 0.32);
border: 1px solid rgba(145, 201, 255, 0.7);
}
/* Тайлы */
.quick-toggle, .quick-menu-toggle {
width: 100px;
height: 40px;
cursor: pointer;
background: transparent;
border: 1px solid #91C9FF;
outline: none;
}
.quick-toggle:hover, .quick-menu-toggle:hover {
border-color: #4F95DA;
}
.quick-toggle:checked, .quick-toggle:active,
.quick-menu-toggle:checked, .quick-menu-toggle:active {
background: #4F95DA;
border-color: #4F95DA;
}
.quick-toggle .segment, .quick-menu-toggle .segment {
border-left: 1px solid #4F95DA;
}
.quick-settings .title, .quick-settings .subtitle,
.quick-settings .icon-button, .quick-settings .quick-toggle {
color: #fff;
}
.quick-slider .slider, .slider {
-barlevel-height: 8px;
-barlevel-border-radius: 9999px;
-barlevel-background-color: rgba(255, 255, 255, 0.16);
-barlevel-border-color: transparent;
-barlevel-active-background-color: #4F95DA;
-barlevel-active-border-color: transparent;
-slider-handle-radius: 9px;
-slider-handle-border-width: 1px;
-slider-handle-border-color: rgba(255, 255, 255, 0.75);
-slider-handle-background-color: #fff;
}
.quick-toggle-menu {
background-color: rgba(18, 18, 22, 0.95);
border: 1px solid #4F95DA;
border-radius: 16px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.55);
}
.quick-settings .separator {
background-color: #fff;
}
.quick-settings .panel-button, .quick-settings .button {
color: #fff;
}
/************* Date Menu (календарь/часы/погода) — общий контейнер *************/
.datemenu, #calendarArea, .datemenu-calendar {
background: linear-gradient(rgba(145, 201, 255, 0.10), rgba(145, 201, 255, 0.10)),
rgba(9, 17, 27, 0.32);
border-radius: 24px;
padding: 12px;
}
.datemenu .separator, .datemenu .vseparator {
background: none;
}
.calendar, .datemenu-calendar .calendar {
padding: 8px;
background: transparent;
border: none;
}
/* Шапка месяца */
.calendar .calendar-month-header, .calendar .month-header, .calendar .header {
padding: 6px 8px;
margin-bottom: 6px;
border-radius: 12px;
}
.calendar .calendar-month-label {
font-weight: 700;
color: #fff;
}
.calendar .pager-button,
.calendar .calendar-change-month-back,
.calendar .calendar-change-month-forward {
border-radius: 50%;
background: transparent;
border: 1px solid #91C9FF;
}
.calendar .pager-button:hover,
.calendar .calendar-change-month-back:hover,
.calendar .calendar-change-month-forward:hover {
border-color: #4F95DA;
}
/* Дни недели */
.calendar .calendar-day-heading, .calendar .day-header {
color: rgba(103, 163, 151, 0.75);
padding: 4px 0;
}
/* Тайлы дней — без «прыжка» */
.calendar .day, .calendar .calendar-day, .calendar .day-base, .calendar .calendar-day-button {
border: 1px solid transparent;
border-radius: 24px;
padding: 3px;
background: transparent;
}
.calendar .day:hover, .calendar .calendar-day:hover,
.calendar .day-base:hover, .calendar .calendar-day-button:hover {
border-color: #4F95DA;
}
/* Выбранный день */
.calendar .day:checked, .calendar .day:selected, .calendar .day:active,
.calendar .calendar-day:checked, .calendar .calendar-day:selected, .calendar .calendar-day:active,
.calendar .day-base:checked, .calendar .day-base:active,
.calendar .calendar-day-button:checked, .calendar .calendar-day-button:selected, .calendar .selected {
border-color: #4F95DA;
color: #fff;
}
/* Сегодня */
.calendar .today, .calendar .calendar-today,
.calendar .day.today, .calendar .calendar-day.today, .calendar .calendar-day-button.today {
background: rgba(79, 149, 218, 0.18);
}
/* Дни другого месяца */
.calendar .other-month-day, .calendar .outside-month, .calendar .other-month {
opacity: .45;
}
/* Карточки Today / World Clocks / Events / Weather */
.datemenu .datemenu-today-button,
.datemenu .world-clocks-button,
.datemenu .events-button,
.datemenu .weather-button,
.datemenu .events-section, .datemenu .events-list, .datemenu .events-box, .datemenu .no-events-box,
.datemenu .clocks-section, .datemenu .world-clocks, .datemenu .world-clocks-section, .datemenu .world-clocks-grid,
.datemenu .weather-section, .datemenu .weather {
background: rgba(40, 44, 52, 0.75);
border: 1px solid #91C9FF;
border-radius: 16px;
padding: 10px;
margin-top: 10px;
color: #fff;
}
.datemenu .world-clocks-button .world-clocks-header,
.datemenu .events-button .events-title,
.datemenu .weather-button .weather-box .weather-header,
.datemenu .section-title, .datemenu .events-section-title, .datemenu .world-clocks-header, .datemenu .section-header {
color: #cfe7ff;
font-weight: 600;
}
/************ NOTIFICATIONS / MESSAGE LIST ************/
/* карточка уведомления (в центре уведомлений и баннер) */
.notification-banner, .message {
background: linear-gradient(rgba(145, 201, 255, 0.10), rgba(145, 201, 255, 0.10)),
rgba(9, 17, 27, 0.88) !important;
border: 1px solid #91C9FF !important;
border-radius: 16px !important;
color: #fff !important;
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
}
.message .message-header,
.message .message-content {
background: transparent !important;
color: #fff !important;
}
/* кнопки в карточке уведомления (collapse/close) */
.message .message-close-button,
.message-notification-group .message-collapse-button {
background: transparent !important;
border: 1px solid #91C9FF !important;
border-radius: 9999px !important;
}
.message .message-close-button:hover,
.message-notification-group .message-collapse-button:hover {
border-color: #4F95DA !important;
}
/* нижняя панель: Do Not Disturb + Clear */
.message-list-controls {
background: transparent !important;
border-top: 1px solid rgba(145, 201, 255, 0.25);
padding-top: 8px;
}
.message-list-controls .button {
background: rgba(9, 17, 27, 0.45) !important;
border: 1px solid #91C9FF !important;
color: #fff !important;
border-radius: 12px !important;
padding: 6px 14px;
}
.message-list-controls .button:hover {
background: rgba(79, 149, 218, 0.18) !important;
border-color: #4F95DA !important;
}
/* сам тумблер Do Not Disturb */
.toggle-switch, .toggle-switch:checked {
background-image: none !important; /* гасим дефолтные svg */
border-radius: 9999px;
min-width: 42px;
min-height: 24px;
border: 1px solid #91C9FF !important;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.06);
}
.toggle-switch {
background: rgba(9, 17, 27, 0.55) !important;
}
.toggle-switch:checked {
background: #4F95DA !important;
border-color: #4F95DA !important;
}
/************ DATE MENU — карточки и кнопки ************/
.datemenu .datemenu-today-button,
.datemenu .world-clocks-button,
.datemenu .events-button,
.datemenu .weather-button,
.datemenu .events-section, .datemenu .events-list, .datemenu .events-box, .datemenu .no-events-box,
.datemenu .clocks-section, .datemenu .world-clocks, .datemenu .world-clocks-section, .datemenu .world-clocks-grid,
.datemenu .weather-section, .datemenu .weather,
.datemenu .button { /* общий стиль для одиночных кнопок вроде “Select Weather Location…” */
background: rgba(40, 44, 52, 0.75) !important;
border: 1px solid #91C9FF !important;
border-radius: 16px !important;
color: #fff !important;
}
/* заголовки секций */
.datemenu .world-clocks-button .world-clocks-header,
.datemenu .events-button .events-title,
.datemenu .weather-button .weather-box .weather-header,
.datemenu .section-title, .datemenu .events-section-title,
.datemenu .world-clocks-header, .datemenu .section-header {
color: #cfe7ff !important;
font-weight: 600 !important;
}
/************ CALENDAR — дни/ховер/selected (без “прыжка”) ************/
.calendar .day, .calendar .calendar-day, .calendar .day-base, .calendar .calendar-day-button {
border: 1px solid transparent; /* базовый бордер, чтобы не прыгало */
border-radius: 24px;
padding: 3px;
background: transparent;
}
.calendar .day:hover, .calendar .calendar-day:hover,
.calendar .day-base:hover, .calendar .calendar-day-button:hover {
border-color: #4F95DA;
}
.calendar .day:checked, .calendar .day:selected, .calendar .day:active,
.calendar .calendar-day:checked, .calendar .calendar-day:selected, .calendar .calendar-day:active,
.calendar .day-base:checked, .calendar .day-base:active,
.calendar .calendar-day-button:checked, .calendar .calendar-day-button:selected, .calendar .selected {
border-color: #4F95DA !important;
color: #fff !important;
}
.calendar .calendar-day.calendar-today,
.calendar .day.today, .calendar .calendar-day.today, .calendar .calendar-day-button.today {
background: rgba(79, 149, 218, 0.18) !important;
}
/************ POPUP-MENU — общая косметика ************/
.popup-menu .popup-menu-item {
color: #fff;
}
.popup-menu .popup-menu-item:hover {
background: rgba(79, 149, 218, 0.12);
}
/*controls manager*/
.controls-manager {
background: transparent;
}
Пробую уже и смотреть какие-то доки? По типу таких https://gjs.guide/extensions/
И gpt спраштваю, не помогает)))
Может кто знрает как это реализовать, хотя бы верхнюю панель