mirror of
https://gitlab.gnome.org/julianschacher/top-bar-organizer.git
synced 2025-10-27 15:19:09 +00:00
Compare commits
No commits in common. "main" and "v6" have entirely different histories.
@ -6,7 +6,7 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
[*.{js,ts,json}]
|
[*.{js,json}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
/docs/panel_master_2021-04-21.js
|
/docs/panel_master_2021-04-21.js
|
||||||
/docs/panel_42.5_2022-10-22.js
|
/docs/panel_42.5_2022-10-22.js
|
||||||
/docs/panel_43.2_2023-01-24.js
|
/docs/panel_43.2_2023-01-24.js
|
||||||
/docs/panel_45.0_2023-09-26.js
|
|
||||||
/docs/panel_46.4_2024-09-11.js
|
|
||||||
/docs/panel_47.rc_2024-09-12.js
|
|
||||||
/docs/panel_48.2_2025-06-08.js
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ env:
|
|||||||
extends: 'eslint:recommended'
|
extends: 'eslint:recommended'
|
||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2022
|
ecmaVersion: 2022
|
||||||
sourceType: module
|
|
||||||
rules:
|
rules:
|
||||||
indent:
|
indent:
|
||||||
- error
|
- error
|
||||||
@ -49,12 +48,6 @@ rules:
|
|||||||
object-curly-spacing:
|
object-curly-spacing:
|
||||||
- error
|
- error
|
||||||
- always
|
- always
|
||||||
comma-dangle:
|
|
||||||
- error
|
|
||||||
- arrays: always-multiline
|
|
||||||
objects: always-multiline
|
|
||||||
imports: always-multiline
|
|
||||||
exports: always-multiline
|
|
||||||
functions: never
|
|
||||||
globals:
|
globals:
|
||||||
|
imports: readonly
|
||||||
log: readonly
|
log: readonly
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/dist/
|
|
||||||
top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip
|
top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
### Bug Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Description of the issue you're experiencing.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Steps to Reproduce
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Steps to reproduce the issue.
|
|
||||||
1. Open ...
|
|
||||||
2. Click on ...
|
|
||||||
...
|
|
||||||
-->
|
|
||||||
|
|
||||||
### What behavior did you observe?
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What actually happened that was unexpected?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### What behavior did you expect?
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What did you expect to happen instead?
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Further Information
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable: Screenshots and/or screencasts, which show the issue.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable: Logs, which show relevant information like error messages.
|
|
||||||
- For core extension issues, open the terminal and run: journalctl -f, then reproduce the issue and paste the relevant messages here in a code block (```).
|
|
||||||
- For extension preferences issues, open the terminal and run: journalctl -f -o cat /usr/bin/gjs, then reproduce the issue and paste the relevant messages here in a code block (```).
|
|
||||||
-->
|
|
||||||
|
|
||||||
### System Information
|
|
||||||
|
|
||||||
- Operating System: <!-- What operating system are you using? -->
|
|
||||||
- GNOME Version: <!-- What GNOME version are you running? Open a terminal and run: gnome-shell --version, then paste the output here. -->
|
|
||||||
|
|
||||||
Enabled Extensions:
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Which extensions do you have enabled? Open a terminal and run: gnome-extensions list --enabled --details, then paste the output here in a code block (```).
|
|
||||||
-->
|
|
||||||
@ -9,22 +9,22 @@ For code of this repo in `.js` files, stick to a textwidth of 80.
|
|||||||
The commit message format is as follows:
|
The commit message format is as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
tag: short description
|
Tag: Short description
|
||||||
|
|
||||||
Longer description here if necessary
|
Longer description here if necessary
|
||||||
```
|
```
|
||||||
|
|
||||||
The `tag` should be one of the following:
|
The `Tag` should be one of the following:
|
||||||
|
|
||||||
- `fix` - for a bug fix.
|
- `Fix` - for a bug fix.
|
||||||
- `update` - for a backwards compatible enhancement.
|
- `Update` - for a backwards compatible enhancement.
|
||||||
- `feature` (formerly also `New`) - for a new feature.
|
- `Feature` (formerly also `New`) - for a new feature.
|
||||||
- `breaking` - for a backwards-incompatible enhancement or feature.
|
- `Breaking` - for a backwards-incompatible enhancement or feature.
|
||||||
- `perf` - for a code change that improves performance.
|
- `Perf` - for a code change that improves performance.
|
||||||
- `refactor` - for a code change that neither fixes a bug nor adds a feature (nor is `perf`).
|
- `Refactor` - for a code change that neither fixes a bug nor adds a feature (nor is `Perf`).
|
||||||
- `build` - for changes that affect the build system or external dependencies.
|
- `Build` - for changes that affect the build system or external dependencies.
|
||||||
- `test` - for adding or correcting tests.
|
- `Test` - for adding or correcting tests.
|
||||||
- `docs` - for changes to documentation only.
|
- `Docs` - for changes to documentation only.
|
||||||
- `other` - for anything that isn't covered by the tags above.
|
- `Other` - for anything that isn't covered by the tags above.
|
||||||
|
|
||||||
Those tags are an adapted version of the tags eslint (<https://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes>) and of the tags Angular (<https://github.com/angular/angular/blob/master/CONTRIBUTING.md#type>) uses.
|
Those tags are an adapted version of the tags eslint (<https://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes>) and of the tags Angular (<https://github.com/angular/angular/blob/master/CONTRIBUTING.md#type>) uses.
|
||||||
|
|||||||
12
README.md
12
README.md
@ -1,12 +0,0 @@
|
|||||||
# Top Bar Organizer
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Top Bar Organizer allows you to organize the items of the GNOME Shell top (menu)bar.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
The extension is available on the [GNOME Extensions website](https://extensions.gnome.org/extension/4356/top-bar-organizer/).
|
|
||||||
Or you can also manually install a release from the [releases page](https://gitlab.gnome.org/june/top-bar-organizer/-/releases).
|
|
||||||
|
|
||||||
There's also a community-maintained [AUR package](https://aur.archlinux.org/packages/gnome-shell-extension-top-bar-organizer) available.
|
|
||||||
4
ambient.d.ts
vendored
4
ambient.d.ts
vendored
@ -1,4 +0,0 @@
|
|||||||
import "@girs/gjs"
|
|
||||||
import "@girs/gjs/dom"
|
|
||||||
import "@girs/gnome-shell/ambient"
|
|
||||||
import "@girs/gnome-shell/extensions/global"
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
/* Taken from: https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/43.5/shell/style.css */
|
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
color: alpha(@theme_fg_color, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:backdrop {
|
|
||||||
color: alpha(@theme_unfocused_fg_color, 0.4);
|
|
||||||
}
|
|
||||||
@ -13,13 +13,5 @@
|
|||||||
<description>Order of items in the right box of the top bar.</description>
|
<description>Order of items in the right box of the top bar.</description>
|
||||||
<default>[]</default>
|
<default>[]</default>
|
||||||
</key>
|
</key>
|
||||||
<key name="hide" type="as">
|
|
||||||
<description>Top bar items to (forcefully) hide.</description>
|
|
||||||
<default>[]</default>
|
|
||||||
</key>
|
|
||||||
<key name="show" type="as">
|
|
||||||
<description>Top bar items to (forcefully) show.</description>
|
|
||||||
<default>[]</default>
|
|
||||||
</key>
|
|
||||||
</schema>
|
</schema>
|
||||||
</schemalist>
|
</schemalist>
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<template class="PrefsBoxOrderItemOptionsDialog" parent="AdwDialog">
|
|
||||||
<!-- Same as the default-width of AdwPreferencesWindow.-->
|
|
||||||
<property name="content-width">640</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwToolbarView">
|
|
||||||
<child type="top">
|
|
||||||
<object class="AdwHeaderBar"></object>
|
|
||||||
</child>
|
|
||||||
<property name="content">
|
|
||||||
<object class="AdwPreferencesPage">
|
|
||||||
<child>
|
|
||||||
<object class="AdwPreferencesGroup">
|
|
||||||
<child>
|
|
||||||
<object class="AdwComboRow" id="visibility-row">
|
|
||||||
<property name="title">Visibility</property>
|
|
||||||
<property name="subtitle">Forcefully hide or show an item or just don't affect its visibility. This option applies every time the top bar gets reordered, an items visiblity might be influenced by other factors and this option might not work for every item.</property>
|
|
||||||
<property name="model">
|
|
||||||
<object class="GtkStringList">
|
|
||||||
<items>
|
|
||||||
<item>Default</item>
|
|
||||||
<item>Forcefully Hide</item>
|
|
||||||
<item>Forcefully Show</item>
|
|
||||||
</items>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
<signal name="notify::selected-item" handler="onVisibilityRowSelectionChanged"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</template>
|
|
||||||
</interface>
|
|
||||||
@ -1,6 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<template class="PrefsBoxOrderItemRow" parent="AdwActionRow">
|
<template class="PrefsBoxOrderItemRow" parent="AdwActionRow">
|
||||||
|
<child type="prefix">
|
||||||
|
<object class="GtkLabel" id="item-name-display-label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child type="prefix">
|
<child type="prefix">
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="icon-name">list-drag-handle-symbolic</property>
|
<property name="icon-name">list-drag-handle-symbolic</property>
|
||||||
@ -36,26 +42,10 @@
|
|||||||
</child>
|
</child>
|
||||||
</template>
|
</template>
|
||||||
<menu id="optionsMenuModel">
|
<menu id="optionsMenuModel">
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label">Move Up</attribute>
|
|
||||||
<attribute name="action">row.move-up</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label">Move Down</attribute>
|
|
||||||
<attribute name="action">row.move-down</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label">Options</attribute>
|
|
||||||
<attribute name="action">row.options</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label">Forget</attribute>
|
<attribute name="label">Forget</attribute>
|
||||||
<attribute name="action">row.forget</attribute>
|
<attribute name="action">options.forget</attribute>
|
||||||
</item>
|
</item>
|
||||||
</section>
|
</section>
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@ -13,9 +13,8 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="PrefsBoxOrderListBox" id="left-box-order-list-box">
|
<object class="PrefsBoxOrderListBox">
|
||||||
<property name="box-order">left-box-order</property>
|
<property name="box-order">left-box-order</property>
|
||||||
<signal name="row-move" handler="onRowMove"/>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@ -27,9 +26,8 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="PrefsBoxOrderListBox" id="center-box-order-list-box">
|
<object class="PrefsBoxOrderListBox">
|
||||||
<property name="box-order">center-box-order</property>
|
<property name="box-order">center-box-order</property>
|
||||||
<signal name="row-move" handler="onRowMove"/>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@ -41,9 +39,8 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="PrefsBoxOrderListBox" id="right-box-order-list-box">
|
<object class="PrefsBoxOrderListBox">
|
||||||
<property name="box-order">right-box-order</property>
|
<property name="box-order">right-box-order</property>
|
||||||
<signal name="row-move" handler="onRowMove"/>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
# Creating a Release
|
|
||||||
|
|
||||||
## Create a Tag
|
|
||||||
|
|
||||||
To create a new tag, do the following:
|
|
||||||
|
|
||||||
1. Fill out `git_annotated_tag_template`.
|
|
||||||
2. Run the following command to tag the current commit with `vX`:
|
|
||||||
|
|
||||||
```
|
|
||||||
git tag -a -F git_annotated_tag_template -s --cleanup=verbatim vX
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Restore `git_annotated_tag_template` to its original state:
|
|
||||||
|
|
||||||
```
|
|
||||||
git restore git_annotated_tag_template
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Push the tag:
|
|
||||||
|
|
||||||
```
|
|
||||||
git push --tags
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build a Release-ZIP
|
|
||||||
|
|
||||||
1. Build the release-ZIP:
|
|
||||||
|
|
||||||
```
|
|
||||||
./package.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Name the release-ZIP after the current version:
|
|
||||||
|
|
||||||
```
|
|
||||||
mv top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip top-bar-organizer@julian.gse.jsts.xyz.shell-extension_vX.zip
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create a GitLab Release
|
|
||||||
|
|
||||||
1. Go to the [Releases section of the repo](https://gitlab.gnome.org/june/top-bar-organizer/-/releases) and click on the "New Release" button.
|
|
||||||
2. Select the corresponding tag created earlier.
|
|
||||||
3. Name the release "Top Bar Organizer vX".
|
|
||||||
4. Copy the [release notes template](./release_notes_template.md) and fill it out.
|
|
||||||
5. Drop the release-ZIP created in the previous step at the end of the release notes.
|
|
||||||
6. Create the release.
|
|
||||||
|
|
||||||
## Uploading to the GNOME Extensions Website
|
|
||||||
|
|
||||||
1. Go to the [upload page of the GNOME extensions website](https://extensions.gnome.org/upload/) and upload the release-ZIP.
|
|
||||||
22
docs/Creating_a_New_Tag.md
Normal file
22
docs/Creating_a_New_Tag.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Creating a New Tag
|
||||||
|
|
||||||
|
To create a new tag, do the following:
|
||||||
|
|
||||||
|
1. Fill out `git_annotated_tag_template`.
|
||||||
|
2. Run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
git tag -a -F git_annotated_tag_template -s --cleanup=verbatim <tagname> [<commit>]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Restore `git_annotated_tag_template` to its original state:
|
||||||
|
|
||||||
|
```
|
||||||
|
git restore git_annotated_tag_template
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Push the new tag.
|
||||||
|
|
||||||
|
```
|
||||||
|
git push --tags
|
||||||
|
```
|
||||||
@ -1,344 +0,0 @@
|
|||||||
// My annotated and cut down `js/ui/panel.js` from gnome-shell/45.0.
|
|
||||||
// All annotations are what I guessed, interpreted and copied while reading the
|
|
||||||
// code and comparing to other `panel.js` versions and might be wrong. They are
|
|
||||||
// prefixed with "Annotation:" to indicate that they're my comments, not
|
|
||||||
// comments that orginally existed.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/45.0/js/ui/panel.js
|
|
||||||
// On: 2023-09-26
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/45.0/js/ui/sessionMode.js
|
|
||||||
// On: 2023-09-26
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// I'm using the word "item" to refer to the thing, which gets added to the top
|
|
||||||
// (menu)bar / panel, where an item has a role/name and an indicator.
|
|
||||||
|
|
||||||
|
|
||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import Clutter from 'gi://Clutter';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import GObject from 'gi://GObject';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import St from 'gi://St';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as PopupMenu from './popupMenu.js';
|
|
||||||
import * as PanelMenu from './panelMenu.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as Main from './main.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import {DateMenuButton} from './dateMenu.js';
|
|
||||||
import {ATIndicator} from './status/accessibility.js';
|
|
||||||
import {InputSourceIndicator} from './status/keyboard.js';
|
|
||||||
import {DwellClickIndicator} from './status/dwellClick.js';
|
|
||||||
import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
// Of note (for PANEL_ITEM_IMPLEMENTATIONS):
|
|
||||||
// const AppMenuButton = [...]
|
|
||||||
// const ActivitiesButton = [...]
|
|
||||||
// const QuickSettings = [...]
|
|
||||||
|
|
||||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
|
||||||
'activities': ActivitiesButton,
|
|
||||||
'appMenu': AppMenuButton,
|
|
||||||
'quickSettings': QuickSettings,
|
|
||||||
'dateMenu': DateMenuButton,
|
|
||||||
'a11y': ATIndicator,
|
|
||||||
'keyboard': InputSourceIndicator,
|
|
||||||
'dwellClick': DwellClickIndicator,
|
|
||||||
'screenRecording': ScreenRecordingIndicator,
|
|
||||||
'screenSharing': ScreenSharingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Annotation: Uses ESM export now.
|
|
||||||
export const Panel = GObject.registerClass(
|
|
||||||
class Panel extends St.Widget {
|
|
||||||
// Annotation: Initializes the top (menu)bar / panel.
|
|
||||||
// Does relevant stuff like:
|
|
||||||
// - Defining `this._leftBox`, `this._centerBox` and `this._rightBox`.
|
|
||||||
// - Finally calling `this._updatePanel()`.
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed (except for
|
|
||||||
// formatting).
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
name: 'panel',
|
|
||||||
reactive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
||||||
|
|
||||||
this._sessionStyle = null;
|
|
||||||
|
|
||||||
this.statusArea = {};
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
|
||||||
|
|
||||||
this._leftBox = new St.BoxLayout({name: 'panelLeft'});
|
|
||||||
this.add_child(this._leftBox);
|
|
||||||
this._centerBox = new St.BoxLayout({name: 'panelCenter'});
|
|
||||||
this.add_child(this._centerBox);
|
|
||||||
this._rightBox = new St.BoxLayout({name: 'panelRight'});
|
|
||||||
this.add_child(this._rightBox);
|
|
||||||
|
|
||||||
this.connect('button-press-event', this._onButtonPress.bind(this));
|
|
||||||
this.connect('touch-event', this._onTouchEvent.bind(this));
|
|
||||||
|
|
||||||
Main.overview.connect('showing', () => {
|
|
||||||
this.add_style_pseudo_class('overview');
|
|
||||||
});
|
|
||||||
Main.overview.connect('hiding', () => {
|
|
||||||
this.remove_style_pseudo_class('overview');
|
|
||||||
});
|
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add(this);
|
|
||||||
Main.ctrlAltTabManager.addGroup(this,
|
|
||||||
_('Top Bar'), 'focus-top-bar-symbolic',
|
|
||||||
{sortGroup: CtrlAltTab.SortGroup.TOP});
|
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', this._updatePanel.bind(this));
|
|
||||||
|
|
||||||
global.display.connect('workareas-changed', () => this.queue_relayout());
|
|
||||||
this._updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
|
|
||||||
// Annotation: Gets called by `this._init()` to populate the top (menu)bar /
|
|
||||||
// panel initially.
|
|
||||||
//
|
|
||||||
// It does the following relevant stuff:
|
|
||||||
// - Calls `this._hideIndicators()`
|
|
||||||
// - Calls `this._updateBox()` for `this._leftBox`, `this._centerBox` and
|
|
||||||
// `this._rightBox` with `panel.left`, `panel.center` and `panel.right` to
|
|
||||||
// populate the boxes with items defined in `panel.left`, `panel.center`
|
|
||||||
// and `panel.right`.
|
|
||||||
//
|
|
||||||
// `panel.left`, `panel.center` and `panel.right` get set via the line
|
|
||||||
// `let panel = Main.sessionMode.panel`, which uses the panel of Mains
|
|
||||||
// (`js/ui/main.js`) instance of SessionMode (`js/ui/sessionMode.js`).
|
|
||||||
//
|
|
||||||
// And in `js/ui/sessionMode.js` (45.0, 2023-09-26) you have different
|
|
||||||
// modes with different panel configuration. For example the "user" mode
|
|
||||||
// with:
|
|
||||||
// ```
|
|
||||||
// panel: {
|
|
||||||
// left: ['activities'],
|
|
||||||
// center: ['dateMenu'],
|
|
||||||
// right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'],
|
|
||||||
// },
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// This way this function populates the top (menu)bar / panel with the
|
|
||||||
// default stuff you see on a fresh Gnome.
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
_updatePanel() {
|
|
||||||
let panel = Main.sessionMode.panel;
|
|
||||||
this._hideIndicators();
|
|
||||||
this._updateBox(panel.left, this._leftBox);
|
|
||||||
this._updateBox(panel.center, this._centerBox);
|
|
||||||
this._updateBox(panel.right, this._rightBox);
|
|
||||||
|
|
||||||
if (panel.left.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (panel.right.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
|
|
||||||
// Default to center if there is no dateMenu
|
|
||||||
else
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.remove_style_class_name(this._sessionStyle);
|
|
||||||
|
|
||||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.add_style_class_name(this._sessionStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function hides all items, which are in the top (menu)bar
|
|
||||||
// panel and in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
_hideIndicators() {
|
|
||||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator)
|
|
||||||
continue;
|
|
||||||
indicator.container.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a role (of an item) and returns a
|
|
||||||
// corresponding indicator, if either of two things are true:
|
|
||||||
// - The indicator is already in `this.statusArea`.
|
|
||||||
// Then it just returns the indicator by using `this.statusArea`.
|
|
||||||
// - The role is in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
// Then it creates a new indicator, adds it to `this.statusArea` and
|
|
||||||
// returns it.
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
_ensureIndicator(role) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator) {
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
|
||||||
if (!constructor) {
|
|
||||||
// This icon is not implemented (this is a bug)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
indicator = new constructor(this);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
}
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a list of items (or rather their roles)
|
|
||||||
// and adds the indicators of those items to a box (like `this._leftBox`)
|
|
||||||
// using `this._ensureIndicator()` to get the indicator corresponding to the
|
|
||||||
// given role.
|
|
||||||
// So only items with roles `this._ensureIndicator()` knows, get added.
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
_updateBox(elements, box) {
|
|
||||||
let nChildren = box.get_n_children();
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let role = elements[i];
|
|
||||||
let indicator = this._ensureIndicator(role);
|
|
||||||
if (indicator == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this._addToPanelBox(role, indicator, i + nChildren, box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function adds the given item to the specified top
|
|
||||||
// (menu)bar / panel box and connects to "destroy" and "menu-set" events.
|
|
||||||
//
|
|
||||||
// It takes the following arguments:
|
|
||||||
// - role: The name of the item to add.
|
|
||||||
// - indicator: The indicator of the item to add.
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - `this._leftBox`
|
|
||||||
// - `this._centerBox`
|
|
||||||
// - `this._rightBox`
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
_addToPanelBox(role, indicator, position, box) {
|
|
||||||
let container = indicator.container;
|
|
||||||
container.show();
|
|
||||||
|
|
||||||
let parent = container.get_parent();
|
|
||||||
if (parent)
|
|
||||||
parent.remove_actor(container);
|
|
||||||
|
|
||||||
|
|
||||||
box.insert_child_at_index(container, position);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
let destroyId = indicator.connect('destroy', emitter => {
|
|
||||||
delete this.statusArea[role];
|
|
||||||
emitter.disconnect(destroyId);
|
|
||||||
});
|
|
||||||
indicator.connect('menu-set', this._onMenuSet.bind(this));
|
|
||||||
this._onMenuSet(indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function allows you to add an item to the top (menu)bar
|
|
||||||
// / panel.
|
|
||||||
// While per default it adds the item to the status area (the right box of
|
|
||||||
// the top bar), you can specify the box and add the item to any of the
|
|
||||||
// three boxes of the top bar.
|
|
||||||
// To add an item to the top bar, you need to give its role and indicator.
|
|
||||||
//
|
|
||||||
// This function takes the following arguments:
|
|
||||||
// - role: A name for the item to add.
|
|
||||||
// - indicator: The indicator for the item to add (must be an instance of
|
|
||||||
// PanelMenu.Button).
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - "left": referring to `this._leftBox`
|
|
||||||
// - "center": referring to `this._centerBox`
|
|
||||||
// - "right": referring to `this._rightBox`
|
|
||||||
// These boxes are what you see in the top bar as the left, right and
|
|
||||||
// center sections.
|
|
||||||
//
|
|
||||||
// Finally this function just calls `this._addToPanelBox()` for the actual
|
|
||||||
// work, so it basically just makes sure the input to
|
|
||||||
// `this._addToPanelBox()` is correct.
|
|
||||||
//
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed.
|
|
||||||
addToStatusArea(role, indicator, position, box) {
|
|
||||||
if (this.statusArea[role])
|
|
||||||
throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`);
|
|
||||||
|
|
||||||
if (!(indicator instanceof PanelMenu.Button))
|
|
||||||
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
||||||
|
|
||||||
position ??= 0;
|
|
||||||
let boxes = {
|
|
||||||
left: this._leftBox,
|
|
||||||
center: this._centerBox,
|
|
||||||
right: this._rightBox,
|
|
||||||
};
|
|
||||||
let boxContainer = boxes[box] || this._rightBox;
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compared to panel_43.2_2023-01-24.js: Nothing changed, except for the
|
|
||||||
// usage of === instead of ==.
|
|
||||||
_onMenuSet(indicator) {
|
|
||||||
if (!indicator.menu || indicator.menu._openChangedId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.menuManager.addMenu(indicator.menu);
|
|
||||||
|
|
||||||
indicator.menu._openChangedId = indicator.menu.connect('open-state-changed',
|
|
||||||
(menu, isOpen) => {
|
|
||||||
let boxAlignment;
|
|
||||||
if (this._leftBox.contains(indicator.container))
|
|
||||||
boxAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (this._centerBox.contains(indicator.container))
|
|
||||||
boxAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
else if (this._rightBox.contains(indicator.container))
|
|
||||||
boxAlignment = Clutter.ActorAlign.END;
|
|
||||||
|
|
||||||
if (boxAlignment === Main.messageTray.bannerAlignment)
|
|
||||||
Main.messageTray.bannerBlocked = isOpen;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
});
|
|
||||||
@ -1,322 +0,0 @@
|
|||||||
// My annotated and cut down js/ui/panel.js from gnome-shell/46.4.
|
|
||||||
// All annotations are what I guessed, interpreted and copied while reading the
|
|
||||||
// code and comparing to other panel.js versions and might be wrong. They are
|
|
||||||
// prefixed with "Annotation:" to indicate that they're my comments, not
|
|
||||||
// comments that orginally existed.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/46.4/js/ui/panel.js
|
|
||||||
// On: 2024-09-11
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/46.4/js/ui/sessionMode.js
|
|
||||||
// On: 2023-09-11
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// I'm using the word "item" to refer to the thing, which gets added to the top
|
|
||||||
// (menu)bar / panel, where an item has a role/name and an indicator.
|
|
||||||
|
|
||||||
|
|
||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import Clutter from 'gi://Clutter';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import GObject from 'gi://GObject';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import St from 'gi://St';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as PopupMenu from './popupMenu.js';
|
|
||||||
import * as PanelMenu from './panelMenu.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as Main from './main.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import {DateMenuButton} from './dateMenu.js';
|
|
||||||
import {ATIndicator} from './status/accessibility.js';
|
|
||||||
import {InputSourceIndicator} from './status/keyboard.js';
|
|
||||||
import {DwellClickIndicator} from './status/dwellClick.js';
|
|
||||||
import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
// Of note (for PANEL_ITEM_IMPLEMENTATIONS):
|
|
||||||
// const AppMenuButton = [...]
|
|
||||||
// const ActivitiesButton = [...]
|
|
||||||
// const QuickSettings = [...]
|
|
||||||
|
|
||||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
|
||||||
'activities': ActivitiesButton,
|
|
||||||
'appMenu': AppMenuButton,
|
|
||||||
'quickSettings': QuickSettings,
|
|
||||||
'dateMenu': DateMenuButton,
|
|
||||||
'a11y': ATIndicator,
|
|
||||||
'keyboard': InputSourceIndicator,
|
|
||||||
'dwellClick': DwellClickIndicator,
|
|
||||||
'screenRecording': ScreenRecordingIndicator,
|
|
||||||
'screenSharing': ScreenSharingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Panel = GObject.registerClass(
|
|
||||||
class Panel extends St.Widget {
|
|
||||||
// Annotation: Initializes the top (menu)bar / panel.
|
|
||||||
// Does relevant stuff like:
|
|
||||||
// - Defining this._leftBox, this._centerBox and this._rightBox.
|
|
||||||
// - Finally calling this._updatePanel().
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Not much changed except from some
|
|
||||||
// slightly different function names/calls.
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
name: 'panel',
|
|
||||||
reactive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
||||||
|
|
||||||
this._sessionStyle = null;
|
|
||||||
|
|
||||||
this.statusArea = {};
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
|
||||||
|
|
||||||
this._leftBox = new St.BoxLayout({name: 'panelLeft'});
|
|
||||||
this.add_child(this._leftBox);
|
|
||||||
this._centerBox = new St.BoxLayout({name: 'panelCenter'});
|
|
||||||
this.add_child(this._centerBox);
|
|
||||||
this._rightBox = new St.BoxLayout({name: 'panelRight'});
|
|
||||||
this.add_child(this._rightBox);
|
|
||||||
|
|
||||||
this.connect('button-press-event', this._onButtonPress.bind(this));
|
|
||||||
this.connect('touch-event', this._onTouchEvent.bind(this));
|
|
||||||
|
|
||||||
Main.overview.connectObject('showing',
|
|
||||||
() => this.add_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
Main.overview.connectObject('hiding',
|
|
||||||
() => this.remove_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add_child(this);
|
|
||||||
Main.ctrlAltTabManager.addGroup(this,
|
|
||||||
_('Top Bar'), 'shell-focus-top-bar-symbolic',
|
|
||||||
{sortGroup: CtrlAltTab.SortGroup.TOP});
|
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', this._updatePanel.bind(this));
|
|
||||||
|
|
||||||
global.display.connect('workareas-changed', () => this.queue_relayout());
|
|
||||||
this._updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
|
|
||||||
// Annotation: Gets called by this._init() to populate the top (menu)bar /
|
|
||||||
// panel initially.
|
|
||||||
//
|
|
||||||
// It does the following relevant stuff:
|
|
||||||
// - Calls this._hideIndicators()
|
|
||||||
// - Calls this._updateBox() for this._leftBox, this._centerBox and
|
|
||||||
// this._rightBox with panel.left, panel.center and panel.right to
|
|
||||||
// populate the boxes with items defined in panel.left, panel.center and
|
|
||||||
// panel.right.
|
|
||||||
//
|
|
||||||
// panel.left, panel.center and panel.right get set via the line let panel
|
|
||||||
// = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js)
|
|
||||||
// instance of SessionMode (js/ui/sessionMode.js).
|
|
||||||
//
|
|
||||||
// And in js/ui/sessionMode.js (46.4, 2024-09-11) you have different modes
|
|
||||||
// with different panel configuration. For example the "user" mode with:
|
|
||||||
// panel: {
|
|
||||||
// left: ['activities'],
|
|
||||||
// center: ['dateMenu'],
|
|
||||||
// right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'],
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This way this function populates the top (menu)bar / panel with the
|
|
||||||
// default stuff you see on a fresh Gnome.
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Nothing changed.
|
|
||||||
_updatePanel() {
|
|
||||||
let panel = Main.sessionMode.panel;
|
|
||||||
this._hideIndicators();
|
|
||||||
this._updateBox(panel.left, this._leftBox);
|
|
||||||
this._updateBox(panel.center, this._centerBox);
|
|
||||||
this._updateBox(panel.right, this._rightBox);
|
|
||||||
|
|
||||||
if (panel.left.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (panel.right.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
|
|
||||||
// Default to center if there is no dateMenu
|
|
||||||
else
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.remove_style_class_name(this._sessionStyle);
|
|
||||||
|
|
||||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.add_style_class_name(this._sessionStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function hides all items, which are in the top (menu)bar
|
|
||||||
// panel and in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Nothing changed.
|
|
||||||
_hideIndicators() {
|
|
||||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator)
|
|
||||||
continue;
|
|
||||||
indicator.container.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a role (of an item) and returns a
|
|
||||||
// corresponding indicator, if either of two things are true:
|
|
||||||
// - The indicator is already in this.statusArea.
|
|
||||||
// Then it just returns the indicator by using this.statusArea.
|
|
||||||
// - The role is in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
// Then it creates a new indicator, adds it to this.statusArea and returns
|
|
||||||
// it.
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Nothing changed.
|
|
||||||
_ensureIndicator(role) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator) {
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
|
||||||
if (!constructor) {
|
|
||||||
// This icon is not implemented (this is a bug)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
indicator = new constructor(this);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
}
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a list of items (or rather their roles)
|
|
||||||
// and adds the indicators of those items to a box (like this._leftBox)
|
|
||||||
// using this._ensureIndicator() to get the indicator corresponding to the
|
|
||||||
// given role.
|
|
||||||
// So only items with roles this._ensureIndicator() knows, get added.
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Nothing changed.
|
|
||||||
_updateBox(elements, box) {
|
|
||||||
let nChildren = box.get_n_children();
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let role = elements[i];
|
|
||||||
let indicator = this._ensureIndicator(role);
|
|
||||||
if (indicator == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this._addToPanelBox(role, indicator, i + nChildren, box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function adds the given item to the specified top
|
|
||||||
// (menu)bar / panel box and connects to "destroy" and "menu-set" events.
|
|
||||||
//
|
|
||||||
// It takes the following arguments:
|
|
||||||
// - role: The name of the item to add.
|
|
||||||
// - indicator: The indicator of the item to add.
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - this._leftBox
|
|
||||||
// - this._centerBox
|
|
||||||
// - this._rightBox
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: The call of
|
|
||||||
// parent.remove_actor(container) changed to parent.remove_child(container).
|
|
||||||
_addToPanelBox(role, indicator, position, box) {
|
|
||||||
let container = indicator.container;
|
|
||||||
container.show();
|
|
||||||
|
|
||||||
let parent = container.get_parent();
|
|
||||||
if (parent)
|
|
||||||
parent.remove_child(container);
|
|
||||||
|
|
||||||
|
|
||||||
box.insert_child_at_index(container, position);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
let destroyId = indicator.connect('destroy', emitter => {
|
|
||||||
delete this.statusArea[role];
|
|
||||||
emitter.disconnect(destroyId);
|
|
||||||
});
|
|
||||||
indicator.connect('menu-set', this._onMenuSet.bind(this));
|
|
||||||
this._onMenuSet(indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function allows you to add an item to the top (menu)bar
|
|
||||||
// / panel.
|
|
||||||
// While per default it adds the item to the status area (the right box of
|
|
||||||
// the top bar), you can specify the box and add the item to any of the
|
|
||||||
// three boxes of the top bar.
|
|
||||||
// To add an item to the top bar, you need to give its role and indicator.
|
|
||||||
//
|
|
||||||
// This function takes the following arguments:
|
|
||||||
// - role: A name for the item to add.
|
|
||||||
// - indicator: The indicator for the item to add (must be an instance of
|
|
||||||
// PanelMenu.Button).
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - "left": referring to this._leftBox
|
|
||||||
// - "center": referring to this._centerBox
|
|
||||||
// - "right": referring to this._rightBox
|
|
||||||
// These boxes are what you see in the top bar as the left, right and
|
|
||||||
// center sections.
|
|
||||||
//
|
|
||||||
// Finally this function just calls this._addToPanelBox() for the actual
|
|
||||||
// work, so it basically just makes sure the input to this._addToPanelBox()
|
|
||||||
// is correct.
|
|
||||||
//
|
|
||||||
// Compared to panel_45.0_2023-09-26.js: Nothing changed.
|
|
||||||
addToStatusArea(role, indicator, position, box) {
|
|
||||||
if (this.statusArea[role])
|
|
||||||
throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`);
|
|
||||||
|
|
||||||
if (!(indicator instanceof PanelMenu.Button))
|
|
||||||
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
||||||
|
|
||||||
position ??= 0;
|
|
||||||
let boxes = {
|
|
||||||
left: this._leftBox,
|
|
||||||
center: this._centerBox,
|
|
||||||
right: this._rightBox,
|
|
||||||
};
|
|
||||||
let boxContainer = boxes[box] || this._rightBox;
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of note:
|
|
||||||
// _onMenuSet(indicator) { [...] }
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
});
|
|
||||||
@ -1,317 +0,0 @@
|
|||||||
// My annotated and cut down js/ui/panel.js from gnome-shell/47.rc.
|
|
||||||
// All annotations are what I guessed, interpreted and copied while reading the
|
|
||||||
// code and comparing to other panel.js versions and might be wrong. They are
|
|
||||||
// prefixed with "Annotation:" to indicate that they're my comments, not
|
|
||||||
// comments that orginally existed.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/47.rc/js/ui/panel.js
|
|
||||||
// On: 2024-09-12
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/47.rc/js/ui/sessionMode.js
|
|
||||||
// On: 2023-09-12
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// I'm using the word "item" to refer to the thing, which gets added to the top
|
|
||||||
// (menu)bar / panel, where an item has a role/name and an indicator.
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import Clutter from 'gi://Clutter';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import GObject from 'gi://GObject';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import St from 'gi://St';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as PopupMenu from './popupMenu.js';
|
|
||||||
import * as PanelMenu from './panelMenu.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as Main from './main.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import {DateMenuButton} from './dateMenu.js';
|
|
||||||
import {ATIndicator} from './status/accessibility.js';
|
|
||||||
import {InputSourceIndicator} from './status/keyboard.js';
|
|
||||||
import {DwellClickIndicator} from './status/dwellClick.js';
|
|
||||||
import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
// Of note (for PANEL_ITEM_IMPLEMENTATIONS):
|
|
||||||
// const AppMenuButton = [...]
|
|
||||||
// const ActivitiesButton = [...]
|
|
||||||
// const QuickSettings = [...]
|
|
||||||
|
|
||||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
|
||||||
'activities': ActivitiesButton,
|
|
||||||
'appMenu': AppMenuButton,
|
|
||||||
'quickSettings': QuickSettings,
|
|
||||||
'dateMenu': DateMenuButton,
|
|
||||||
'a11y': ATIndicator,
|
|
||||||
'keyboard': InputSourceIndicator,
|
|
||||||
'dwellClick': DwellClickIndicator,
|
|
||||||
'screenRecording': ScreenRecordingIndicator,
|
|
||||||
'screenSharing': ScreenSharingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Panel = GObject.registerClass(
|
|
||||||
class Panel extends St.Widget {
|
|
||||||
// Annotation: Initializes the top (menu)bar / panel.
|
|
||||||
// Does relevant stuff like:
|
|
||||||
// - Defining this._leftBox, this._centerBox and this._rightBox.
|
|
||||||
// - Finally calling this._updatePanel().
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
name: 'panel',
|
|
||||||
reactive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
||||||
|
|
||||||
this._sessionStyle = null;
|
|
||||||
|
|
||||||
this.statusArea = {};
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
|
||||||
|
|
||||||
this._leftBox = new St.BoxLayout({name: 'panelLeft'});
|
|
||||||
this.add_child(this._leftBox);
|
|
||||||
this._centerBox = new St.BoxLayout({name: 'panelCenter'});
|
|
||||||
this.add_child(this._centerBox);
|
|
||||||
this._rightBox = new St.BoxLayout({name: 'panelRight'});
|
|
||||||
this.add_child(this._rightBox);
|
|
||||||
|
|
||||||
this.connect('button-press-event', this._onButtonPress.bind(this));
|
|
||||||
this.connect('touch-event', this._onTouchEvent.bind(this));
|
|
||||||
|
|
||||||
Main.overview.connectObject('showing',
|
|
||||||
() => this.add_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
Main.overview.connectObject('hiding',
|
|
||||||
() => this.remove_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add_child(this);
|
|
||||||
Main.ctrlAltTabManager.addGroup(this,
|
|
||||||
_('Top Bar'), 'shell-focus-top-bar-symbolic',
|
|
||||||
{sortGroup: CtrlAltTab.SortGroup.TOP});
|
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', this._updatePanel.bind(this));
|
|
||||||
|
|
||||||
global.display.connect('workareas-changed', () => this.queue_relayout());
|
|
||||||
this._updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
|
|
||||||
// Annotation: Gets called by this._init() to populate the top (menu)bar /
|
|
||||||
// panel initially.
|
|
||||||
//
|
|
||||||
// It does the following relevant stuff:
|
|
||||||
// - Calls this._hideIndicators()
|
|
||||||
// - Calls this._updateBox() for this._leftBox, this._centerBox and
|
|
||||||
// this._rightBox with panel.left, panel.center and panel.right to
|
|
||||||
// populate the boxes with items defined in panel.left, panel.center and
|
|
||||||
// panel.right.
|
|
||||||
//
|
|
||||||
// panel.left, panel.center and panel.right get set via the line let panel
|
|
||||||
// = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js)
|
|
||||||
// instance of SessionMode (js/ui/sessionMode.js).
|
|
||||||
//
|
|
||||||
// And in js/ui/sessionMode.js (47.rc, 2024-09-12) you have different modes
|
|
||||||
// with different panel configuration. For example the "user" mode with:
|
|
||||||
// panel: {
|
|
||||||
// left: ['activities'],
|
|
||||||
// center: ['dateMenu'],
|
|
||||||
// right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'],
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This way this function populates the top (menu)bar / panel with the
|
|
||||||
// default stuff you see on a fresh Gnome.
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_updatePanel() {
|
|
||||||
let panel = Main.sessionMode.panel;
|
|
||||||
this._hideIndicators();
|
|
||||||
this._updateBox(panel.left, this._leftBox);
|
|
||||||
this._updateBox(panel.center, this._centerBox);
|
|
||||||
this._updateBox(panel.right, this._rightBox);
|
|
||||||
|
|
||||||
if (panel.left.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (panel.right.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
|
|
||||||
// Default to center if there is no dateMenu
|
|
||||||
else
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.remove_style_class_name(this._sessionStyle);
|
|
||||||
|
|
||||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.add_style_class_name(this._sessionStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function hides all items, which are in the top (menu)bar
|
|
||||||
// panel and in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_hideIndicators() {
|
|
||||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator)
|
|
||||||
continue;
|
|
||||||
indicator.container.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a role (of an item) and returns a
|
|
||||||
// corresponding indicator, if either of two things are true:
|
|
||||||
// - The indicator is already in this.statusArea.
|
|
||||||
// Then it just returns the indicator by using this.statusArea.
|
|
||||||
// - The role is in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
// Then it creates a new indicator, adds it to this.statusArea and returns
|
|
||||||
// it.
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_ensureIndicator(role) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator) {
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
|
||||||
if (!constructor) {
|
|
||||||
// This icon is not implemented (this is a bug)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
indicator = new constructor(this);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
}
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a list of items (or rather their roles)
|
|
||||||
// and adds the indicators of those items to a box (like this._leftBox)
|
|
||||||
// using this._ensureIndicator() to get the indicator corresponding to the
|
|
||||||
// given role.
|
|
||||||
// So only items with roles this._ensureIndicator() knows, get added.
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_updateBox(elements, box) {
|
|
||||||
let nChildren = box.get_n_children();
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let role = elements[i];
|
|
||||||
let indicator = this._ensureIndicator(role);
|
|
||||||
if (indicator == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this._addToPanelBox(role, indicator, i + nChildren, box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function adds the given item to the specified top
|
|
||||||
// (menu)bar / panel box and connects to "destroy" and "menu-set" events.
|
|
||||||
//
|
|
||||||
// It takes the following arguments:
|
|
||||||
// - role: The name of the item to add.
|
|
||||||
// - indicator: The indicator of the item to add.
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - this._leftBox
|
|
||||||
// - this._centerBox
|
|
||||||
// - this._rightBox
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
_addToPanelBox(role, indicator, position, box) {
|
|
||||||
let container = indicator.container;
|
|
||||||
container.show();
|
|
||||||
|
|
||||||
let parent = container.get_parent();
|
|
||||||
if (parent)
|
|
||||||
parent.remove_child(container);
|
|
||||||
|
|
||||||
|
|
||||||
box.insert_child_at_index(container, position);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
let destroyId = indicator.connect('destroy', emitter => {
|
|
||||||
delete this.statusArea[role];
|
|
||||||
emitter.disconnect(destroyId);
|
|
||||||
});
|
|
||||||
indicator.connect('menu-set', this._onMenuSet.bind(this));
|
|
||||||
this._onMenuSet(indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function allows you to add an item to the top (menu)bar
|
|
||||||
// / panel.
|
|
||||||
// While per default it adds the item to the status area (the right box of
|
|
||||||
// the top bar), you can specify the box and add the item to any of the
|
|
||||||
// three boxes of the top bar.
|
|
||||||
// To add an item to the top bar, you need to give its role and indicator.
|
|
||||||
//
|
|
||||||
// This function takes the following arguments:
|
|
||||||
// - role: A name for the item to add.
|
|
||||||
// - indicator: The indicator for the item to add (must be an instance of
|
|
||||||
// PanelMenu.Button).
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - "left": referring to this._leftBox
|
|
||||||
// - "center": referring to this._centerBox
|
|
||||||
// - "right": referring to this._rightBox
|
|
||||||
// These boxes are what you see in the top bar as the left, right and
|
|
||||||
// center sections.
|
|
||||||
//
|
|
||||||
// Finally this function just calls this._addToPanelBox() for the actual
|
|
||||||
// work, so it basically just makes sure the input to this._addToPanelBox()
|
|
||||||
// is correct.
|
|
||||||
//
|
|
||||||
// Compared to panel_46.4_2024-09-11.js: Nothing changed.
|
|
||||||
addToStatusArea(role, indicator, position, box) {
|
|
||||||
if (this.statusArea[role])
|
|
||||||
throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`);
|
|
||||||
|
|
||||||
if (!(indicator instanceof PanelMenu.Button))
|
|
||||||
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
||||||
|
|
||||||
position ??= 0;
|
|
||||||
let boxes = {
|
|
||||||
left: this._leftBox,
|
|
||||||
center: this._centerBox,
|
|
||||||
right: this._rightBox,
|
|
||||||
};
|
|
||||||
let boxContainer = boxes[box] || this._rightBox;
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of note:
|
|
||||||
// _onMenuSet(indicator) { [...] }
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
});
|
|
||||||
@ -1,322 +0,0 @@
|
|||||||
// My annotated and cut down js/ui/panel.js from gnome-shell/48.2.
|
|
||||||
// All annotations are what I guessed, interpreted and copied while reading the
|
|
||||||
// code and comparing to other panel.js versions and might be wrong. They are
|
|
||||||
// prefixed with "Annotation:" to indicate that they're my comments, not
|
|
||||||
// comments that orginally existed.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panel.js
|
|
||||||
// On: 2025-06-08
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/sessionMode.js
|
|
||||||
// On: 2025-06-08
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// I'm using the word "item" to refer to the thing, which gets added to the top
|
|
||||||
// (menu)bar / panel, where an item has a role/name and an indicator.
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import Clutter from 'gi://Clutter';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import GObject from 'gi://GObject';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import St from 'gi://St';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as PopupMenu from './popupMenu.js';
|
|
||||||
import * as PanelMenu from './panelMenu.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as Main from './main.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import {DateMenuButton} from './dateMenu.js';
|
|
||||||
import {ATIndicator} from './status/accessibility.js';
|
|
||||||
import {InputSourceIndicator} from './status/keyboard.js';
|
|
||||||
import {DwellClickIndicator} from './status/dwellClick.js';
|
|
||||||
import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
// Of note (for PANEL_ITEM_IMPLEMENTATIONS):
|
|
||||||
// const AppMenuButton = [...]
|
|
||||||
// const ActivitiesButton = [...]
|
|
||||||
// const QuickSettings = [...]
|
|
||||||
|
|
||||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
|
||||||
'activities': ActivitiesButton,
|
|
||||||
'appMenu': AppMenuButton,
|
|
||||||
'quickSettings': QuickSettings,
|
|
||||||
'dateMenu': DateMenuButton,
|
|
||||||
'a11y': ATIndicator,
|
|
||||||
'keyboard': InputSourceIndicator,
|
|
||||||
'dwellClick': DwellClickIndicator,
|
|
||||||
'screenRecording': ScreenRecordingIndicator,
|
|
||||||
'screenSharing': ScreenSharingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Panel = GObject.registerClass(
|
|
||||||
class Panel extends St.Widget {
|
|
||||||
// Annotation: Initializes the top (menu)bar / panel.
|
|
||||||
// Does relevant stuff like:
|
|
||||||
// - Defining this._leftBox, this._centerBox and this._rightBox.
|
|
||||||
// - Finally calling this._updatePanel().
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: connectObject instead of connect
|
|
||||||
// gets used, which shouldn't be relevant for this extension.
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
name: 'panel',
|
|
||||||
reactive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
||||||
|
|
||||||
this._sessionStyle = null;
|
|
||||||
|
|
||||||
this.statusArea = {};
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
|
||||||
|
|
||||||
this._leftBox = new St.BoxLayout({name: 'panelLeft'});
|
|
||||||
this.add_child(this._leftBox);
|
|
||||||
this._centerBox = new St.BoxLayout({name: 'panelCenter'});
|
|
||||||
this.add_child(this._centerBox);
|
|
||||||
this._rightBox = new St.BoxLayout({name: 'panelRight'});
|
|
||||||
this.add_child(this._rightBox);
|
|
||||||
|
|
||||||
this.connect('button-press-event', this._onButtonPress.bind(this));
|
|
||||||
this.connect('touch-event', this._onTouchEvent.bind(this));
|
|
||||||
|
|
||||||
Main.overview.connectObject('showing',
|
|
||||||
() => this.add_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
Main.overview.connectObject('hiding',
|
|
||||||
() => this.remove_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add_child(this);
|
|
||||||
Main.ctrlAltTabManager.addGroup(this,
|
|
||||||
_('Top Bar'), 'shell-focus-top-bar-symbolic',
|
|
||||||
{sortGroup: CtrlAltTab.SortGroup.TOP});
|
|
||||||
|
|
||||||
Main.sessionMode.connectObject('updated',
|
|
||||||
this._updatePanel.bind(this),
|
|
||||||
this);
|
|
||||||
|
|
||||||
global.display.connectObject('workareas-changed',
|
|
||||||
() => this.queue_relayout(),
|
|
||||||
this);
|
|
||||||
this._updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
|
|
||||||
// Annotation: Gets called by this._init() to populate the top (menu)bar /
|
|
||||||
// panel initially.
|
|
||||||
//
|
|
||||||
// It does the following relevant stuff:
|
|
||||||
// - Calls this._hideIndicators()
|
|
||||||
// - Calls this._updateBox() for this._leftBox, this._centerBox and
|
|
||||||
// this._rightBox with panel.left, panel.center and panel.right to
|
|
||||||
// populate the boxes with items defined in panel.left, panel.center and
|
|
||||||
// panel.right.
|
|
||||||
//
|
|
||||||
// panel.left, panel.center and panel.right get set via the line let panel
|
|
||||||
// = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js)
|
|
||||||
// instance of SessionMode (js/ui/sessionMode.js).
|
|
||||||
//
|
|
||||||
// And in js/ui/sessionMode.js (48.2, 2025-06-08) you have different modes
|
|
||||||
// with different panel configuration. For example the "user" mode with:
|
|
||||||
// panel: {
|
|
||||||
// left: ['activities'],
|
|
||||||
// center: ['dateMenu'],
|
|
||||||
// right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'],
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This way this function populates the top (menu)bar / panel with the
|
|
||||||
// default stuff you see on a fresh Gnome.
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
_updatePanel() {
|
|
||||||
let panel = Main.sessionMode.panel;
|
|
||||||
this._hideIndicators();
|
|
||||||
this._updateBox(panel.left, this._leftBox);
|
|
||||||
this._updateBox(panel.center, this._centerBox);
|
|
||||||
this._updateBox(panel.right, this._rightBox);
|
|
||||||
|
|
||||||
if (panel.left.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (panel.right.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
|
|
||||||
// Default to center if there is no dateMenu
|
|
||||||
else
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.remove_style_class_name(this._sessionStyle);
|
|
||||||
|
|
||||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.add_style_class_name(this._sessionStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function hides all items, which are in the top (menu)bar
|
|
||||||
// panel and in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
_hideIndicators() {
|
|
||||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator)
|
|
||||||
continue;
|
|
||||||
indicator.container.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a role (of an item) and returns a
|
|
||||||
// corresponding indicator, if either of two things are true:
|
|
||||||
// - The indicator is already in this.statusArea.
|
|
||||||
// Then it just returns the indicator by using this.statusArea.
|
|
||||||
// - The role is in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
// Then it creates a new indicator, adds it to this.statusArea and returns
|
|
||||||
// it.
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
_ensureIndicator(role) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator) {
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
|
||||||
if (!constructor) {
|
|
||||||
// This icon is not implemented (this is a bug)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
indicator = new constructor(this);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
}
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a list of items (or rather their roles)
|
|
||||||
// and adds the indicators of those items to a box (like this._leftBox)
|
|
||||||
// using this._ensureIndicator() to get the indicator corresponding to the
|
|
||||||
// given role.
|
|
||||||
// So only items with roles this._ensureIndicator() knows, get added.
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
_updateBox(elements, box) {
|
|
||||||
let nChildren = box.get_n_children();
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let role = elements[i];
|
|
||||||
let indicator = this._ensureIndicator(role);
|
|
||||||
if (indicator == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this._addToPanelBox(role, indicator, i + nChildren, box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function adds the given item to the specified top
|
|
||||||
// (menu)bar / panel box and connects to "destroy" and "menu-set" events.
|
|
||||||
//
|
|
||||||
// It takes the following arguments:
|
|
||||||
// - role: The name of the item to add.
|
|
||||||
// - indicator: The indicator of the item to add.
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - this._leftBox
|
|
||||||
// - this._centerBox
|
|
||||||
// - this._rightBox
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
_addToPanelBox(role, indicator, position, box) {
|
|
||||||
let container = indicator.container;
|
|
||||||
container.show();
|
|
||||||
|
|
||||||
let parent = container.get_parent();
|
|
||||||
if (parent)
|
|
||||||
parent.remove_child(container);
|
|
||||||
|
|
||||||
|
|
||||||
box.insert_child_at_index(container, position);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
let destroyId = indicator.connect('destroy', emitter => {
|
|
||||||
delete this.statusArea[role];
|
|
||||||
emitter.disconnect(destroyId);
|
|
||||||
});
|
|
||||||
indicator.connect('menu-set', this._onMenuSet.bind(this));
|
|
||||||
this._onMenuSet(indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function allows you to add an item to the top (menu)bar
|
|
||||||
// / panel.
|
|
||||||
// While per default it adds the item to the status area (the right box of
|
|
||||||
// the top bar), you can specify the box and add the item to any of the
|
|
||||||
// three boxes of the top bar.
|
|
||||||
// To add an item to the top bar, you need to give its role and indicator.
|
|
||||||
//
|
|
||||||
// This function takes the following arguments:
|
|
||||||
// - role: A name for the item to add.
|
|
||||||
// - indicator: The indicator for the item to add (must be an instance of
|
|
||||||
// PanelMenu.Button).
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - "left": referring to this._leftBox
|
|
||||||
// - "center": referring to this._centerBox
|
|
||||||
// - "right": referring to this._rightBox
|
|
||||||
// These boxes are what you see in the top bar as the left, right and
|
|
||||||
// center sections.
|
|
||||||
//
|
|
||||||
// Finally this function just calls this._addToPanelBox() for the actual
|
|
||||||
// work, so it basically just makes sure the input to this._addToPanelBox()
|
|
||||||
// is correct.
|
|
||||||
//
|
|
||||||
// Compared to panel_47.rc_2024-09-12.js: Nothing changed.
|
|
||||||
addToStatusArea(role, indicator, position, box) {
|
|
||||||
if (this.statusArea[role])
|
|
||||||
throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`);
|
|
||||||
|
|
||||||
if (!(indicator instanceof PanelMenu.Button))
|
|
||||||
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
||||||
|
|
||||||
position ??= 0;
|
|
||||||
let boxes = {
|
|
||||||
left: this._leftBox,
|
|
||||||
center: this._centerBox,
|
|
||||||
right: this._rightBox,
|
|
||||||
};
|
|
||||||
let boxContainer = boxes[box] || this._rightBox;
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of note:
|
|
||||||
// _onMenuSet(indicator) { [...] }
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
});
|
|
||||||
@ -1,321 +0,0 @@
|
|||||||
// My annotated and cut down js/ui/panel.js from gnome-shell/49.0.
|
|
||||||
// All annotations are what I guessed, interpreted and copied while reading the
|
|
||||||
// code and comparing to other panel.js versions and might be wrong. They are
|
|
||||||
// prefixed with "Annotation:" to indicate that they're my comments, not
|
|
||||||
// comments that orginally existed.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/49.0/js/ui/panel.js
|
|
||||||
// On: 2025-10-03
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// Taken from: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/49.0/js/ui/sessionMode.js
|
|
||||||
// On: 2025-10-03
|
|
||||||
// License: This code is licensed under GPLv2.
|
|
||||||
|
|
||||||
// I'm using the word "item" to refer to the thing, which gets added to the top
|
|
||||||
// (menu)bar / panel, where an item has a role/name and an indicator.
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import Clutter from 'gi://Clutter';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import GObject from 'gi://GObject';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import St from 'gi://St';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as PopupMenu from './popupMenu.js';
|
|
||||||
import * as PanelMenu from './panelMenu.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import * as Main from './main.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
import {DateMenuButton} from './dateMenu.js';
|
|
||||||
import {ATIndicator} from './status/accessibility.js';
|
|
||||||
import {InputSourceIndicator} from './status/keyboard.js';
|
|
||||||
import {DwellClickIndicator} from './status/dwellClick.js';
|
|
||||||
import {ScreenRecordingIndicator, ScreenSharingIndicator} from './status/remoteAccess.js';
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for this
|
|
||||||
// Extension.
|
|
||||||
|
|
||||||
// Of note (for PANEL_ITEM_IMPLEMENTATIONS):
|
|
||||||
// const ActivitiesButton = [...]
|
|
||||||
// const QuickSettings = [...]
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: AppMenuButton got removed.
|
|
||||||
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: AppMenuButton got removed.
|
|
||||||
const PANEL_ITEM_IMPLEMENTATIONS = {
|
|
||||||
'activities': ActivitiesButton,
|
|
||||||
'quickSettings': QuickSettings,
|
|
||||||
'dateMenu': DateMenuButton,
|
|
||||||
'a11y': ATIndicator,
|
|
||||||
'keyboard': InputSourceIndicator,
|
|
||||||
'dwellClick': DwellClickIndicator,
|
|
||||||
'screenRecording': ScreenRecordingIndicator,
|
|
||||||
'screenSharing': ScreenSharingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Panel = GObject.registerClass(
|
|
||||||
class Panel extends St.Widget {
|
|
||||||
// Annotation: Initializes the top (menu)bar / panel.
|
|
||||||
// Does relevant stuff like:
|
|
||||||
// - Defining this._leftBox, this._centerBox and this._rightBox.
|
|
||||||
// - Finally calling this._updatePanel().
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
name: 'panel',
|
|
||||||
reactive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
||||||
|
|
||||||
this._sessionStyle = null;
|
|
||||||
|
|
||||||
this.statusArea = {};
|
|
||||||
|
|
||||||
this.menuManager = new PopupMenu.PopupMenuManager(this);
|
|
||||||
|
|
||||||
this._leftBox = new St.BoxLayout({name: 'panelLeft'});
|
|
||||||
this.add_child(this._leftBox);
|
|
||||||
this._centerBox = new St.BoxLayout({name: 'panelCenter'});
|
|
||||||
this.add_child(this._centerBox);
|
|
||||||
this._rightBox = new St.BoxLayout({name: 'panelRight'});
|
|
||||||
this.add_child(this._rightBox);
|
|
||||||
|
|
||||||
this.connect('button-press-event', this._onButtonPress.bind(this));
|
|
||||||
this.connect('touch-event', this._onTouchEvent.bind(this));
|
|
||||||
|
|
||||||
Main.overview.connectObject('showing',
|
|
||||||
() => this.add_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
Main.overview.connectObject('hiding',
|
|
||||||
() => this.remove_style_pseudo_class('overview'),
|
|
||||||
this);
|
|
||||||
|
|
||||||
Main.layoutManager.panelBox.add_child(this);
|
|
||||||
Main.ctrlAltTabManager.addGroup(this,
|
|
||||||
_('Top Bar'), 'shell-focus-top-bar-symbolic',
|
|
||||||
{sortGroup: CtrlAltTab.SortGroup.TOP});
|
|
||||||
|
|
||||||
Main.sessionMode.connectObject('updated',
|
|
||||||
this._updatePanel.bind(this),
|
|
||||||
this);
|
|
||||||
|
|
||||||
global.display.connectObject('workareas-changed',
|
|
||||||
() => this.queue_relayout(),
|
|
||||||
this);
|
|
||||||
this._updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
|
|
||||||
// Annotation: Gets called by this._init() to populate the top (menu)bar /
|
|
||||||
// panel initially.
|
|
||||||
//
|
|
||||||
// It does the following relevant stuff:
|
|
||||||
// - Calls this._hideIndicators()
|
|
||||||
// - Calls this._updateBox() for this._leftBox, this._centerBox and
|
|
||||||
// this._rightBox with panel.left, panel.center and panel.right to
|
|
||||||
// populate the boxes with items defined in panel.left, panel.center and
|
|
||||||
// panel.right.
|
|
||||||
//
|
|
||||||
// panel.left, panel.center and panel.right get set via the line let panel
|
|
||||||
// = Main.sessionMode.panel, which uses the panel of Mains (js/ui/main.js)
|
|
||||||
// instance of SessionMode (js/ui/sessionMode.js).
|
|
||||||
//
|
|
||||||
// And in js/ui/sessionMode.js (49.0, 2025-10-03) you have different modes
|
|
||||||
// with different panel configuration. For example the "user" mode with:
|
|
||||||
// panel: {
|
|
||||||
// left: ['activities'],
|
|
||||||
// center: ['dateMenu'],
|
|
||||||
// right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'quickSettings'],
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This way this function populates the top (menu)bar / panel with the
|
|
||||||
// default stuff you see on a fresh Gnome.
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_updatePanel() {
|
|
||||||
let panel = Main.sessionMode.panel;
|
|
||||||
this._hideIndicators();
|
|
||||||
this._updateBox(panel.left, this._leftBox);
|
|
||||||
this._updateBox(panel.center, this._centerBox);
|
|
||||||
this._updateBox(panel.right, this._rightBox);
|
|
||||||
|
|
||||||
if (panel.left.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
|
|
||||||
else if (panel.right.includes('dateMenu'))
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
|
|
||||||
// Default to center if there is no dateMenu
|
|
||||||
else
|
|
||||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
|
||||||
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.remove_style_class_name(this._sessionStyle);
|
|
||||||
|
|
||||||
this._sessionStyle = Main.sessionMode.panelStyle;
|
|
||||||
if (this._sessionStyle)
|
|
||||||
this.add_style_class_name(this._sessionStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function hides all items, which are in the top (menu)bar
|
|
||||||
// panel and in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_hideIndicators() {
|
|
||||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator)
|
|
||||||
continue;
|
|
||||||
indicator.container.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a role (of an item) and returns a
|
|
||||||
// corresponding indicator, if either of two things are true:
|
|
||||||
// - The indicator is already in this.statusArea.
|
|
||||||
// Then it just returns the indicator by using this.statusArea.
|
|
||||||
// - The role is in PANEL_ITEM_IMPLEMENTATIONS.
|
|
||||||
// Then it creates a new indicator, adds it to this.statusArea and returns
|
|
||||||
// it.
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_ensureIndicator(role) {
|
|
||||||
let indicator = this.statusArea[role];
|
|
||||||
if (!indicator) {
|
|
||||||
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
|
|
||||||
if (!constructor) {
|
|
||||||
// This icon is not implemented (this is a bug)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
indicator = new constructor(this);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
}
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function takes a list of items (or rather their roles)
|
|
||||||
// and adds the indicators of those items to a box (like this._leftBox)
|
|
||||||
// using this._ensureIndicator() to get the indicator corresponding to the
|
|
||||||
// given role.
|
|
||||||
// So only items with roles this._ensureIndicator() knows, get added.
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_updateBox(elements, box) {
|
|
||||||
let nChildren = box.get_n_children();
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let role = elements[i];
|
|
||||||
let indicator = this._ensureIndicator(role);
|
|
||||||
if (indicator == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this._addToPanelBox(role, indicator, i + nChildren, box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function adds the given item to the specified top
|
|
||||||
// (menu)bar / panel box and connects to "destroy" and "menu-set" events.
|
|
||||||
//
|
|
||||||
// It takes the following arguments:
|
|
||||||
// - role: The name of the item to add.
|
|
||||||
// - indicator: The indicator of the item to add.
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - this._leftBox
|
|
||||||
// - this._centerBox
|
|
||||||
// - this._rightBox
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
_addToPanelBox(role, indicator, position, box) {
|
|
||||||
let container = indicator.container;
|
|
||||||
container.show();
|
|
||||||
|
|
||||||
let parent = container.get_parent();
|
|
||||||
if (parent)
|
|
||||||
parent.remove_child(container);
|
|
||||||
|
|
||||||
|
|
||||||
box.insert_child_at_index(container, position);
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
let destroyId = indicator.connect('destroy', emitter => {
|
|
||||||
delete this.statusArea[role];
|
|
||||||
emitter.disconnect(destroyId);
|
|
||||||
});
|
|
||||||
indicator.connect('menu-set', this._onMenuSet.bind(this));
|
|
||||||
this._onMenuSet(indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotation: This function allows you to add an item to the top (menu)bar
|
|
||||||
// / panel.
|
|
||||||
// While per default it adds the item to the status area (the right box of
|
|
||||||
// the top bar), you can specify the box and add the item to any of the
|
|
||||||
// three boxes of the top bar.
|
|
||||||
// To add an item to the top bar, you need to give its role and indicator.
|
|
||||||
//
|
|
||||||
// This function takes the following arguments:
|
|
||||||
// - role: A name for the item to add.
|
|
||||||
// - indicator: The indicator for the item to add (must be an instance of
|
|
||||||
// PanelMenu.Button).
|
|
||||||
// - position: Where in the box to add the item.
|
|
||||||
// - box: The box to add the item to.
|
|
||||||
// Can be one of the following:
|
|
||||||
// - "left": referring to this._leftBox
|
|
||||||
// - "center": referring to this._centerBox
|
|
||||||
// - "right": referring to this._rightBox
|
|
||||||
// These boxes are what you see in the top bar as the left, right and
|
|
||||||
// center sections.
|
|
||||||
//
|
|
||||||
// Finally this function just calls this._addToPanelBox() for the actual
|
|
||||||
// work, so it basically just makes sure the input to this._addToPanelBox()
|
|
||||||
// is correct.
|
|
||||||
//
|
|
||||||
// Compared to panel_48.2_2025-06-08.js: Nothing changed.
|
|
||||||
addToStatusArea(role, indicator, position, box) {
|
|
||||||
if (this.statusArea[role])
|
|
||||||
throw new Error(`Extension point conflict: there is already a status indicator for role ${role}`);
|
|
||||||
|
|
||||||
if (!(indicator instanceof PanelMenu.Button))
|
|
||||||
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
||||||
|
|
||||||
position ??= 0;
|
|
||||||
let boxes = {
|
|
||||||
left: this._leftBox,
|
|
||||||
center: this._centerBox,
|
|
||||||
right: this._rightBox,
|
|
||||||
};
|
|
||||||
let boxContainer = boxes[box] || this._rightBox;
|
|
||||||
this.statusArea[role] = indicator;
|
|
||||||
this._addToPanelBox(role, indicator, position, boxContainer);
|
|
||||||
return indicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of note:
|
|
||||||
// _onMenuSet(indicator) { [...] }
|
|
||||||
|
|
||||||
// Annotation: [...] Cut out bunch of stuff here, which isn't relevant for
|
|
||||||
// this Extension.
|
|
||||||
});
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
Top Bar Organizer vX includes the following changes:
|
|
||||||
|
|
||||||
# Relevant and/or Breaking Changes
|
|
||||||
|
|
||||||
The following relevant and/or breaking changes of this version:
|
|
||||||
|
|
||||||
## Breaking Change
|
|
||||||
|
|
||||||
Description
|
|
||||||
|
|
||||||
## Relevant Change
|
|
||||||
|
|
||||||
Description
|
|
||||||
|
|
||||||
# Other Changes
|
|
||||||
|
|
||||||
- a change
|
|
||||||
- another change
|
|
||||||
|
|
||||||
# `git shortlog`
|
|
||||||
|
|
||||||
The git shortlog for this version:
|
|
||||||
|
|
||||||
```
|
|
||||||
git shortlog vX-1..vX
|
|
||||||
```
|
|
||||||
@ -1,9 +1,13 @@
|
|||||||
Top Bar Organizer vX
|
Top Bar Organizer v1 includes the following changes:
|
||||||
|
|
||||||
Relevant and/or Breaking Changes:
|
# Relevant and/or Breaking Changes
|
||||||
- breaking change: description
|
|
||||||
- relevant change: description
|
|
||||||
|
|
||||||
Other Changes:
|
The following relevant and/or breaking changes of this version:
|
||||||
- a change
|
|
||||||
- another change
|
<list relevant and/or breaking changes of this version here>
|
||||||
|
|
||||||
|
# `git shortlog`
|
||||||
|
|
||||||
|
The git shortlog for this version:
|
||||||
|
|
||||||
|
<paste the git shortlog for this version here>
|
||||||
|
|||||||
1985
package-lock.json
generated
1985
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -2,23 +2,16 @@
|
|||||||
"name": "top-bar-organizer",
|
"name": "top-bar-organizer",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A Gnome Shell Extension for organizing your Gnome Shell top bar.",
|
"description": "A Gnome Shell Extension for organizing your Gnome Shell top bar.",
|
||||||
"type": "module",
|
|
||||||
"directories": {
|
"directories": {
|
||||||
"doc": "docs"
|
"doc": "docs"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@gitlab.gnome.org:june/top-bar-organizer.git"
|
"url": "git@gitlab.gnome.org:julianschacher/top-bar-organizer.git"
|
||||||
},
|
},
|
||||||
"author": "June",
|
"author": "Julian Schacher",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.32.0"
|
||||||
"eslint-plugin-jsdoc": "^50.7.1",
|
|
||||||
"typescript": "^5.8.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@girs/gjs": "^4.0.0-beta.23",
|
|
||||||
"@girs/gnome-shell": "^48.0.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,9 @@ set -e
|
|||||||
|
|
||||||
REAL_BASE_DIR=$( dirname $( readlink -f "$0" ))
|
REAL_BASE_DIR=$( dirname $( readlink -f "$0" ))
|
||||||
|
|
||||||
rm -rf "$REAL_BASE_DIR/dist"
|
gnome-extensions pack "$REAL_BASE_DIR/src" \
|
||||||
cd "$REAL_BASE_DIR"
|
|
||||||
npx tsc
|
|
||||||
cp "$REAL_BASE_DIR/src/metadata.json" "$REAL_BASE_DIR/dist/metadata.json"
|
|
||||||
gnome-extensions pack "$REAL_BASE_DIR/dist" \
|
|
||||||
--force \
|
--force \
|
||||||
--extra-source extensionModules \
|
--extra-source extensionModules \
|
||||||
--extra-source prefsModules \
|
--extra-source prefsModules \
|
||||||
--extra-source ../data/ui \
|
--extra-source ../data/ui \
|
||||||
--extra-source ../data/css \
|
|
||||||
--schema ../data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml
|
--schema ../data/org.gnome.shell.extensions.top-bar-organizer.gschema.xml
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB |
@ -1,65 +1,52 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
/* exported init */
|
||||||
|
|
||||||
import St from "gi://St"
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
import type Gio from "gi://Gio"
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
const Main = imports.ui.main;
|
||||||
import * as Panel from "resource:///org/gnome/shell/ui/panel.js";
|
const Panel = imports.ui.panel;
|
||||||
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
|
|
||||||
|
|
||||||
import BoxOrderManager from "./extensionModules/BoxOrderManager.js";
|
const BoxOrderManager = Me.imports.extensionModules.BoxOrderManager;
|
||||||
import type { Box } from "./extensionModules/BoxOrderManager.js";
|
|
||||||
|
|
||||||
export interface CustomPanel extends Panel.Panel {
|
class Extension {
|
||||||
_leftBox: St.BoxLayout;
|
constructor() {
|
||||||
_centerBox: St.BoxLayout;
|
}
|
||||||
_rightBox: St.BoxLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TopBarOrganizerExtension extends Extension {
|
enable() {
|
||||||
_settings!: Gio.Settings;
|
this._settings = ExtensionUtils.getSettings();
|
||||||
_boxOrderManager!: BoxOrderManager;
|
|
||||||
_settingsHandlerIds!: number[];
|
|
||||||
|
|
||||||
enable(): void {
|
this._boxOrderManager = new BoxOrderManager.BoxOrderManager();
|
||||||
this._settings = this.getSettings();
|
|
||||||
|
|
||||||
this._boxOrderManager = new BoxOrderManager({}, this._settings);
|
|
||||||
|
|
||||||
/// Stuff to do on startup(extension enable).
|
/// Stuff to do on startup(extension enable).
|
||||||
// Initially handle new top bar items and order top bar boxes.
|
// Initially handle new top bar items and order top bar boxes.
|
||||||
this.#handleNewItemsAndOrderTopBar();
|
this.#handleNewItemsAndOrderTopBar();
|
||||||
|
|
||||||
// Overwrite the `Panel._addToPanelBox` method with one handling new
|
// Overwrite `Panel._addToPanelBox` method with one handling new items
|
||||||
// items.
|
// and also handle AppIndicators getting ready, to handle new items.
|
||||||
this.#overwritePanelAddToPanelBox();
|
this.#overwritePanelAddToPanelBox();
|
||||||
// Handle AppIndicators getting ready, to handle new AppIndicator items.
|
|
||||||
this._boxOrderManager.connect("appIndicatorReady", () => {
|
this._boxOrderManager.connect("appIndicatorReady", () => {
|
||||||
this.#handleNewItemsAndOrderTopBar();
|
this.#handleNewItemsAndOrderTopBar();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle changes of settings.
|
// Handle changes of configured box orders.
|
||||||
this._settingsHandlerIds = [];
|
this._settingsHandlerIds = [];
|
||||||
const addSettingsChangeHandler = (settingsName: string) => {
|
const addConfiguredBoxOrderChangeHandler = (box) => {
|
||||||
const handlerId = this._settings.connect(`changed::${settingsName}`, () => {
|
let handlerId = this._settings.connect(`changed::${box}-box-order`, () => {
|
||||||
this.#handleNewItemsAndOrderTopBar();
|
this.#handleNewItemsAndOrderTopBar();
|
||||||
});
|
});
|
||||||
this._settingsHandlerIds.push(handlerId);
|
this._settingsHandlerIds.push(handlerId);
|
||||||
};
|
};
|
||||||
addSettingsChangeHandler("left-box-order");
|
addConfiguredBoxOrderChangeHandler("left");
|
||||||
addSettingsChangeHandler("center-box-order");
|
addConfiguredBoxOrderChangeHandler("center");
|
||||||
addSettingsChangeHandler("right-box-order");
|
addConfiguredBoxOrderChangeHandler("right");
|
||||||
addSettingsChangeHandler("hide");
|
|
||||||
addSettingsChangeHandler("show");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disable(): void {
|
disable() {
|
||||||
// Revert the overwrite of `Panel._addToPanelBox`.
|
// Revert the overwrite of `Panel._addToPanelBox`.
|
||||||
// @ts-ignore
|
|
||||||
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
|
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
|
||||||
// Set `Panel._originalAddToPanelBox` to `undefined`.
|
// Set `Panel._originalAddToPanelBox` to `undefined`.
|
||||||
// @ts-ignore
|
Panel._originalAddToPanelBox = undefined;
|
||||||
Panel.Panel.prototype._originalAddToPanelBox = undefined;
|
|
||||||
|
|
||||||
// Disconnect signals.
|
// Disconnect signals.
|
||||||
for (const handlerId of this._settingsHandlerIds) {
|
for (const handlerId of this._settingsHandlerIds) {
|
||||||
@ -67,9 +54,7 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
}
|
}
|
||||||
this._boxOrderManager.disconnectSignals();
|
this._boxOrderManager.disconnectSignals();
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this._settings = null;
|
this._settings = null;
|
||||||
// @ts-ignore
|
|
||||||
this._boxOrderManager = null;
|
this._boxOrderManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +66,9 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
* Overwrite `Panel._addToPanelBox` with a custom method, which simply calls
|
* Overwrite `Panel._addToPanelBox` with a custom method, which simply calls
|
||||||
* the original one and handles new items and orders the top bar afterwards.
|
* the original one and handles new items and orders the top bar afterwards.
|
||||||
*/
|
*/
|
||||||
#overwritePanelAddToPanelBox(): void {
|
#overwritePanelAddToPanelBox() {
|
||||||
// Add the original `Panel._addToPanelBox` method as
|
// Add the original `Panel._addToPanelBox` method as
|
||||||
// `Panel._originalAddToPanelBox`.
|
// `Panel._originalAddToPanelBox`.
|
||||||
// @ts-ignore
|
|
||||||
Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox;
|
Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox;
|
||||||
|
|
||||||
const handleNewItemsAndOrderTopBar = () => {
|
const handleNewItemsAndOrderTopBar = () => {
|
||||||
@ -95,7 +79,6 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
Panel.Panel.prototype._addToPanelBox = function(role, indicator, position, box) {
|
Panel.Panel.prototype._addToPanelBox = function(role, indicator, position, box) {
|
||||||
// Simply call the original `_addToPanelBox` and order the top bar
|
// Simply call the original `_addToPanelBox` and order the top bar
|
||||||
// and handle new items afterwards.
|
// and handle new items afterwards.
|
||||||
// @ts-ignore
|
|
||||||
this._originalAddToPanelBox(role, indicator, position, box);
|
this._originalAddToPanelBox(role, indicator, position, box);
|
||||||
handleNewItemsAndOrderTopBar();
|
handleNewItemsAndOrderTopBar();
|
||||||
};
|
};
|
||||||
@ -108,39 +91,39 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
/**
|
/**
|
||||||
* This method orders the top bar items of the specified box according to
|
* This method orders the top bar items of the specified box according to
|
||||||
* the configured box orders.
|
* the configured box orders.
|
||||||
* @param {Box} box - The box to order.
|
* @param {string} box - The box to order.
|
||||||
*/
|
*/
|
||||||
#orderTopBarItems(box: Box): void {
|
#orderTopBarItems(box) {
|
||||||
// Only run, when the session mode is "user" or the parent session mode
|
// Only run, when in "user" session mode.
|
||||||
// is "user".
|
if(Main.sessionMode.currentMode !== "user") {
|
||||||
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the valid box order.
|
// Get the valid box order.
|
||||||
const validBoxOrder = this._boxOrderManager.getValidBoxOrder(box);
|
const validBoxOrder = this._boxOrderManager.createValidBoxOrder(box);
|
||||||
|
|
||||||
// Get the relevant box of `Main.panel`.
|
// Get the relevant box of `Main.panel`.
|
||||||
let panelBox = (Main.panel as CustomPanel)[`_${box}Box`];
|
let panelBox;
|
||||||
|
switch (box) {
|
||||||
|
case "left":
|
||||||
|
panelBox = Main.panel._leftBox;
|
||||||
|
break;
|
||||||
|
case "center":
|
||||||
|
panelBox = Main.panel._centerBox;
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
panelBox = Main.panel._rightBox;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/// Go through the items of the validBoxOrder and order the GNOME Shell
|
/// Go through the items (or rather their roles) of the validBoxOrder
|
||||||
/// top bar box accordingly.
|
/// and order the panelBox accordingly.
|
||||||
for (let i = 0; i < validBoxOrder.length; i++) {
|
for (let i = 0; i < validBoxOrder.length; i++) {
|
||||||
const item = validBoxOrder[i];
|
const role = validBoxOrder[i];
|
||||||
// Get the indicator container associated with the current role.
|
// Get the indicator container associated with the current role.
|
||||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container;
|
const associatedIndicatorContainer = Main.panel.statusArea[role].container;
|
||||||
if (!(associatedIndicatorContainer instanceof St.Bin)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save whether or not the indicator container is visible.
|
associatedIndicatorContainer.get_parent().remove_child(associatedIndicatorContainer);
|
||||||
const isVisible = associatedIndicatorContainer.visible;
|
|
||||||
|
|
||||||
const parent = associatedIndicatorContainer.get_parent();
|
|
||||||
if (parent !== null) {
|
|
||||||
parent.remove_child(associatedIndicatorContainer);
|
|
||||||
}
|
|
||||||
if (box === "right") {
|
if (box === "right") {
|
||||||
// If the target panel box is the right panel box, insert the
|
// If the target panel box is the right panel box, insert the
|
||||||
// indicator container at index `-1`, which just adds it to the
|
// indicator container at index `-1`, which just adds it to the
|
||||||
@ -158,19 +141,6 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
} else {
|
} else {
|
||||||
panelBox.insert_child_at_index(associatedIndicatorContainer, i);
|
panelBox.insert_child_at_index(associatedIndicatorContainer, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the indicator container...
|
|
||||||
// - ...if it wasn't visible before and the hide property of the
|
|
||||||
// item is "default".
|
|
||||||
// - if the hide property of the item is "hide".
|
|
||||||
// In all other cases have the item show.
|
|
||||||
// An e.g. screen recording indicator still wouldn't show tho, since
|
|
||||||
// this here acts on the indicator container, but a screen recording
|
|
||||||
// indicator is hidden on the indicator level.
|
|
||||||
if ((!isVisible && item.hide === "default") ||
|
|
||||||
item.hide === "hide") {
|
|
||||||
associatedIndicatorContainer.hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// To handle the case, where the box order got set to a permutation
|
// To handle the case, where the box order got set to a permutation
|
||||||
// of an outdated box order, it would be wise, if the caller updated the
|
// of an outdated box order, it would be wise, if the caller updated the
|
||||||
@ -181,10 +151,9 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
* This method handles all new items currently present in the top bar and
|
* This method handles all new items currently present in the top bar and
|
||||||
* orders the items of all top bar boxes.
|
* orders the items of all top bar boxes.
|
||||||
*/
|
*/
|
||||||
#handleNewItemsAndOrderTopBar(): void {
|
#handleNewItemsAndOrderTopBar() {
|
||||||
// Only run, when the session mode is "user" or the parent session mode
|
// Only run, when in "user" session mode.
|
||||||
// is "user".
|
if(Main.sessionMode.currentMode !== "user") {
|
||||||
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,3 +167,7 @@ export default class TopBarOrganizerExtension extends Extension {
|
|||||||
// top bar items at the beginning of this method, this isn't a concern.
|
// top bar items at the beginning of this method, this isn't a concern.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
return new Extension();
|
||||||
|
}
|
||||||
272
src/extensionModules/BoxOrderManager.js
Normal file
272
src/extensionModules/BoxOrderManager.js
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported BoxOrderManager */
|
||||||
|
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides methods get, set and interact with box orders, while
|
||||||
|
* taking over the work of translating between what is stored in settings and
|
||||||
|
* what is really useable by the other extension code.
|
||||||
|
* It's basically a heavy wrapper around the box orders stored in the settings.
|
||||||
|
*/
|
||||||
|
var BoxOrderManager = GObject.registerClass({
|
||||||
|
Signals: {
|
||||||
|
"appIndicatorReady": {}
|
||||||
|
}
|
||||||
|
}, class BoxOrderManager extends GObject.Object {
|
||||||
|
#appIndicatorReadyHandlerIdMap;
|
||||||
|
#appIndicatorItemApplicationRoleMap;
|
||||||
|
#settings;
|
||||||
|
|
||||||
|
constructor(params = {}) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
this.#appIndicatorReadyHandlerIdMap = new Map();
|
||||||
|
this.#appIndicatorItemApplicationRoleMap = new Map();
|
||||||
|
|
||||||
|
this.#settings = ExtensionUtils.getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an AppIndicator/KStatusNotifierItem item by associating the role
|
||||||
|
* of the given item with the application of the
|
||||||
|
* AppIndicator/KStatusNotifier item and returning a placeholder role.
|
||||||
|
* In the case, where the application can't be determined, this method
|
||||||
|
* throws an error. However it also makes sure that once the app indicators
|
||||||
|
* "ready" signal emits, this classes "appIndicatorReady" signal emits as
|
||||||
|
* well.
|
||||||
|
* @param {string} indicatorContainer - The container of the indicator of the
|
||||||
|
* AppIndicator/KStatusNotifierItem item.
|
||||||
|
* @param {string} role - The role of the AppIndicator/KStatusNotifierItem
|
||||||
|
* item.
|
||||||
|
* @returns {string} The placeholder role.
|
||||||
|
*/
|
||||||
|
#handleAppIndicatorItem(indicatorContainer, role) {
|
||||||
|
const appIndicator = indicatorContainer.get_child()._indicator;
|
||||||
|
let application = appIndicator.id;
|
||||||
|
|
||||||
|
if (!application && this.#appIndicatorReadyHandlerIdMap) {
|
||||||
|
const handlerId = appIndicator.connect("ready", () => {
|
||||||
|
this.emit("appIndicatorReady");
|
||||||
|
appIndicator.disconnect(handlerId);
|
||||||
|
this.#appIndicatorReadyHandlerIdMap.delete(handlerId);
|
||||||
|
});
|
||||||
|
this.#appIndicatorReadyHandlerIdMap.set(handlerId, appIndicator);
|
||||||
|
throw new Error("Application can't be determined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the Dropbox client appends its PID to the id, drop the PID and
|
||||||
|
// the hyphen before it.
|
||||||
|
if (application.startsWith("dropbox-client-")) {
|
||||||
|
application = "dropbox-client";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate the role with the application.
|
||||||
|
let roles = this.#appIndicatorItemApplicationRoleMap.get(application);
|
||||||
|
if (roles) {
|
||||||
|
// If the application already has an array of associated roles, just
|
||||||
|
// add the role to it, if needed.
|
||||||
|
if (!roles.includes(role)) {
|
||||||
|
roles.push(role);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise create a new array.
|
||||||
|
this.#appIndicatorItemApplicationRoleMap.set(application, [role]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the placeholder.
|
||||||
|
// A box order containing this placeholder can later be resolved to
|
||||||
|
// relevant roles using `#resolveAppIndicatorPlaceholders`.
|
||||||
|
return `appindicator-kstatusnotifieritem-${application}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a box order and replaces AppIndicator placeholder roles with
|
||||||
|
* actual roles.
|
||||||
|
* @param {string[]} - The box order of which to replace placeholder roles.
|
||||||
|
* @returns {string[]} - A box order with all placeholder roles
|
||||||
|
* resolved/replaced to/with actual roles.
|
||||||
|
*/
|
||||||
|
#resolveAppIndicatorPlaceholders(boxOrder) {
|
||||||
|
let resolvedBoxOrder = [];
|
||||||
|
for (const role of boxOrder) {
|
||||||
|
// If the role isn't a placeholder, just add it to the resolved box
|
||||||
|
// order.
|
||||||
|
if (!role.startsWith("appindicator-kstatusnotifieritem-")) {
|
||||||
|
resolvedBoxOrder.push(role);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the role is a placeholder, replace it.
|
||||||
|
// First get the application this placeholder is associated with.
|
||||||
|
const application = role.replace("appindicator-kstatusnotifieritem-", "");
|
||||||
|
|
||||||
|
// Then get the actual roles associated with this application.
|
||||||
|
let actualRoles = this.#appIndicatorItemApplicationRoleMap.get(application);
|
||||||
|
|
||||||
|
// If there are no actual roles, continue.
|
||||||
|
if (!actualRoles) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise add the actual roles to the resolved box order.
|
||||||
|
resolvedBoxOrder.push(...actualRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedBoxOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects all signals (and disables future signal connection).
|
||||||
|
* This is typically used before nulling an instance of this class to make
|
||||||
|
* sure all signals are disconnected.
|
||||||
|
*/
|
||||||
|
disconnectSignals() {
|
||||||
|
for (const [handlerId, appIndicator] of this.#appIndicatorReadyHandlerIdMap) {
|
||||||
|
if (handlerId && appIndicator?.signalHandlerIsConnected(handlerId)) {
|
||||||
|
appIndicator.disconnect(handlerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#appIndicatorReadyHandlerIdMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns a valid box order for the given top bar box.
|
||||||
|
* This means it returns a box order, where only roles are included, which
|
||||||
|
* have their associated indicator container already in some box of the
|
||||||
|
* Gnome Shell top bar.
|
||||||
|
* @param {string} box - The top bar box to return the valid box order for.
|
||||||
|
* Must be one of the following values:
|
||||||
|
* - "left"
|
||||||
|
* - "center"
|
||||||
|
* - "right"
|
||||||
|
* @returns {string[]} - The valid box order.
|
||||||
|
*/
|
||||||
|
createValidBoxOrder(box) {
|
||||||
|
// Get a resolved box order.
|
||||||
|
let boxOrder = this.#resolveAppIndicatorPlaceholders(this.#settings.get_strv(`${box}-box-order`));
|
||||||
|
|
||||||
|
// ToDo: simplify.
|
||||||
|
// Get the indicator containers (of the items) currently present in the
|
||||||
|
// Gnome Shell top bar.
|
||||||
|
const indicatorContainers = [
|
||||||
|
Main.panel._leftBox.get_children(),
|
||||||
|
Main.panel._centerBox.get_children(),
|
||||||
|
Main.panel._rightBox.get_children()
|
||||||
|
].flat();
|
||||||
|
|
||||||
|
// Create an indicator containers set from the indicator containers for
|
||||||
|
// fast easy access.
|
||||||
|
const indicatorContainerSet = new Set(indicatorContainers);
|
||||||
|
|
||||||
|
// Go through the box order and only add items to the valid box order,
|
||||||
|
// where their indicator is present in the Gnome Shell top bar
|
||||||
|
// currently.
|
||||||
|
let validBoxOrder = [];
|
||||||
|
for (const role of boxOrder) {
|
||||||
|
// Get the indicator container associated with the current role.
|
||||||
|
const associatedIndicatorContainer = Main.panel.statusArea[role]?.container;
|
||||||
|
|
||||||
|
if (indicatorContainerSet.has(associatedIndicatorContainer)) {
|
||||||
|
validBoxOrder.push(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validBoxOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves all new items currently present in the Gnome Shell top
|
||||||
|
* bar to the correct box orders.
|
||||||
|
*/
|
||||||
|
saveNewTopBarItems() {
|
||||||
|
// Only run, when in "user" session mode.
|
||||||
|
if(Main.sessionMode.currentMode !== "user") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the configured box orders from settings.
|
||||||
|
const boxOrders = {
|
||||||
|
left: this.#settings.get_strv("left-box-order"),
|
||||||
|
center: this.#settings.get_strv("center-box-order"),
|
||||||
|
right: this.#settings.get_strv("right-box-order"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get roles (of items) currently present in the Gnome Shell top bar and
|
||||||
|
// index them using their associated indicator container.
|
||||||
|
let indicatorContainerRoleMap = new Map();
|
||||||
|
for (const role in Main.panel.statusArea) {
|
||||||
|
indicatorContainerRoleMap.set(Main.panel.statusArea[role].container, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the indicator containers (of the items) currently present in the
|
||||||
|
// Gnome Shell top bar boxes.
|
||||||
|
const boxIndicatorContainers = {
|
||||||
|
left: Main.panel._leftBox.get_children(),
|
||||||
|
center: Main.panel._centerBox.get_children(),
|
||||||
|
// Reverse this array, since the items in the left and center box
|
||||||
|
// are logically LTR, while the items in the right box are RTL.
|
||||||
|
right: Main.panel._rightBox.get_children().reverse()
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function goes through the indicator containers of the given box
|
||||||
|
// and adds roles of new items to the box order.
|
||||||
|
const addNewItemsToBoxOrder = (indicatorContainers, boxOrder, box) => {
|
||||||
|
for (const indicatorContainer of indicatorContainers) {
|
||||||
|
// First get the role associated with the current indicator
|
||||||
|
// container.
|
||||||
|
let role = indicatorContainerRoleMap.get(indicatorContainer);
|
||||||
|
if (!role) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle an AppIndicator/KStatusNotifierItem item differently.
|
||||||
|
if (role.startsWith("appindicator-")) {
|
||||||
|
try {
|
||||||
|
role = this.#handleAppIndicatorItem(indicatorContainer, role);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message !== "Application can't be determined.") {
|
||||||
|
throw(e);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the role to the box order, if it isn't in in one already.
|
||||||
|
if (!boxOrders.left.includes(role)
|
||||||
|
&& !boxOrders.center.includes(role)
|
||||||
|
&& !boxOrders.right.includes(role)) {
|
||||||
|
if (box === "right") {
|
||||||
|
// Add the items to the beginning for this array, since
|
||||||
|
// its RTL.
|
||||||
|
boxOrder.unshift(role);
|
||||||
|
} else {
|
||||||
|
boxOrder.push(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addNewItemsToBoxOrder(boxIndicatorContainers.left, boxOrders.left, "left");
|
||||||
|
addNewItemsToBoxOrder(boxIndicatorContainers.center, boxOrders.center, "center");
|
||||||
|
addNewItemsToBoxOrder(boxIndicatorContainers.right, boxOrders.right, "right");
|
||||||
|
|
||||||
|
// This function saves the given box order to settings.
|
||||||
|
const saveBoxOrderToSettings = (boxOrder, box) => {
|
||||||
|
const currentBoxOrder = this.#settings.get_strv(`${box}-box-order`);
|
||||||
|
// Only save the updated box order to settings, if it is different,
|
||||||
|
// to avoid loops, when listening on settings changes.
|
||||||
|
if (JSON.stringify(currentBoxOrder) !== JSON.stringify(boxOrder)) {
|
||||||
|
this.#settings.set_strv(`${box}-box-order`, boxOrder);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
saveBoxOrderToSettings(boxOrders.left, "left");
|
||||||
|
saveBoxOrderToSettings(boxOrders.center, "center");
|
||||||
|
saveBoxOrderToSettings(boxOrders.right, "right");
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,388 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import St from "gi://St";
|
|
||||||
import type Gio from "gi://Gio";
|
|
||||||
|
|
||||||
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
|
||||||
|
|
||||||
import type { CustomPanel } from "../extension.js"
|
|
||||||
|
|
||||||
export type Box = "left" | "center" | "right";
|
|
||||||
type Hide = "hide" | "show" | "default";
|
|
||||||
/**
|
|
||||||
* A resolved box order item containing the items role, settings identifier and
|
|
||||||
* additional information.
|
|
||||||
*/
|
|
||||||
interface ResolvedBoxOrderItem {
|
|
||||||
settingsId: string // The settings identifier of the item.
|
|
||||||
role: string // The role of the item.
|
|
||||||
hide: Hide // Whether the item should be (forcefully) hidden, (forcefully) shown or just be left as is.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides an interfaces to the box orders stored in settings.
|
|
||||||
* It takes care of handling AppIndicator and Task Up UltraLite items and
|
|
||||||
* resolving from the internal item settings identifiers to roles.
|
|
||||||
* In the end this results in convenient functions, which are directly useful in
|
|
||||||
* other extension code.
|
|
||||||
*/
|
|
||||||
export default class BoxOrderManager extends GObject.Object {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
Signals: {
|
|
||||||
"appIndicatorReady": {},
|
|
||||||
},
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't have type guarantees here, since this is working with types from
|
|
||||||
// the KStatusNotifier/AppIndicator extension.
|
|
||||||
#appIndicatorReadyHandlerIdMap: Map<any, any>;
|
|
||||||
#appIndicatorItemSettingsIdToRolesMap: Map<string, string[]>;
|
|
||||||
#taskUpUltraLiteItemRoles: string[];
|
|
||||||
#settings: Gio.Settings;
|
|
||||||
|
|
||||||
constructor(params = {}, settings: Gio.Settings) {
|
|
||||||
// @ts-ignore Params should be passed, see: https://gjs.guide/guides/gobject/subclassing.html#subclassing-gobject
|
|
||||||
super(params);
|
|
||||||
|
|
||||||
this.#appIndicatorReadyHandlerIdMap = new Map();
|
|
||||||
this.#appIndicatorItemSettingsIdToRolesMap = new Map();
|
|
||||||
this.#taskUpUltraLiteItemRoles = [];
|
|
||||||
|
|
||||||
this.#settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a box order for the given top bar box from settings.
|
|
||||||
* @param {Box} box - The top bar box for which to get the box order.
|
|
||||||
* @returns {string[]} - The box order consisting of an array of item
|
|
||||||
* settings identifiers.
|
|
||||||
*/
|
|
||||||
#getBoxOrder(box: Box): string[] {
|
|
||||||
return this.#settings.get_strv(`${box}-box-order`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the given box order to settings, making sure to only save a changed
|
|
||||||
* box order, to avoid loops when listening on settings changes.
|
|
||||||
* @param {Box} box - The top bar box for which to save the box order.
|
|
||||||
* @param {string[]} boxOrder - The box order to save. Must be an array of
|
|
||||||
* item settings identifiers.
|
|
||||||
*/
|
|
||||||
#saveBoxOrder(box: Box, boxOrder: string[]): void {
|
|
||||||
const currentBoxOrder = this.#getBoxOrder(box);
|
|
||||||
|
|
||||||
// Only save the given box order to settings, if it is different, to
|
|
||||||
// avoid loops when listening on settings changes.
|
|
||||||
if (JSON.stringify(boxOrder) !== JSON.stringify(currentBoxOrder)) {
|
|
||||||
this.#settings.set_strv(`${box}-box-order`, boxOrder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles an AppIndicator/KStatusNotifierItem item by deriving a settings
|
|
||||||
* identifier and then associating the role of the given item to the items
|
|
||||||
* settings identifier.
|
|
||||||
* It then returns the derived settings identifier.
|
|
||||||
* In the case, where the settings identifier can't be derived, because the
|
|
||||||
* application can't be determined, this method throws an error. However it
|
|
||||||
* then also makes sure that once the app indicators "ready" signal emits,
|
|
||||||
* this classes "appIndicatorReady" signal emits as well, such that it and
|
|
||||||
* other methods can be called again to properly handle the item.
|
|
||||||
* @param {St.Bin} indicatorContainer - The container of the indicator of the
|
|
||||||
* AppIndicator/KStatusNotifierItem item.
|
|
||||||
* @param {string} role - The role of the AppIndicator/KStatusNotifierItem
|
|
||||||
* item.
|
|
||||||
* @returns {string} The derived items settings identifier.
|
|
||||||
*/
|
|
||||||
#handleAppIndicatorItem(indicatorContainer: St.Bin, role: string): string {
|
|
||||||
// Since this is working with types from the
|
|
||||||
// AppIndicator/KStatusNotifierItem extension, we loose a bunch of type
|
|
||||||
// safety here.
|
|
||||||
// https://github.com/ubuntu/gnome-shell-extension-appindicator
|
|
||||||
const appIndicator = (indicatorContainer.get_child() as any)._indicator;
|
|
||||||
let application = appIndicator.id;
|
|
||||||
|
|
||||||
if (!application && this.#appIndicatorReadyHandlerIdMap) {
|
|
||||||
const handlerId = appIndicator.connect("ready", () => {
|
|
||||||
this.emit("appIndicatorReady");
|
|
||||||
appIndicator.disconnect(handlerId);
|
|
||||||
this.#appIndicatorReadyHandlerIdMap.delete(handlerId);
|
|
||||||
});
|
|
||||||
this.#appIndicatorReadyHandlerIdMap.set(handlerId, appIndicator);
|
|
||||||
throw new Error("Application can't be determined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the Dropbox client appends its PID to the id, drop the PID and
|
|
||||||
// the hyphen before it.
|
|
||||||
if (application.startsWith("dropbox-client-")) {
|
|
||||||
application = "dropbox-client";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derive the items settings identifier from the application name.
|
|
||||||
const itemSettingsId = `appindicator-kstatusnotifieritem-${application}`;
|
|
||||||
|
|
||||||
// Associate the role with the items settings identifier.
|
|
||||||
let roles = this.#appIndicatorItemSettingsIdToRolesMap.get(itemSettingsId);
|
|
||||||
if (roles) {
|
|
||||||
// If the settings identifier already has an array of associated
|
|
||||||
// roles, just add the role to it, if needed.
|
|
||||||
if (!roles.includes(role)) {
|
|
||||||
roles.push(role);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise create a new array.
|
|
||||||
this.#appIndicatorItemSettingsIdToRolesMap.set(itemSettingsId, [role]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the item settings identifier.
|
|
||||||
return itemSettingsId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a Task Up UltraLite item by storing its role and returning the
|
|
||||||
* Task Up UltraLite settings identifier.
|
|
||||||
* This is needed since the Task Up UltraLite extension creates a bunch of
|
|
||||||
* top bar items as part of its functionality, so we want to group them
|
|
||||||
* under one identifier in the settings.
|
|
||||||
* https://extensions.gnome.org/extension/7700/task-up-ultralite/
|
|
||||||
* @param {string} role - The role of the Task Up UltraLite item.
|
|
||||||
* @returns {string} The settings identifier to use.
|
|
||||||
*/
|
|
||||||
#handleTaskUpUltraLiteItem(role: string): string {
|
|
||||||
const roles = this.#taskUpUltraLiteItemRoles;
|
|
||||||
|
|
||||||
if (!roles.includes(role)) {
|
|
||||||
roles.push(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "item-role-group-task-up-ultralite";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a resolved box order for the given top bar box, where all
|
|
||||||
* AppIndicator and Task Up UltraLite items got resolved using their roles,
|
|
||||||
* meaning they might be present multiple times or not at all depending on
|
|
||||||
* the roles stored.
|
|
||||||
* The items of the box order also have additional information stored.
|
|
||||||
* @param {Box} box - The top bar box for which to get the resolved box order.
|
|
||||||
* @returns {ResolvedBoxOrderItem[]} - The resolved box order.
|
|
||||||
*/
|
|
||||||
#getResolvedBoxOrder(box: Box): ResolvedBoxOrderItem[] {
|
|
||||||
let boxOrder = this.#getBoxOrder(box);
|
|
||||||
|
|
||||||
const itemsToHide = this.#settings.get_strv("hide");
|
|
||||||
const itemsToShow = this.#settings.get_strv("show");
|
|
||||||
|
|
||||||
let resolvedBoxOrder = [];
|
|
||||||
for (const itemSettingsId of boxOrder) {
|
|
||||||
const resolvedBoxOrderItem = {
|
|
||||||
settingsId: itemSettingsId,
|
|
||||||
role: "",
|
|
||||||
hide: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the hide state of the item.
|
|
||||||
if (itemsToHide.includes(resolvedBoxOrderItem.settingsId)) {
|
|
||||||
resolvedBoxOrderItem.hide = "hide";
|
|
||||||
} else if (itemsToShow.includes(resolvedBoxOrderItem.settingsId)) {
|
|
||||||
resolvedBoxOrderItem.hide = "show";
|
|
||||||
} else {
|
|
||||||
resolvedBoxOrderItem.hide = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the items settings identifier doesn't indicate that the item
|
|
||||||
// is an AppIndicator/KStatusNotifierItem item or the Task Up
|
|
||||||
// UltraLite item role group, then its identifier is the role and it
|
|
||||||
// can just be added to the resolved box order.
|
|
||||||
if (!itemSettingsId.startsWith("appindicator-kstatusnotifieritem-") &&
|
|
||||||
itemSettingsId !== "item-role-group-task-up-ultralite") {
|
|
||||||
resolvedBoxOrderItem.role = resolvedBoxOrderItem.settingsId;
|
|
||||||
resolvedBoxOrder.push(resolvedBoxOrderItem);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the items settings identifier indicates otherwise, then handle
|
|
||||||
// the item specially.
|
|
||||||
|
|
||||||
// Get the roles associated with the items settings id.
|
|
||||||
let roles: string[] = [];
|
|
||||||
if (itemSettingsId.startsWith("appindicator-kstatusnotifieritem-")) {
|
|
||||||
roles = this.#appIndicatorItemSettingsIdToRolesMap.get(resolvedBoxOrderItem.settingsId) ?? [];
|
|
||||||
} else if (itemSettingsId === "item-role-group-task-up-ultralite") {
|
|
||||||
roles = this.#taskUpUltraLiteItemRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new resolved box order item for each role and add it to
|
|
||||||
// the resolved box order.
|
|
||||||
for (const role of roles) {
|
|
||||||
const newResolvedBoxOrderItem = JSON.parse(JSON.stringify(resolvedBoxOrderItem));
|
|
||||||
newResolvedBoxOrderItem.role = role;
|
|
||||||
resolvedBoxOrder.push(newResolvedBoxOrderItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvedBoxOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects all signals (and disables future signal connection).
|
|
||||||
* This is typically used before nulling an instance of this class to make
|
|
||||||
* sure all signals are disconnected.
|
|
||||||
*/
|
|
||||||
disconnectSignals(): void {
|
|
||||||
for (const [handlerId, appIndicator] of this.#appIndicatorReadyHandlerIdMap) {
|
|
||||||
if (handlerId && appIndicator?.signalHandlerIsConnected(handlerId)) {
|
|
||||||
appIndicator.disconnect(handlerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
this.#appIndicatorReadyHandlerIdMap = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a valid box order for the given top bar box, where all AppIndicator
|
|
||||||
* and Task Up UltraLite items got resolved and where only items are
|
|
||||||
* included, which are in some GNOME Shell top bar box.
|
|
||||||
* The items of the box order also have additional information stored.
|
|
||||||
* @param {Box} box - The top bar box to return the valid box order for.
|
|
||||||
* @returns {ResolvedBoxOrderItem[]} - The valid box order.
|
|
||||||
*/
|
|
||||||
getValidBoxOrder(box: Box): ResolvedBoxOrderItem[] {
|
|
||||||
// Get a resolved box order.
|
|
||||||
let resolvedBoxOrder = this.#getResolvedBoxOrder(box);
|
|
||||||
|
|
||||||
// Get the indicator containers (of the items) currently present in the
|
|
||||||
// GNOME Shell top bar.
|
|
||||||
// They should be St.Bins (see link), so ensure that using a filter.
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panelMenu.js?ref_type=tags#L21
|
|
||||||
const indicatorContainers = new Set([
|
|
||||||
(Main.panel as CustomPanel)._leftBox.get_children(),
|
|
||||||
(Main.panel as CustomPanel)._centerBox.get_children(),
|
|
||||||
(Main.panel as CustomPanel)._rightBox.get_children(),
|
|
||||||
].flat().filter(ic => ic instanceof St.Bin));
|
|
||||||
|
|
||||||
// Go through the resolved box order and only add items to the valid box
|
|
||||||
// order, where their indicator is currently present in the GNOME Shell
|
|
||||||
// top bar.
|
|
||||||
let validBoxOrder: ResolvedBoxOrderItem[] = [];
|
|
||||||
for (const item of resolvedBoxOrder) {
|
|
||||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container;
|
|
||||||
if (!(associatedIndicatorContainer instanceof St.Bin)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indicatorContainers.has(associatedIndicatorContainer)) {
|
|
||||||
validBoxOrder.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return validBoxOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method saves all new items currently present in the GNOME Shell top
|
|
||||||
* bar to the settings.
|
|
||||||
*/
|
|
||||||
saveNewTopBarItems(): void {
|
|
||||||
// Only run, when the session mode is "user" or the parent session mode
|
|
||||||
// is "user".
|
|
||||||
if (Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the box orders.
|
|
||||||
const boxOrders = {
|
|
||||||
left: this.#getBoxOrder("left"),
|
|
||||||
center: this.#getBoxOrder("center"),
|
|
||||||
right: this.#getBoxOrder("right"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get roles (of items) currently present in the GNOME Shell top bar and
|
|
||||||
// index them using their associated indicator container.
|
|
||||||
let indicatorContainerRoleMap = new Map<St.Bin, string>();
|
|
||||||
for (const role in (Main.panel.statusArea as any)) {
|
|
||||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[role]?.container;
|
|
||||||
if (!(associatedIndicatorContainer instanceof St.Bin)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
indicatorContainerRoleMap.set(associatedIndicatorContainer, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the indicator containers (of the items) currently present in the
|
|
||||||
// GNOME Shell top bar boxes.
|
|
||||||
// They should be St.Bins (see link), so ensure that using a filter.
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panelMenu.js?ref_type=tags#L21
|
|
||||||
const boxIndicatorContainers = {
|
|
||||||
left: (Main.panel as CustomPanel)._leftBox.get_children().filter(ic => ic instanceof St.Bin),
|
|
||||||
center: (Main.panel as CustomPanel)._centerBox.get_children().filter(ic => ic instanceof St.Bin),
|
|
||||||
// Reverse this array, since the items in the left and center box
|
|
||||||
// are logically LTR, while the items in the right box are RTL.
|
|
||||||
right: (Main.panel as CustomPanel)._rightBox.get_children().filter(ic => ic instanceof St.Bin).reverse(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function goes through the indicator containers of the given box
|
|
||||||
// and adds new item settings identifiers to the given box order.
|
|
||||||
const addNewItemSettingsIdsToBoxOrder = (indicatorContainers: St.Bin[], boxOrder: string[], box: Box) => {
|
|
||||||
for (const indicatorContainer of indicatorContainers) {
|
|
||||||
// First get the role associated with the current indicator
|
|
||||||
// container.
|
|
||||||
let role = indicatorContainerRoleMap.get(indicatorContainer);
|
|
||||||
if (!role) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then get a settings identifier for the item.
|
|
||||||
let itemSettingsId;
|
|
||||||
if (role.startsWith("appindicator-")) {
|
|
||||||
// If the role indicates that the item is an
|
|
||||||
// AppIndicator/KStatusNotifierItem item, then handle it
|
|
||||||
// differently.
|
|
||||||
try {
|
|
||||||
itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role);
|
|
||||||
} catch (e) {
|
|
||||||
if (!(e instanceof Error)) {
|
|
||||||
throw(e);
|
|
||||||
}
|
|
||||||
if (e.message !== "Application can't be determined.") {
|
|
||||||
throw(e);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (role.startsWith("task-button-")) {
|
|
||||||
// If the role indicates that the item is a Task Up
|
|
||||||
// UltraLite item, then handle it differently.
|
|
||||||
itemSettingsId = this.#handleTaskUpUltraLiteItem(role);
|
|
||||||
} else { // Otherwise just use the role as the settings identifier.
|
|
||||||
itemSettingsId = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the items settings identifier to the box order, if it
|
|
||||||
// isn't in in one already.
|
|
||||||
if (!boxOrders.left.includes(itemSettingsId)
|
|
||||||
&& !boxOrders.center.includes(itemSettingsId)
|
|
||||||
&& !boxOrders.right.includes(itemSettingsId)) {
|
|
||||||
if (box === "right") {
|
|
||||||
// Add the items to the beginning for this array, since
|
|
||||||
// its RTL.
|
|
||||||
boxOrder.unshift(itemSettingsId);
|
|
||||||
} else {
|
|
||||||
boxOrder.push(itemSettingsId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.left, boxOrders.left, "left");
|
|
||||||
addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.center, boxOrders.center, "center");
|
|
||||||
addNewItemSettingsIdsToBoxOrder(boxIndicatorContainers.right, boxOrders.right, "right");
|
|
||||||
|
|
||||||
this.#saveBoxOrder("left", boxOrders.left);
|
|
||||||
this.#saveBoxOrder("center", boxOrders.center);
|
|
||||||
this.#saveBoxOrder("right", boxOrders.right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,8 +2,8 @@
|
|||||||
"uuid": "top-bar-organizer@julian.gse.jsts.xyz",
|
"uuid": "top-bar-organizer@julian.gse.jsts.xyz",
|
||||||
"name": "Top Bar Organizer",
|
"name": "Top Bar Organizer",
|
||||||
"description": "Organize the items of the top (menu)bar.",
|
"description": "Organize the items of the top (menu)bar.",
|
||||||
"version": 15,
|
"version": 6,
|
||||||
"shell-version": [ "45", "46", "47", "48", "49" ],
|
"shell-version": [ "42", "43" ],
|
||||||
"settings-schema": "org.gnome.shell.extensions.top-bar-organizer",
|
"settings-schema": "org.gnome.shell.extensions.top-bar-organizer",
|
||||||
"url": "https://gitlab.gnome.org/june/top-bar-organizer"
|
"url": "https://gitlab.gnome.org/julianschacher/top-bar-organizer"
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/prefs.js
Normal file
14
src/prefs.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported buildPrefsWidget, init */
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
const PrefsPage = Me.imports.prefsModules.PrefsPage;
|
||||||
|
|
||||||
|
function buildPrefsWidget() {
|
||||||
|
return new PrefsPage.PrefsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
}
|
||||||
32
src/prefs.ts
32
src/prefs.ts
@ -1,32 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Gtk from "gi://Gtk";
|
|
||||||
import Gdk from "gi://Gdk";
|
|
||||||
|
|
||||||
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
||||||
|
|
||||||
import PrefsPage from "./prefsModules/PrefsPage.js";
|
|
||||||
|
|
||||||
export default class TopBarOrganizerPreferences extends ExtensionPreferences {
|
|
||||||
getPreferencesWidget() {
|
|
||||||
const provider = new Gtk.CssProvider();
|
|
||||||
provider.load_from_path(this.metadata.dir.get_path() + "/css/prefs.css");
|
|
||||||
const defaultGdkDisplay = Gdk.Display.get_default();
|
|
||||||
Gtk.StyleContext.add_provider_for_display(
|
|
||||||
(defaultGdkDisplay as Gdk.Display),
|
|
||||||
provider,
|
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
|
||||||
);
|
|
||||||
|
|
||||||
const prefsPage = new PrefsPage();
|
|
||||||
|
|
||||||
prefsPage.connect("destroy", () => {
|
|
||||||
Gtk.StyleContext.remove_provider_for_display(
|
|
||||||
(defaultGdkDisplay as Gdk.Display),
|
|
||||||
provider
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return prefsPage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import Adw from "gi://Adw";
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
import type Gio from "gi://Gio";
|
|
||||||
import type Gtk from "gi://Gtk";
|
|
||||||
|
|
||||||
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
||||||
|
|
||||||
export default class PrefsBoxOrderItemOptionsDialog extends Adw.Dialog {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
GTypeName: "PrefsBoxOrderItemOptionsDialog",
|
|
||||||
Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-item-options-dialog.ui", GLib.UriFlags.NONE),
|
|
||||||
InternalChildren: [
|
|
||||||
"visibility-row",
|
|
||||||
],
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare _visibility_row: Adw.ComboRow;
|
|
||||||
#settings: Gio.Settings;
|
|
||||||
item: string;
|
|
||||||
|
|
||||||
constructor(params = {}, item: string) {
|
|
||||||
super(params);
|
|
||||||
|
|
||||||
// Associate `this` with an item.
|
|
||||||
this.item = item;
|
|
||||||
// Load the settings.
|
|
||||||
this.#settings = ExtensionPreferences.lookupByURL(import.meta.url)!.getSettings();
|
|
||||||
|
|
||||||
// Set the selected visibility row choice to the settings value.
|
|
||||||
const itemsToHide = new Set(this.#settings.get_strv("hide"));
|
|
||||||
const itemsToShow = new Set(this.#settings.get_strv("show"));
|
|
||||||
if (itemsToHide.has(this.item)) {
|
|
||||||
this._visibility_row.set_selected(1);
|
|
||||||
} else if (itemsToShow.has(this.item)) {
|
|
||||||
this._visibility_row.set_selected(2);
|
|
||||||
} else {
|
|
||||||
this._visibility_row.set_selected(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibilityRowSelectionChanged(): void {
|
|
||||||
const visibility = (this._visibility_row.get_selected_item() as Gtk.StringObject).get_string();
|
|
||||||
const itemsToHide = new Set(this.#settings.get_strv("hide"));
|
|
||||||
const itemsToShow = new Set(this.#settings.get_strv("show"));
|
|
||||||
|
|
||||||
switch (visibility) {
|
|
||||||
case "Forcefully Hide":
|
|
||||||
itemsToHide.add(this.item)
|
|
||||||
itemsToShow.delete(this.item);
|
|
||||||
break;
|
|
||||||
case "Forcefully Show":
|
|
||||||
itemsToHide.delete(this.item)
|
|
||||||
itemsToShow.add(this.item);
|
|
||||||
break;
|
|
||||||
case "Default":
|
|
||||||
itemsToHide.delete(this.item)
|
|
||||||
itemsToShow.delete(this.item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#settings.set_strv("hide", Array.from(itemsToHide));
|
|
||||||
this.#settings.set_strv("show", Array.from(itemsToShow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
146
src/prefsModules/PrefsBoxOrderItemRow.js
Normal file
146
src/prefsModules/PrefsBoxOrderItemRow.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported PrefsBoxOrderItemRow */
|
||||||
|
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Gdk = imports.gi.Gdk;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
const Adw = imports.gi.Adw;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
var PrefsBoxOrderItemRow = GObject.registerClass({
|
||||||
|
GTypeName: "PrefsBoxOrderItemRow",
|
||||||
|
Template: Me.dir.get_child("ui").get_child("prefs-box-order-item-row.ui").get_uri(),
|
||||||
|
InternalChildren: [
|
||||||
|
"item-name-display-label"
|
||||||
|
]
|
||||||
|
}, class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||||
|
#drag_starting_point_x;
|
||||||
|
#drag_starting_point_y;
|
||||||
|
|
||||||
|
constructor(params = {}, item) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
this.#associateItem(item);
|
||||||
|
this.#setupActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate `this` with an item.
|
||||||
|
* @param {String} item
|
||||||
|
*/
|
||||||
|
#associateItem(item) {
|
||||||
|
this.item = item;
|
||||||
|
|
||||||
|
if (item.startsWith("appindicator-kstatusnotifieritem-")) {
|
||||||
|
// Set `this._item_name_display_label` to something nicer, if the
|
||||||
|
// associated item is an AppIndicator/KStatusNotifierItem item.
|
||||||
|
this._item_name_display_label.set_label(item.replace("appindicator-kstatusnotifieritem-", ""));
|
||||||
|
} else {
|
||||||
|
// Otherwise just set it to `item`.
|
||||||
|
this._item_name_display_label.set_label(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup actions.
|
||||||
|
*/
|
||||||
|
#setupActions() {
|
||||||
|
const actionGroup = new Gio.SimpleActionGroup();
|
||||||
|
|
||||||
|
const forgetAction = new Gio.SimpleAction({
|
||||||
|
name: "forget"
|
||||||
|
});
|
||||||
|
forgetAction.connect("activate", (_action, _params) => {
|
||||||
|
const parentListBox = this.get_parent();
|
||||||
|
parentListBox.remove(this);
|
||||||
|
parentListBox.saveBoxOrderToSettings();
|
||||||
|
});
|
||||||
|
actionGroup.add_action(forgetAction);
|
||||||
|
|
||||||
|
this.insert_action_group("options", actionGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragPrepare(_source, x, y) {
|
||||||
|
const value = new GObject.Value();
|
||||||
|
value.init(PrefsBoxOrderItemRow);
|
||||||
|
value.set_object(this);
|
||||||
|
|
||||||
|
this.#drag_starting_point_x = x;
|
||||||
|
this.#drag_starting_point_y = y;
|
||||||
|
return Gdk.ContentProvider.new_for_value(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragBegin(_source, drag) {
|
||||||
|
let dragWidget = new Gtk.ListBox();
|
||||||
|
let allocation = this.get_allocation();
|
||||||
|
dragWidget.set_size_request(allocation.width, allocation.height);
|
||||||
|
|
||||||
|
let dragPrefsBoxOrderItemRow = new PrefsBoxOrderItemRow({}, this.item);
|
||||||
|
dragWidget.append(dragPrefsBoxOrderItemRow);
|
||||||
|
dragWidget.drag_highlight_row(dragPrefsBoxOrderItemRow);
|
||||||
|
|
||||||
|
let currentDragIcon = Gtk.DragIcon.get_for_drag(drag);
|
||||||
|
currentDragIcon.set_child(dragWidget);
|
||||||
|
drag.set_hotspot(this.#drag_starting_point_x, this.#drag_starting_point_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a new drop on `this` properly.
|
||||||
|
// `value` is the thing getting dropped.
|
||||||
|
onDrop(_target, value, _x, _y) {
|
||||||
|
// If `this` got dropped onto itself, do nothing.
|
||||||
|
if (value === this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the GtkListBoxes of `this` and the drop value.
|
||||||
|
const ownListBox = this.get_parent();
|
||||||
|
const valueListBox = value.get_parent();
|
||||||
|
|
||||||
|
// Get the position of `this` and the drop value.
|
||||||
|
const ownPosition = this.get_index();
|
||||||
|
const valuePosition = value.get_index();
|
||||||
|
|
||||||
|
// Remove the drop value from its list box.
|
||||||
|
valueListBox.remove(value);
|
||||||
|
|
||||||
|
// Since an element got potentially removed from the list of `this`,
|
||||||
|
// get the position of `this` again.
|
||||||
|
const updatedOwnPosition = this.get_index();
|
||||||
|
|
||||||
|
if (ownListBox !== valueListBox) {
|
||||||
|
// First handle the case where `this` and the drop value are in
|
||||||
|
// different list boxes.
|
||||||
|
if ((ownListBox.boxOrder === "right-box-order" && valueListBox.boxOrder === "left-box-order")
|
||||||
|
|| (ownListBox.boxOrder === "right-box-order" && valueListBox.boxOrder === "center-box-order")
|
||||||
|
|| (ownListBox.boxOrder === "center-box-order" && valueListBox.boxOrder === "left-box-order")) {
|
||||||
|
// If the list box of the drop value comes before the list
|
||||||
|
// box of `this`, add the drop value after `this`.
|
||||||
|
ownListBox.insert(value, updatedOwnPosition + 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise, add the drop value where `this` currently is.
|
||||||
|
ownListBox.insert(value, updatedOwnPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (valuePosition < ownPosition) {
|
||||||
|
// If the drop value was before `this`, add the drop value
|
||||||
|
// after `this`.
|
||||||
|
ownListBox.insert(value, updatedOwnPosition + 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise, add the drop value where `this` currently is.
|
||||||
|
ownListBox.insert(value, updatedOwnPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finally save the box order(/s) to settings.
|
||||||
|
ownListBox.saveBoxOrderToSettings();
|
||||||
|
// If the list boxes of `this` and the drop value were different,
|
||||||
|
// save an updated box order for the list were the drop value was in
|
||||||
|
// as well.
|
||||||
|
if (ownListBox !== valueListBox) {
|
||||||
|
valueListBox.saveBoxOrderToSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,161 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Gtk from "gi://Gtk";
|
|
||||||
import Gdk from "gi://Gdk";
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import Adw from "gi://Adw";
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
|
|
||||||
import PrefsBoxOrderItemOptionsDialog from "./PrefsBoxOrderItemOptionsDialog.js";
|
|
||||||
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
|
|
||||||
|
|
||||||
export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
GTypeName: "PrefsBoxOrderItemRow",
|
|
||||||
Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-item-row.ui", GLib.UriFlags.NONE),
|
|
||||||
Signals: {
|
|
||||||
"move": {
|
|
||||||
param_types: [GObject.TYPE_STRING],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, this);
|
|
||||||
this.install_action("row.forget", null, (self, _actionName, _param) => {
|
|
||||||
const parentListBox = self.get_parent() as PrefsBoxOrderListBox;
|
|
||||||
parentListBox.removeRow(self as PrefsBoxOrderItemRow);
|
|
||||||
parentListBox.saveBoxOrderToSettings();
|
|
||||||
parentListBox.determineRowMoveActionEnable();
|
|
||||||
});
|
|
||||||
this.install_action("row.options", null, (self, _actionName, _param) => {
|
|
||||||
const itemOptionsDialog = new PrefsBoxOrderItemOptionsDialog({
|
|
||||||
// Get the title from self as the constructor of
|
|
||||||
// PrefsBoxOrderItemRow already processes the item name into a
|
|
||||||
// nice title.
|
|
||||||
title: (self as PrefsBoxOrderItemRow).get_title()
|
|
||||||
}, (self as PrefsBoxOrderItemRow).item);
|
|
||||||
itemOptionsDialog.present(self);
|
|
||||||
});
|
|
||||||
this.install_action("row.move-up", null, (self, _actionName, _param) => self.emit("move", "up"));
|
|
||||||
this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down"));
|
|
||||||
}
|
|
||||||
|
|
||||||
item: string;
|
|
||||||
#drag_starting_point_x?: number;
|
|
||||||
#drag_starting_point_y?: number;
|
|
||||||
|
|
||||||
constructor(params = {}, item: string) {
|
|
||||||
super(params);
|
|
||||||
|
|
||||||
// Associate `this` with an item.
|
|
||||||
this.item = item;
|
|
||||||
if (this.item.startsWith("appindicator-kstatusnotifieritem-")) {
|
|
||||||
// Set the title to something nicer, if the associated item is an
|
|
||||||
// AppIndicator/KStatusNotifierItem item.
|
|
||||||
this.set_title(this.item.replace("appindicator-kstatusnotifieritem-", ""));
|
|
||||||
} else if (this.item === "item-role-group-task-up-ultralite") {
|
|
||||||
// Set the title to something nicer, if the item in question is the
|
|
||||||
// Task Up UltraLite item role group.
|
|
||||||
this.set_title("Task Up UltraLite Items");
|
|
||||||
} else {
|
|
||||||
// Otherwise just set it to `item`.
|
|
||||||
this.set_title(this.item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDragPrepare(_source: Gtk.DragSource, x: number, y: number): Gdk.ContentProvider {
|
|
||||||
const value = new GObject.Value();
|
|
||||||
value.init(PrefsBoxOrderItemRow.$gtype);
|
|
||||||
value.set_object(this);
|
|
||||||
|
|
||||||
this.#drag_starting_point_x = x;
|
|
||||||
this.#drag_starting_point_y = y;
|
|
||||||
return Gdk.ContentProvider.new_for_value(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDragBegin(_source: Gtk.DragSource, drag: Gdk.Drag): void {
|
|
||||||
let dragWidget = new Gtk.ListBox();
|
|
||||||
let allocation = this.get_allocation();
|
|
||||||
dragWidget.set_size_request(allocation.width, allocation.height);
|
|
||||||
|
|
||||||
let dragPrefsBoxOrderItemRow = new PrefsBoxOrderItemRow({}, this.item);
|
|
||||||
dragWidget.append(dragPrefsBoxOrderItemRow);
|
|
||||||
dragWidget.drag_highlight_row(dragPrefsBoxOrderItemRow);
|
|
||||||
|
|
||||||
let currentDragIcon = Gtk.DragIcon.get_for_drag(drag);
|
|
||||||
currentDragIcon.set_child(dragWidget);
|
|
||||||
// Even tho this should always be the case, ensure the values for the hotspot aren't undefined.
|
|
||||||
if (typeof this.#drag_starting_point_x !== "undefined" &&
|
|
||||||
typeof this.#drag_starting_point_y !== "undefined") {
|
|
||||||
drag.set_hotspot(this.#drag_starting_point_x, this.#drag_starting_point_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a new drop on `this` properly.
|
|
||||||
// `value` is the thing getting dropped.
|
|
||||||
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean {
|
|
||||||
// According to the type annotations of Gtk.DropTarget, value is of type
|
|
||||||
// GObject.Value, so ensure the one we work with is of type
|
|
||||||
// PrefsBoxOrderItemRow.
|
|
||||||
if (!(value instanceof PrefsBoxOrderItemRow)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `this` got dropped onto itself, do nothing.
|
|
||||||
if (value === this) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the GtkListBoxes of `this` and the drop value.
|
|
||||||
const ownListBox = this.get_parent() as PrefsBoxOrderListBox;
|
|
||||||
const valueListBox = value.get_parent() as PrefsBoxOrderListBox;
|
|
||||||
|
|
||||||
// Get the position of `this` and the drop value.
|
|
||||||
const ownPosition = this.get_index();
|
|
||||||
const valuePosition = value.get_index();
|
|
||||||
|
|
||||||
// Remove the drop value from its list box.
|
|
||||||
valueListBox.removeRow(value);
|
|
||||||
|
|
||||||
// Since an element got potentially removed from the list of `this`,
|
|
||||||
// get the position of `this` again.
|
|
||||||
const updatedOwnPosition = this.get_index();
|
|
||||||
|
|
||||||
if (ownListBox !== valueListBox) {
|
|
||||||
// First handle the case where `this` and the drop value are in
|
|
||||||
// different list boxes.
|
|
||||||
if ((ownListBox.boxOrder === "right-box-order" && valueListBox.boxOrder === "left-box-order")
|
|
||||||
|| (ownListBox.boxOrder === "right-box-order" && valueListBox.boxOrder === "center-box-order")
|
|
||||||
|| (ownListBox.boxOrder === "center-box-order" && valueListBox.boxOrder === "left-box-order")) {
|
|
||||||
// If the list box of the drop value comes before the list
|
|
||||||
// box of `this`, add the drop value after `this`.
|
|
||||||
ownListBox.insertRow(value, updatedOwnPosition + 1);
|
|
||||||
} else {
|
|
||||||
// Otherwise, add the drop value where `this` currently is.
|
|
||||||
ownListBox.insertRow(value, updatedOwnPosition);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (valuePosition < ownPosition) {
|
|
||||||
// If the drop value was before `this`, add the drop value
|
|
||||||
// after `this`.
|
|
||||||
ownListBox.insertRow(value, updatedOwnPosition + 1);
|
|
||||||
} else {
|
|
||||||
// Otherwise, add the drop value where `this` currently is.
|
|
||||||
ownListBox.insertRow(value, updatedOwnPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finally save the box order(/s) to settings and make sure move
|
|
||||||
/// actions are correctly enabled/disabled.
|
|
||||||
ownListBox.saveBoxOrderToSettings();
|
|
||||||
ownListBox.determineRowMoveActionEnable();
|
|
||||||
// If the list boxes of `this` and the drop value were different, handle
|
|
||||||
// the former list box of the drop value as well.
|
|
||||||
if (ownListBox !== valueListBox) {
|
|
||||||
valueListBox.saveBoxOrderToSettings();
|
|
||||||
valueListBox.determineRowMoveActionEnable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
81
src/prefsModules/PrefsBoxOrderListBox.js
Normal file
81
src/prefsModules/PrefsBoxOrderListBox.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported PrefsBoxOrderListBox */
|
||||||
|
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
const PrefsBoxOrderItemRow = Me.imports.prefsModules.PrefsBoxOrderItemRow;
|
||||||
|
const PrefsBoxOrderListEmptyPlaceholder = Me.imports.prefsModules.PrefsBoxOrderListEmptyPlaceholder;
|
||||||
|
|
||||||
|
var PrefsBoxOrderListBox = GObject.registerClass({
|
||||||
|
GTypeName: "PrefsBoxOrderListBox",
|
||||||
|
Template: Me.dir.get_child("ui").get_child("prefs-box-order-list-box.ui").get_uri(),
|
||||||
|
Properties: {
|
||||||
|
BoxOrder: GObject.ParamSpec.string(
|
||||||
|
"box-order",
|
||||||
|
"Box Order",
|
||||||
|
"The box order this PrefsBoxOrderListBox is associated with.",
|
||||||
|
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||||
|
#settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} params
|
||||||
|
*/
|
||||||
|
constructor(params = {}) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
// Load the settings.
|
||||||
|
this.#settings = ExtensionUtils.getSettings();
|
||||||
|
|
||||||
|
// Add a placeholder widget for the case, where no GtkListBoxRows are
|
||||||
|
// present.
|
||||||
|
this.set_placeholder(new PrefsBoxOrderListEmptyPlaceholder.PrefsBoxOrderListEmptyPlaceholder());
|
||||||
|
}
|
||||||
|
|
||||||
|
get boxOrder() {
|
||||||
|
return this._boxOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
set boxOrder(value) {
|
||||||
|
this._boxOrder = value;
|
||||||
|
|
||||||
|
// Load the settings here as well, since a `CONSTRUCT_ONLY` property
|
||||||
|
// apparently can't access `this.#settings`.
|
||||||
|
const settings = ExtensionUtils.getSettings();
|
||||||
|
// Get the actual box order for the given box order name from settings.
|
||||||
|
const boxOrder = settings.get_strv(this._boxOrder);
|
||||||
|
// Populate this GtkListBox with GtkListBoxRows for the items of the
|
||||||
|
// given configured box order.
|
||||||
|
for (const item of boxOrder) {
|
||||||
|
const listBoxRow = new PrefsBoxOrderItemRow.PrefsBoxOrderItemRow({}, item);
|
||||||
|
this.append(listBoxRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notify("box-order");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the box order represented by `this` (and its
|
||||||
|
* `PrefsBoxOrderItemRows`) to settings.
|
||||||
|
*/
|
||||||
|
saveBoxOrderToSettings() {
|
||||||
|
let currentBoxOrder = [];
|
||||||
|
for (let potentialPrefsBoxOrderItemRow of this) {
|
||||||
|
// Only process PrefsBoxOrderItemRows.
|
||||||
|
if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = potentialPrefsBoxOrderItemRow.item;
|
||||||
|
currentBoxOrder.push(item);
|
||||||
|
}
|
||||||
|
this.#settings.set_strv(this.boxOrder, currentBoxOrder);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,152 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Gtk from "gi://Gtk";
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
import type Gio from "gi://Gio";
|
|
||||||
|
|
||||||
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
||||||
|
|
||||||
import PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
|
|
||||||
import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js";
|
|
||||||
|
|
||||||
export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
GTypeName: "PrefsBoxOrderListBox",
|
|
||||||
Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-list-box.ui", GLib.UriFlags.NONE),
|
|
||||||
Properties: {
|
|
||||||
BoxOrder: GObject.ParamSpec.string(
|
|
||||||
"box-order",
|
|
||||||
"Box Order",
|
|
||||||
"The box order this PrefsBoxOrderListBox is associated with.",
|
|
||||||
GObject.ParamFlags.READWRITE,
|
|
||||||
""
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Signals: {
|
|
||||||
"row-move": {
|
|
||||||
param_types: [PrefsBoxOrderItemRow.$gtype, GObject.TYPE_STRING],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_boxOrder!: string;
|
|
||||||
#settings: Gio.Settings;
|
|
||||||
#rowSignalHandlerIds = new Map<PrefsBoxOrderItemRow, number[]>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} params
|
|
||||||
*/
|
|
||||||
constructor(params = {}) {
|
|
||||||
super(params);
|
|
||||||
|
|
||||||
// Load the settings.
|
|
||||||
this.#settings = ExtensionPreferences.lookupByURL(import.meta.url)!.getSettings();
|
|
||||||
|
|
||||||
// Add a placeholder widget for the case, where no GtkListBoxRows are
|
|
||||||
// present.
|
|
||||||
this.set_placeholder(new PrefsBoxOrderListEmptyPlaceholder());
|
|
||||||
}
|
|
||||||
|
|
||||||
get boxOrder(): string {
|
|
||||||
return this._boxOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
set boxOrder(value: string) {
|
|
||||||
this._boxOrder = value;
|
|
||||||
|
|
||||||
// Get the actual box order for the given box order name from settings.
|
|
||||||
const boxOrder = this.#settings.get_strv(this._boxOrder);
|
|
||||||
// Populate this GtkListBox with GtkListBoxRows for the items of the
|
|
||||||
// given configured box order.
|
|
||||||
for (const item of boxOrder) {
|
|
||||||
const row = new PrefsBoxOrderItemRow({}, item);
|
|
||||||
this.insertRow(row, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.determineRowMoveActionEnable();
|
|
||||||
this.notify("box-order");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the given PrefsBoxOrderItemRow to this list box at the given
|
|
||||||
* position.
|
|
||||||
* Also handles stuff like connecting signals.
|
|
||||||
*/
|
|
||||||
insertRow(row: PrefsBoxOrderItemRow, position: number): void {
|
|
||||||
this.insert(row, position);
|
|
||||||
|
|
||||||
const signalHandlerIds: number[] = [];
|
|
||||||
signalHandlerIds.push(row.connect("move", (row, direction) => {
|
|
||||||
this.emit("row-move", row, direction);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.#rowSignalHandlerIds.set(row, signalHandlerIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the given PrefsBoxOrderItemRow from this list box.
|
|
||||||
* Also handles stuff like disconnecting signals.
|
|
||||||
*/
|
|
||||||
removeRow(row: PrefsBoxOrderItemRow): void {
|
|
||||||
const signalHandlerIds = this.#rowSignalHandlerIds.get(row) ?? [];
|
|
||||||
|
|
||||||
for (const id of signalHandlerIds) {
|
|
||||||
row.disconnect(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.remove(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the box order represented by `this` (and its
|
|
||||||
* `PrefsBoxOrderItemRows`) to settings.
|
|
||||||
*/
|
|
||||||
saveBoxOrderToSettings(): void {
|
|
||||||
let currentBoxOrder: string[] = [];
|
|
||||||
for (let potentialPrefsBoxOrderItemRow of this) {
|
|
||||||
// Only process PrefsBoxOrderItemRows.
|
|
||||||
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = potentialPrefsBoxOrderItemRow.item;
|
|
||||||
currentBoxOrder.push(item);
|
|
||||||
}
|
|
||||||
this.#settings.set_strv(this.boxOrder, currentBoxOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not each move action of each PrefsBoxOrderItemRow
|
|
||||||
* should be enabled or disabled.
|
|
||||||
*/
|
|
||||||
determineRowMoveActionEnable(): void {
|
|
||||||
for (let potentialPrefsBoxOrderItemRow of this) {
|
|
||||||
// Only process PrefsBoxOrderItemRows.
|
|
||||||
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = potentialPrefsBoxOrderItemRow;
|
|
||||||
|
|
||||||
// If the current row is the topmost row in the topmost list box,
|
|
||||||
// then disable the move-up action.
|
|
||||||
if (row.get_index() === 0 && this.boxOrder === "left-box-order") {
|
|
||||||
row.action_set_enabled("row.move-up", false);
|
|
||||||
} else { // Else enable it.
|
|
||||||
row.action_set_enabled("row.move-up", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the current row is the bottommost row in the bottommost list
|
|
||||||
// box, then disable the move-down action.
|
|
||||||
const rowNextSibling = row.get_next_sibling();
|
|
||||||
if ((rowNextSibling instanceof PrefsBoxOrderListEmptyPlaceholder || rowNextSibling === null) && this.boxOrder === "right-box-order") {
|
|
||||||
row.action_set_enabled("row.move-down", false);
|
|
||||||
} else { // Else enable it.
|
|
||||||
row.action_set_enabled("row.move-down", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js
Normal file
31
src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported PrefsBoxOrderListEmptyPlaceholder */
|
||||||
|
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
var PrefsBoxOrderListEmptyPlaceholder = GObject.registerClass({
|
||||||
|
GTypeName: "PrefsBoxOrderListEmptyPlaceholder",
|
||||||
|
Template: Me.dir.get_child("ui").get_child("prefs-box-order-list-empty-placeholder.ui").get_uri()
|
||||||
|
}, class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
|
||||||
|
// Handle a new drop on `this` properly.
|
||||||
|
// `value` is the thing getting dropped.
|
||||||
|
onDrop(_target, value, _x, _y) {
|
||||||
|
// Get the GtkListBoxes of `this` and the drop value.
|
||||||
|
const ownListBox = this.get_parent();
|
||||||
|
const valueListBox = value.get_parent();
|
||||||
|
|
||||||
|
// Remove the drop value from its list box.
|
||||||
|
valueListBox.remove(value);
|
||||||
|
|
||||||
|
// Insert the drop value into the list box of `this`.
|
||||||
|
ownListBox.insert(value, 0);
|
||||||
|
|
||||||
|
/// Finally save the box orders to settings.
|
||||||
|
ownListBox.saveBoxOrderToSettings();
|
||||||
|
valueListBox.saveBoxOrderToSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,48 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Gtk from "gi://Gtk";
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
|
|
||||||
import PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
|
|
||||||
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
|
|
||||||
|
|
||||||
export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
GTypeName: "PrefsBoxOrderListEmptyPlaceholder",
|
|
||||||
Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-list-empty-placeholder.ui", GLib.UriFlags.NONE),
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a new drop on `this` properly.
|
|
||||||
// `value` is the thing getting dropped.
|
|
||||||
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean {
|
|
||||||
// According to the type annotations of Gtk.DropTarget, value is of type
|
|
||||||
// GObject.Value, so ensure the one we work with is of type
|
|
||||||
// PrefsBoxOrderItemRow.
|
|
||||||
if (!(value instanceof PrefsBoxOrderItemRow)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the GtkListBoxes of `this` and the drop value.
|
|
||||||
const ownListBox = this.get_parent() as PrefsBoxOrderListBox;
|
|
||||||
const valueListBox = value.get_parent() as PrefsBoxOrderListBox;
|
|
||||||
|
|
||||||
// Remove the drop value from its list box.
|
|
||||||
valueListBox.removeRow(value);
|
|
||||||
|
|
||||||
// Insert the drop value into the list box of `this`.
|
|
||||||
ownListBox.insertRow(value, 0);
|
|
||||||
|
|
||||||
/// Finally save the box orders to settings and make sure move actions
|
|
||||||
/// are correctly enabled/disabled.
|
|
||||||
ownListBox.saveBoxOrderToSettings();
|
|
||||||
ownListBox.determineRowMoveActionEnable();
|
|
||||||
valueListBox.saveBoxOrderToSettings();
|
|
||||||
valueListBox.determineRowMoveActionEnable();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
src/prefsModules/PrefsPage.js
Normal file
88
src/prefsModules/PrefsPage.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
"use strict";
|
||||||
|
/* exported PrefsPage */
|
||||||
|
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
const Adw = imports.gi.Adw;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
const ScrollManager = Me.imports.prefsModules.ScrollManager;
|
||||||
|
|
||||||
|
// Imports to make UI file work.
|
||||||
|
/* exported PrefsBoxOrderListBox */
|
||||||
|
const PrefsBoxOrderListBox = Me.imports.prefsModules.PrefsBoxOrderListBox;
|
||||||
|
|
||||||
|
var PrefsPage = GObject.registerClass({
|
||||||
|
GTypeName: "PrefsPage",
|
||||||
|
Template: Me.dir.get_child("ui").get_child("prefs-page.ui").get_uri()
|
||||||
|
}, class PrefsPage extends Adw.PreferencesPage {
|
||||||
|
constructor(params = {}) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
this.#setupDNDScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function sets up Drag-and-Drop scrolling.
|
||||||
|
* This means that scroll up or down is happening when a Drag-and-Drop
|
||||||
|
* operation is in progress and the user has their cursor either in the
|
||||||
|
* upper or lower 10% of this widget respectively.
|
||||||
|
*/
|
||||||
|
#setupDNDScroll() {
|
||||||
|
// Pass `this.get_first_child()` to the ScrollManager, since this
|
||||||
|
// `PrefsPage` extends an `Adw.PreferencesPage` and the first child of
|
||||||
|
// an `Adw.PreferencesPage` is the built-in `Gtk.ScrolledWindow`.
|
||||||
|
const scrollManager = new ScrollManager.ScrollManager(this.get_first_child());
|
||||||
|
|
||||||
|
/// Setup GtkDropControllerMotion event controller and make use of its
|
||||||
|
/// events.
|
||||||
|
let controller = new Gtk.DropControllerMotion();
|
||||||
|
|
||||||
|
// Scroll, when the pointer is in the right places.
|
||||||
|
controller.connect("motion", (_, _x, y) => {
|
||||||
|
if (y <= this.get_allocated_height() * 0.1) {
|
||||||
|
// If the pointer is currently in the upper ten percent of this
|
||||||
|
// widget, then scroll up.
|
||||||
|
scrollManager.startScrollUp();
|
||||||
|
} else if (y >= this.get_allocated_height() * 0.9) {
|
||||||
|
// If the pointer is currently in the lower ten percent of this
|
||||||
|
// widget, then scroll down.
|
||||||
|
scrollManager.startScrollDown();
|
||||||
|
} else {
|
||||||
|
// Otherwise stop scrolling.
|
||||||
|
scrollManager.stopScrollAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure scrolling stops, when DND operation ends.
|
||||||
|
this._dndEnded = true;
|
||||||
|
const stopScrollAllAtDNDEnd = () => {
|
||||||
|
scrollManager.stopScrollAll();
|
||||||
|
this._dndEnded = true;
|
||||||
|
};
|
||||||
|
controller.connect("leave", () => {
|
||||||
|
stopScrollAllAtDNDEnd();
|
||||||
|
});
|
||||||
|
controller.connect("enter", () => {
|
||||||
|
// Make use of `this._dndEnded` to setup stopScrollAtDNDEnd only
|
||||||
|
// once per DND operation.
|
||||||
|
if (this._dndEnded) {
|
||||||
|
let drag = controller.get_drop().get_drag();
|
||||||
|
drag.connect("drop-performed", () => {
|
||||||
|
stopScrollAllAtDNDEnd();
|
||||||
|
});
|
||||||
|
drag.connect("dnd-finished", () => {
|
||||||
|
stopScrollAllAtDNDEnd();
|
||||||
|
});
|
||||||
|
drag.connect("cancel", () => {
|
||||||
|
stopScrollAllAtDNDEnd();
|
||||||
|
});
|
||||||
|
this._dndEnded = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.add_controller(controller);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,196 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import Gdk from "gi://Gdk";
|
|
||||||
import Gtk from "gi://Gtk";
|
|
||||||
import GObject from "gi://GObject";
|
|
||||||
import Adw from "gi://Adw";
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
|
|
||||||
import ScrollManager from "./ScrollManager.js";
|
|
||||||
import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js";
|
|
||||||
import type PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
|
|
||||||
|
|
||||||
// Imports to make UI file work.
|
|
||||||
// eslint-disable-next-line
|
|
||||||
import PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
|
|
||||||
|
|
||||||
export default class PrefsPage extends Adw.PreferencesPage {
|
|
||||||
static {
|
|
||||||
GObject.registerClass({
|
|
||||||
GTypeName: "PrefsPage",
|
|
||||||
Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-page.ui", GLib.UriFlags.NONE),
|
|
||||||
InternalChildren: [
|
|
||||||
"left-box-order-list-box",
|
|
||||||
"center-box-order-list-box",
|
|
||||||
"right-box-order-list-box",
|
|
||||||
],
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_dndEnded?: boolean;
|
|
||||||
declare _left_box_order_list_box: PrefsBoxOrderListBox;
|
|
||||||
declare _center_box_order_list_box: PrefsBoxOrderListBox;
|
|
||||||
declare _right_box_order_list_box: PrefsBoxOrderListBox;
|
|
||||||
|
|
||||||
constructor(params = {}) {
|
|
||||||
super(params);
|
|
||||||
|
|
||||||
this.#setupDNDScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function sets up Drag-and-Drop scrolling.
|
|
||||||
* This means that scroll up or down is happening when a Drag-and-Drop
|
|
||||||
* operation is in progress and the user has their cursor either in the
|
|
||||||
* upper or lower 10% of this widget respectively.
|
|
||||||
*/
|
|
||||||
#setupDNDScroll(): void {
|
|
||||||
// Pass `this.get_first_child()` to the ScrollManager, since this
|
|
||||||
// `PrefsPage` extends an `Adw.PreferencesPage` and the first child of
|
|
||||||
// an `Adw.PreferencesPage` is the built-in `Gtk.ScrolledWindow`.
|
|
||||||
const scrollManager = new ScrollManager(this.get_first_child() as Gtk.ScrolledWindow);
|
|
||||||
|
|
||||||
/// Setup GtkDropControllerMotion event controller and make use of its
|
|
||||||
/// events.
|
|
||||||
let controller = new Gtk.DropControllerMotion();
|
|
||||||
|
|
||||||
// Make sure scrolling stops, when DND operation ends.
|
|
||||||
this._dndEnded = true;
|
|
||||||
|
|
||||||
// Scroll, when the pointer is in the right places and a DND operation
|
|
||||||
// is properly set up (this._dndEnded is false).
|
|
||||||
controller.connect("motion", (_, _x, y) => {
|
|
||||||
if ((y <= this.get_allocated_height() * 0.1) && !this._dndEnded) {
|
|
||||||
// If the pointer is currently in the upper ten percent of this
|
|
||||||
// widget, then scroll up.
|
|
||||||
scrollManager.startScrollUp();
|
|
||||||
} else if ((y >= this.get_allocated_height() * 0.9) && !this._dndEnded) {
|
|
||||||
// If the pointer is currently in the lower ten percent of this
|
|
||||||
// widget, then scroll down.
|
|
||||||
scrollManager.startScrollDown();
|
|
||||||
} else {
|
|
||||||
// Otherwise stop scrolling.
|
|
||||||
scrollManager.stopScrollAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const stopScrollAllAtDNDEnd = () => {
|
|
||||||
this._dndEnded = true;
|
|
||||||
scrollManager.stopScrollAll();
|
|
||||||
};
|
|
||||||
controller.connect("leave", () => {
|
|
||||||
stopScrollAllAtDNDEnd();
|
|
||||||
});
|
|
||||||
controller.connect("enter", () => {
|
|
||||||
// Make use of `this._dndEnded` to setup stopScrollAtDNDEnd only
|
|
||||||
// once per DND operation.
|
|
||||||
if (this._dndEnded) {
|
|
||||||
const drag = controller.get_drop()?.get_drag() ?? null;
|
|
||||||
// Ensure we have a Gdk.Drag.
|
|
||||||
// If this is not the case for whatever reason, then don't start
|
|
||||||
// DND scrolling and just return.
|
|
||||||
if (!(drag instanceof Gdk.Drag)) {
|
|
||||||
// TODO: maybe add logging
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
drag.connect("drop-performed", () => {
|
|
||||||
stopScrollAllAtDNDEnd();
|
|
||||||
});
|
|
||||||
drag.connect("dnd-finished", () => {
|
|
||||||
stopScrollAllAtDNDEnd();
|
|
||||||
});
|
|
||||||
drag.connect("cancel", () => {
|
|
||||||
stopScrollAllAtDNDEnd();
|
|
||||||
});
|
|
||||||
this._dndEnded = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.add_controller(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
onRowMove(listBox: PrefsBoxOrderListBox, row: PrefsBoxOrderItemRow, direction: string): void {
|
|
||||||
const rowPosition = row.get_index();
|
|
||||||
|
|
||||||
if (direction === "up") { // If the direction of the move is up.
|
|
||||||
// Handle the case, where the row is the topmost row in the list box.
|
|
||||||
if (rowPosition === 0) {
|
|
||||||
switch (listBox.boxOrder) {
|
|
||||||
// If the row is also in the topmost list box, then do
|
|
||||||
// nothing and return.
|
|
||||||
case "left-box-order":
|
|
||||||
log("The row is already the topmost row in the topmost box order.");
|
|
||||||
return;
|
|
||||||
// If the row is in the center list box, then move it up to
|
|
||||||
// the left one.
|
|
||||||
case "center-box-order":
|
|
||||||
listBox.removeRow(row);
|
|
||||||
this._left_box_order_list_box.insertRow(row, -1);
|
|
||||||
// First save the box order of the destination, then do
|
|
||||||
// "a save for clean up".
|
|
||||||
this._left_box_order_list_box.saveBoxOrderToSettings();
|
|
||||||
this._left_box_order_list_box.determineRowMoveActionEnable();
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
// If the row is in the right list box, then move it up to
|
|
||||||
// the center one.
|
|
||||||
case "right-box-order":
|
|
||||||
listBox.removeRow(row);
|
|
||||||
this._center_box_order_list_box.insertRow(row, -1);
|
|
||||||
this._center_box_order_list_box.saveBoxOrderToSettings();
|
|
||||||
this._center_box_order_list_box.determineRowMoveActionEnable();
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else just move the row up in the box.
|
|
||||||
listBox.removeRow(row);
|
|
||||||
listBox.insertRow(row, rowPosition - 1);
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
} else { // Else the direction of the move must be down.
|
|
||||||
// Handle the case, where the row is the bottommost row in the list box.
|
|
||||||
const rowNextSibling = row.get_next_sibling();
|
|
||||||
if (rowNextSibling instanceof PrefsBoxOrderListEmptyPlaceholder || rowNextSibling === null) {
|
|
||||||
switch (listBox.boxOrder) {
|
|
||||||
// If the row is also in the bottommost list box, then do
|
|
||||||
// nothing and return.
|
|
||||||
case "right-box-order":
|
|
||||||
log("The row is already the bottommost row in the bottommost box order.");
|
|
||||||
return;
|
|
||||||
// If the row is in the center list box, then move it down
|
|
||||||
// to the right one.
|
|
||||||
case "center-box-order":
|
|
||||||
listBox.removeRow(row);
|
|
||||||
this._right_box_order_list_box.insertRow(row, 0);
|
|
||||||
this._right_box_order_list_box.saveBoxOrderToSettings();
|
|
||||||
this._right_box_order_list_box.determineRowMoveActionEnable();
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
// If the row is in the left list box, then move it down to
|
|
||||||
// the center one.
|
|
||||||
case "left-box-order":
|
|
||||||
listBox.removeRow(row);
|
|
||||||
this._center_box_order_list_box.insertRow(row, 0);
|
|
||||||
this._center_box_order_list_box.saveBoxOrderToSettings();
|
|
||||||
this._center_box_order_list_box.determineRowMoveActionEnable();
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else just move the row down in the box.
|
|
||||||
listBox.removeRow(row);
|
|
||||||
listBox.insertRow(row, rowPosition + 1);
|
|
||||||
listBox.saveBoxOrderToSettings();
|
|
||||||
listBox.determineRowMoveActionEnable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +1,23 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
/* exported ScrollManager */
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
|
||||||
import GLib from "gi://GLib";
|
var ScrollManager = class ScrollManager {
|
||||||
import type Gtk from "gi://Gtk";
|
#gtkScrolledWindow;
|
||||||
|
#scrollUp;
|
||||||
|
#scrollDown;
|
||||||
|
|
||||||
export default class ScrollManager {
|
/**
|
||||||
#gtkScrolledWindow: Gtk.ScrolledWindow;
|
* @param {Gtk.ScrolledWindow} gtkScrolledWindow
|
||||||
#scrollUp: boolean;
|
*/
|
||||||
#scrollDown: boolean;
|
constructor(gtkScrolledWindow) {
|
||||||
|
|
||||||
constructor(gtkScrolledWindow: Gtk.ScrolledWindow) {
|
|
||||||
this.#gtkScrolledWindow = gtkScrolledWindow;
|
this.#gtkScrolledWindow = gtkScrolledWindow;
|
||||||
|
|
||||||
this.#scrollUp = false;
|
this.#scrollUp = false;
|
||||||
this.#scrollDown = false;
|
this.#scrollDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
startScrollUp(): void {
|
startScrollUp() {
|
||||||
// If the scroll up is already started, don't do anything.
|
// If the scroll up is already started, don't do anything.
|
||||||
if (this.#scrollUp) {
|
if (this.#scrollUp) {
|
||||||
return;
|
return;
|
||||||
@ -42,7 +44,7 @@ export default class ScrollManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startScrollDown(): void {
|
startScrollDown() {
|
||||||
// If the scroll down is already started, don't do anything.
|
// If the scroll down is already started, don't do anything.
|
||||||
if (this.#scrollDown) {
|
if (this.#scrollDown) {
|
||||||
return;
|
return;
|
||||||
@ -72,16 +74,16 @@ export default class ScrollManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stopScrollUp(): void {
|
stopScrollUp() {
|
||||||
this.#scrollUp = false;
|
this.#scrollUp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopScrollDown(): void {
|
stopScrollDown() {
|
||||||
this.#scrollDown = false;
|
this.#scrollDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopScrollAll(): void {
|
stopScrollAll() {
|
||||||
this.stopScrollUp();
|
this.stopScrollUp();
|
||||||
this.stopScrollDown();
|
this.stopScrollDown();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "NodeNext",
|
|
||||||
"outDir": "./dist",
|
|
||||||
"sourceMap": false,
|
|
||||||
"strict": true,
|
|
||||||
// To preserve imports for UI files.
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"target": "es2022",
|
|
||||||
"lib": [
|
|
||||||
"ES2022"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"ambient.d.ts",
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"src/extension.ts",
|
|
||||||
"src/prefs.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user