Add basic app menu

This commit is contained in:
2026-04-04 06:01:59 -07:00
parent de270cf9f5
commit 69343573af
10 changed files with 1643 additions and 41 deletions

49
debian/changelog vendored
View File

@@ -1,3 +1,52 @@
vesperos-taskbar (26.3) vesperos; urgency=medium
* Add Windows 10-style Start Menu replacing the GNOME app grid popup.
- Clicking the Show Apps button now opens a floating Win10-style menu
instead of the GNOME overview app grid.
- Includes a search bar that filters installed applications in real time.
- "Pinned" view shows favourites + running apps in a 4-column icon grid.
- "All apps" view shows an alphabetically sorted, scrollable app list
with letter dividers.
- Footer row shows the current user name and a power button with a
submenu for Lock, Sign out, Sleep, Restart, and Shut down.
- Menu is positioned above/beside the Show Apps button depending on
panel side; clamped to monitor bounds on multi-monitor setups.
- Closes on Escape key, outside click, or when the GNOME overview opens.
- Right-click context menu on the Show Apps button is unchanged.
-- VesperOS Desktop Team <contact@oxmc.me> Sat, 04 Apr 2026 12:00:00 +0000
vesperos-taskbar (26.2) vesperos; urgency=medium
* Re-based on upstream version 73.1 (additional bug fixes).
* Fix crash in activateFirstWindow when window list is empty.
* Fix memory leaks: properly destroy menus in TaskbarAppIcon and
ShowAppsIconWrapper on extension disable.
* Fix extension enable stalling when Zorin Dash is already disabled.
* Fix intellihide panel visibility logic to avoid unintended hide.
* Fix overview disable to end hotkey preview cycle before teardown.
* Fix null-dereference on intellihide reference in panel toggle handler.
* Fix show-desktop button removal to clean up pending timeout and
restore hidden workspace state before destroying the button.
* Fix panelManager cleanup of boxPointer signal ID on disable.
* Fix Workspace prototype not restored when extension is disabled
while the overview spread is active.
* Fix panel.outerSize reference to panel.geom.outerSize.
* Guard _newLookingGlassResize against missing primary monitor panel.
* Fix taskbar destroy to clean up _onStageKeyPress override when the
overview is open at disable time.
* Fix workspace signal disconnect to tolerate removed dynamic workspaces.
* Fix menu-state-changed signal connected before item container exists.
* Fix animateWindowOpacity reading initialOpacity before window
actor reassignment.
* Fix ColorUtils RGB-to-HSV conversion variable shadowing bug.
* Fix windowPreview workspace.activate to always restore _shouldAnimate
via try/finally.
* Fix windowPreview set_child_at_index with null parent guard and
index clamping.
-- VesperOS Desktop Team <contact@oxmc.me> Fri, 03 Apr 2026 15:30:00 +0000
vesperos-taskbar (26.1) vesperos; urgency=medium
* VesperOS fork of Zorin OS's gnome-shell-extension-zorin-taskbar package.

2
debian/rules vendored
View File

@@ -59,6 +59,8 @@ override_dh_install:
install -m 644 $$lang_dir/LC_MESSAGES/gtk4-ding.mo \
$(PKG_DIR)/usr/share/locale/$$lang/LC_MESSAGES/; \
done
# --- Start Menu themes ---
cp -r $(CURDIR)/src/themes $(EXT_DIR)/themes
override_dh_fixperms:
dh_fixperms

View File

@@ -854,5 +854,10 @@
<default>''</default>
<summary>The preferences page name to display</summary>
</key>
<key type="s" name="start-menu-stylesheet">
<default>''</default>
<summary>Custom stylesheet for the Start Menu</summary>
<description>Path to a CSS file to load on top of the built-in Start Menu styles. Leave empty to use defaults.</description>
</key>
</schema>
</schemalist>

742
src/startMenu.js Normal file
View File

@@ -0,0 +1,742 @@
/*
* This file is part of the VesperOS system-taskbar extension.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*/
import Clutter from 'gi://Clutter'
import Gio from 'gi://Gio'
import GLib from 'gi://GLib'
import GObject from 'gi://GObject'
import Pango from 'gi://Pango'
import Shell from 'gi://Shell'
import St from 'gi://St'
import * as AppFavorites from 'resource:///org/gnome/shell/ui/appFavorites.js'
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
import * as Util from 'resource:///org/gnome/shell/misc/util.js'
import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'
import * as Utils from './utils.js'
import { SETTINGS } from './extension.js'
const MENU_WIDTH = 460
const MENU_HEIGHT = 560
const APP_ICON_SIZE = 48
const GRID_COLUMNS = 4
const CELL_SIZE = Math.floor(MENU_WIDTH / GRID_COLUMNS) // 115
export const Win10StartMenu = GObject.registerClass(
class Win10StartMenu extends St.Widget {
_init(dtpPanel) {
super._init({
style_class: 'win10-start-menu',
reactive: true,
visible: false,
width: MENU_WIDTH,
height: MENU_HEIGHT,
})
this._dtpPanel = dtpPanel
this._appSystem = Shell.AppSystem.get_default()
this.isOpen = false
this._captureId = 0
this._focusId = 0
this._sourceActor = null
this._customStylesheet = null
this._powerPopup = null
this._timeoutsHandler = new Utils.TimeoutsHandler()
this._buildUI()
Main.uiGroup.add_child(this)
this._signalsHandler = new Utils.GlobalSignalsHandler()
this._signalsHandler.add(
[Main.overview, 'showing', () => { if (this.isOpen) this.close() }],
[this._appSystem, 'installed-changed', () => {
this._allAppsList.remove_all_children()
}],
[SETTINGS, 'changed::start-menu-stylesheet', () => this._reloadStylesheet()],
)
this._reloadStylesheet()
}
_reloadStylesheet() {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme()
if (this._customStylesheet) {
theme.unload_stylesheet(this._customStylesheet)
this._customStylesheet = null
}
let path = SETTINGS.get_string('start-menu-stylesheet').trim()
if (path) {
let file = Gio.File.new_for_path(path)
if (file.query_exists(null)) {
this._customStylesheet = file
theme.load_stylesheet(this._customStylesheet)
}
}
}
_buildUI() {
let outer = new St.BoxLayout({
style_class: 'win10-start-menu-inner',
vertical: true,
reactive: true,
x_expand: true,
y_expand: true,
})
// ── Search bar ──────────────────────────────────────────────────────────
this._searchEntry = new St.Entry({
style_class: 'win10-search-entry',
hint_text: _('Search apps'),
can_focus: true,
x_expand: true,
})
this._searchEntry.clutter_text.connect(
'text-changed',
() => this._onSearchTextChanged(),
)
outer.add_child(this._searchEntry)
// ── Content stack (pinned / all-apps / search results) ──────────────────
this._stack = new St.Widget({
layout_manager: new Clutter.BinLayout(),
y_expand: true,
x_expand: true,
})
this._pinnedView = this._buildPinnedView()
this._allAppsView = this._buildAllAppsView()
this._searchView = this._buildSearchView()
this._allAppsView.visible = false
this._searchView.visible = false
this._stack.add_child(this._pinnedView)
this._stack.add_child(this._allAppsView)
this._stack.add_child(this._searchView)
outer.add_child(this._stack)
// ── Footer ──────────────────────────────────────────────────────────────
outer.add_child(this._buildFooter())
this._outer = outer
this.add_child(outer)
}
_buildPinnedView() {
let view = new St.BoxLayout({
vertical: true,
x_expand: true,
y_expand: true,
})
let header = new St.BoxLayout({ style_class: 'win10-section-header' })
header.add_child(
new St.Label({
text: _('Pinned'),
style_class: 'win10-section-label',
x_expand: true,
y_align: Clutter.ActorAlign.CENTER,
}),
)
let allAppsBtn = new St.Button({
label: _('All apps '),
style_class: 'win10-link-button',
y_align: Clutter.ActorAlign.CENTER,
can_focus: true,
})
allAppsBtn.connect('clicked', () => this._showView('all'))
header.add_child(allAppsBtn)
view.add_child(header)
let scroll = new St.ScrollView({
style_class: 'win10-apps-scroll',
hscrollbar_policy: St.PolicyType.NEVER,
vscrollbar_policy: St.PolicyType.AUTOMATIC,
y_expand: true,
x_expand: true,
})
this._pinnedGrid = new St.BoxLayout({
style_class: 'win10-app-grid',
vertical: true,
x_expand: true,
})
scroll.add_child(this._pinnedGrid)
view.add_child(scroll)
return view
}
_buildAllAppsView() {
let view = new St.BoxLayout({
vertical: true,
x_expand: true,
y_expand: true,
})
let header = new St.BoxLayout({ style_class: 'win10-section-header' })
let backBtn = new St.Button({
style_class: 'win10-link-button',
y_align: Clutter.ActorAlign.CENTER,
can_focus: true,
})
let backBox = new St.BoxLayout({ y_align: Clutter.ActorAlign.CENTER })
backBox.add_child(
new St.Icon({
icon_name: 'go-previous-symbolic',
icon_size: 14,
style_class: 'win10-back-icon',
}),
)
backBox.add_child(
new St.Label({ text: _('Back'), y_align: Clutter.ActorAlign.CENTER }),
)
backBtn.set_child(backBox)
backBtn.connect('clicked', () => this._showView('pinned'))
header.add_child(backBtn)
header.add_child(
new St.Label({
text: _('All apps'),
style_class: 'win10-section-label',
x_expand: true,
y_align: Clutter.ActorAlign.CENTER,
}),
)
view.add_child(header)
let scroll = new St.ScrollView({
style_class: 'win10-apps-scroll',
hscrollbar_policy: St.PolicyType.NEVER,
vscrollbar_policy: St.PolicyType.AUTOMATIC,
y_expand: true,
x_expand: true,
})
this._allAppsList = new St.BoxLayout({ vertical: true, x_expand: true })
scroll.add_child(this._allAppsList)
view.add_child(scroll)
return view
}
_buildSearchView() {
let view = new St.BoxLayout({
vertical: true,
x_expand: true,
y_expand: true,
})
view.add_child(
new St.Label({
text: _('Search results'),
style_class: 'win10-section-label',
style: 'margin: 8px 12px 4px;',
}),
)
let scroll = new St.ScrollView({
style_class: 'win10-apps-scroll',
hscrollbar_policy: St.PolicyType.NEVER,
vscrollbar_policy: St.PolicyType.AUTOMATIC,
y_expand: true,
x_expand: true,
})
this._searchList = new St.BoxLayout({ vertical: true, x_expand: true })
scroll.add_child(this._searchList)
view.add_child(scroll)
return view
}
_buildFooter() {
let footer = new St.BoxLayout({
style_class: 'win10-footer',
x_expand: true,
})
// User button (left)
let userBtn = new St.Button({
style_class: 'win10-footer-button',
x_expand: true,
x_align: Clutter.ActorAlign.START,
can_focus: true,
})
let userBox = new St.BoxLayout()
userBox.add_child(
new St.Icon({
icon_name: 'avatar-default-symbolic',
icon_size: 20,
style_class: 'win10-footer-icon',
}),
)
let realName = GLib.get_real_name() || ''
let displayName =
realName && realName !== 'Unknown' ? realName : GLib.get_user_name()
userBox.add_child(
new St.Label({
text: displayName,
style_class: 'win10-footer-label',
y_align: Clutter.ActorAlign.CENTER,
}),
)
userBtn.set_child(userBox)
footer.add_child(userBtn)
// Power button (right)
let powerBtn = new St.Button({
style_class: 'win10-footer-button win10-power-button',
can_focus: true,
})
powerBtn.set_child(
new St.Icon({ icon_name: 'system-shutdown-symbolic', icon_size: 20 }),
)
powerBtn.connect('clicked', () => this._openPowerMenu(powerBtn))
this._powerBtn = powerBtn
footer.add_child(powerBtn)
return footer
}
_openPowerMenu(powerBtn) {
// Toggle: clicking again while open closes it
if (this._powerPopup) {
this._powerPopup.destroy()
return
}
let popup = new St.BoxLayout({
style_class: 'win10-power-popup',
vertical: true,
reactive: true,
})
for (let { label, icon, fn } of [
{
label: _('Lock'),
icon: 'changes-prevent-symbolic',
fn: () => {
this.close()
Util.spawn(['loginctl', 'lock-session'])
},
},
{
label: _('Sign out'),
icon: 'system-log-out-symbolic',
fn: () => Util.spawn(['gnome-session-quit', '--logout']),
},
{
label: _('Sleep'),
icon: 'weather-clear-night-symbolic',
fn: () => {
this.close()
Util.spawn(['systemctl', 'suspend'])
},
},
{
label: _('Restart'),
icon: 'system-reboot-symbolic',
fn: () => Util.spawn(['gnome-session-quit', '--reboot']),
},
{
label: _('Shut down'),
icon: 'system-shutdown-symbolic',
fn: () => Util.spawn(['gnome-session-quit', '--power-off']),
},
]) {
let btn = new St.Button({
style_class: 'win10-power-item',
x_align: Clutter.ActorAlign.FILL,
x_expand: true,
can_focus: true,
})
let box = new St.BoxLayout()
box.add_child(
new St.Icon({
icon_name: icon,
icon_size: 16,
style_class: 'win10-power-icon',
}),
)
box.add_child(
new St.Label({
text: label,
style_class: 'win10-power-label',
y_align: Clutter.ActorAlign.CENTER,
}),
)
btn.set_child(box)
btn.connect('clicked', () => {
popup.destroy()
fn()
})
popup.add_child(btn)
}
Main.uiGroup.add_child(popup)
this._powerPopup = popup
// Position above power button after allocation
let idleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
idleId = 0
if (!popup.get_parent()) return GLib.SOURCE_REMOVE
let [bx, by] = powerBtn.get_transformed_position()
popup.set_position(
Math.round(bx + powerBtn.width - popup.width),
Math.round(by - popup.height),
)
return GLib.SOURCE_REMOVE
})
let grabDisconnected = false
let grabId = global.stage.connect('captured-event', (s, e) => {
if (e.type() === Clutter.EventType.BUTTON_PRESS) {
let src = e.get_source()
if (!popup.contains(src) && src !== powerBtn) {
grabDisconnected = true
global.stage.disconnect(grabId)
if (popup.get_parent()) popup.destroy()
}
}
return Clutter.EVENT_PROPAGATE
})
popup.connect('destroy', () => {
if (idleId) {
GLib.source_remove(idleId)
idleId = 0
}
if (!grabDisconnected) {
grabDisconnected = true
global.stage.disconnect(grabId)
}
this._powerPopup = null
})
}
_populatePinnedGrid() {
this._pinnedGrid.remove_all_children()
let favorites = AppFavorites.getAppFavorites().getFavorites()
let favIds = new Set(favorites.map(a => a.get_id()))
let others = Gio.AppInfo.get_all()
.filter(info => info.should_show() && !favIds.has(info.get_id()))
.map(info => this._appSystem.lookup_app(info.get_id()))
.filter(app => app !== null)
.sort((a, b) => a.get_name().localeCompare(b.get_name()))
let apps = [...favorites, ...others]
let row = null
apps.forEach((app, i) => {
if (i % GRID_COLUMNS === 0) {
row = new St.BoxLayout({ x_expand: true })
this._pinnedGrid.add_child(row)
}
row.add_child(this._makeGridButton(app))
})
}
_populateAllAppsList() {
this._allAppsList.remove_all_children()
let apps = Gio.AppInfo.get_all()
.filter(info => info.should_show())
.map(info => this._appSystem.lookup_app(info.get_id()))
.filter(app => app !== null)
.sort((a, b) => a.get_name().localeCompare(b.get_name()))
let currentLetter = null
for (let app of apps) {
let firstChar = (app.get_name()[0] || '#').toUpperCase()
if (firstChar !== currentLetter) {
currentLetter = firstChar
this._allAppsList.add_child(
new St.Label({ text: firstChar, style_class: 'win10-alpha-sep' }),
)
}
this._allAppsList.add_child(this._makeListButton(app))
}
}
_makeGridButton(app) {
let btn = new St.Button({
style_class: 'win10-grid-app-btn',
width: CELL_SIZE,
height: CELL_SIZE,
reactive: true,
track_hover: true,
can_focus: true,
})
let box = new St.BoxLayout({
vertical: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
})
box.add_child(app.create_icon_texture(APP_ICON_SIZE))
let label = new St.Label({
text: app.get_name(),
style_class: 'win10-grid-app-label',
x_align: Clutter.ActorAlign.CENTER,
})
label.clutter_text.ellipsize = Pango.EllipsizeMode.END
label.clutter_text.line_wrap = false
box.add_child(label)
btn.set_child(box)
btn.connect('clicked', () => {
this.close()
app.activate()
})
return btn
}
_makeListButton(app) {
let btn = new St.Button({
style_class: 'win10-list-app-btn',
x_expand: true,
x_align: Clutter.ActorAlign.FILL,
reactive: true,
track_hover: true,
can_focus: true,
})
let box = new St.BoxLayout({
x_expand: true,
y_align: Clutter.ActorAlign.CENTER,
})
let icon = app.create_icon_texture(24)
box.add_child(icon)
box.add_child(
new St.Label({
text: app.get_name(),
style_class: 'win10-list-app-label',
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
}),
)
btn.set_child(box)
btn.connect('clicked', () => {
this.close()
app.activate()
})
return btn
}
_showView(which) {
this._pinnedView.visible = which === 'pinned'
this._allAppsView.visible = which === 'all'
this._searchView.visible = which === 'search'
if (which === 'all' && this._allAppsList.get_n_children() === 0) {
this._populateAllAppsList()
}
}
_onSearchTextChanged() {
this._timeoutsHandler.add(['searchDebounce', 150, () => {
let query = this._searchEntry.get_text().trim().toLowerCase()
if (query.length === 0) this._showView('pinned')
else this._doSearch(query)
}])
}
_doSearch(query) {
this._searchList.remove_all_children()
let apps = Gio.AppInfo.get_all()
.filter(info => {
if (!info.should_show()) return false
let name = info.get_name().toLowerCase()
let desc = (info.get_description() || '').toLowerCase()
return name.includes(query) || desc.includes(query)
})
.map(info => this._appSystem.lookup_app(info.get_id()))
.filter(app => app !== null)
.sort((a, b) => a.get_name().localeCompare(b.get_name()))
if (apps.length === 0) {
this._searchList.add_child(
new St.Label({
text: _('No results found'),
style_class: 'win10-no-results',
x_align: Clutter.ActorAlign.CENTER,
}),
)
} else {
for (let app of apps) {
this._searchList.add_child(this._makeListButton(app))
}
}
this._showView('search')
}
open(sourceActor) {
if (this.isOpen) return
this.isOpen = true
this._sourceActor = sourceActor || null
this.remove_all_transitions()
this._searchEntry.set_text('')
this._populatePinnedGrid()
this._showView('pinned')
this._updatePosition(sourceActor)
this.visible = true
this.opacity = 0
this.translation_y = 10
this.ease({
opacity: 255,
translation_y: 0,
duration: 180,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
})
// Defer captured-event + focus until after the triggering button-press
// finishes propagating, so we don't catch our own opening click.
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
if (!this.isOpen) return GLib.SOURCE_REMOVE
this._captureId = global.stage.connect(
'captured-event', this._onCapturedEvent.bind(this),
)
// Close when an app window gains focus (Wayland: window clicks bypass stage)
this._focusId = global.display.connect(
'notify::focus-window', () => { if (this.isOpen) this.close() },
)
this._searchEntry.grab_key_focus()
return GLib.SOURCE_REMOVE
})
}
close() {
if (!this.isOpen) return
this.isOpen = false
if (this._captureId) {
global.stage.disconnect(this._captureId)
this._captureId = 0
}
if (this._focusId) {
global.display.disconnect(this._focusId)
this._focusId = 0
}
this._sourceActor = null
this.remove_all_transitions()
if (this._powerPopup) {
this._powerPopup.destroy()
this._powerPopup = null
}
this.ease({
opacity: 0,
translation_y: 10,
duration: 120,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
onComplete: () => {
if (!this.isOpen) this.visible = false
},
})
}
toggle(sourceActor) {
if (this.isOpen) this.close()
else this.open(sourceActor)
}
_updatePosition(sourceActor) {
let monitor = this._dtpPanel.monitor
let panelPos = this._dtpPanel.geom.position
let menuX, menuY
if (sourceActor) {
let [ax, ay] = sourceActor.get_transformed_position()
let aw = sourceActor.width
let ah = sourceActor.height
switch (panelPos) {
case St.Side.BOTTOM:
menuX = ax
menuY = ay - MENU_HEIGHT
break
case St.Side.TOP:
menuX = ax
menuY = ay + ah
break
case St.Side.LEFT:
menuX = ax + aw
menuY = ay
break
case St.Side.RIGHT:
menuX = ax - MENU_WIDTH
menuY = ay
break
default:
menuX = ax
menuY = ay - MENU_HEIGHT
}
} else {
menuX = monitor.x + 4
menuY = monitor.y + monitor.height - MENU_HEIGHT
}
// Clamp to monitor bounds
menuX = Math.max(
monitor.x + 4,
Math.min(menuX, monitor.x + monitor.width - MENU_WIDTH - 4),
)
menuY = Math.max(
monitor.y + 4,
Math.min(menuY, monitor.y + monitor.height - MENU_HEIGHT - 4),
)
this.set_size(MENU_WIDTH, MENU_HEIGHT)
this._outer.set_size(MENU_WIDTH, MENU_HEIGHT)
this.set_position(Math.round(menuX), Math.round(menuY))
}
_onCapturedEvent(_actor, event) {
if (!this.isOpen) return Clutter.EVENT_PROPAGATE
if (event.type() === Clutter.EventType.BUTTON_PRESS) {
let [ex, ey] = event.get_coords()
let [mx, my] = this.get_transformed_position()
if (ex < mx || ex > mx + this.width || ey < my || ey > my + this.height) {
// Let the source actor (show-apps button) handle its own toggle
let src = event.get_source()
if (this._sourceActor && this._sourceActor.contains(src))
return Clutter.EVENT_PROPAGATE
this.close()
}
} else if (event.type() === Clutter.EventType.KEY_PRESS) {
if (event.get_key_symbol() === Clutter.KEY_Escape) {
this.close()
return Clutter.EVENT_STOP
}
}
return Clutter.EVENT_PROPAGATE
}
destroy() {
if (this._captureId) {
global.stage.disconnect(this._captureId)
this._captureId = 0
}
if (this._focusId) {
global.display.disconnect(this._focusId)
this._focusId = 0
}
if (this._powerPopup) {
this._powerPopup.destroy()
this._powerPopup = null
}
if (this._customStylesheet) {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme()
theme.unload_stylesheet(this._customStylesheet)
this._customStylesheet = null
}
this._timeoutsHandler.destroy()
this._signalsHandler.destroy()
super.destroy()
}
},
)

View File

@@ -201,3 +201,173 @@
#uiGroup.br25 .vesperostaskbarMainPanel .panel-button.clock-display .clock {
border-radius: 25px;
}
/* ── Windows 10-style Start Menu ────────────────────────────────────────── */
.win10-start-menu {
background-color: #202020;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
/* Search bar */
.win10-search-entry {
background-color: #2d2d2d;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 4px;
color: #ffffff;
font-size: 13px;
margin: 12px 12px 4px;
padding: 8px 12px;
caret-color: #0078d4;
}
.win10-search-entry:focus {
border-color: #0078d4;
}
/* Section headers */
.win10-section-header {
padding: 8px 12px 4px;
}
.win10-section-label {
color: #b0b0b0;
font-size: 12px;
font-weight: bold;
}
.win10-link-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #b0b0b0;
font-size: 12px;
padding: 4px 8px;
}
.win10-link-button:hover {
background-color: rgba(255, 255, 255, 0.08);
color: #ffffff;
}
.win10-back-icon {
padding-right: 4px;
}
/* App grid (pinned) */
.win10-app-grid {
padding: 4px 0;
}
.win10-grid-app-btn {
background-color: transparent;
border: none;
border-radius: 6px;
padding: 8px 4px;
}
.win10-grid-app-btn:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-grid-app-label {
color: #e0e0e0;
font-size: 11px;
margin-top: 4px;
text-align: center;
/* max-width matches CELL_SIZE so long names are clipped */
max-width: 107px;
}
/* App list (all apps / search results) */
.win10-list-app-btn {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 6px 12px;
}
.win10-list-app-btn:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-list-app-label {
color: #e0e0e0;
font-size: 13px;
margin-left: 10px;
}
.win10-alpha-sep {
color: #0078d4;
font-size: 13px;
font-weight: bold;
padding: 8px 12px 2px;
}
.win10-no-results {
color: #888888;
font-size: 13px;
padding: 24px 12px;
}
/* Footer */
.win10-footer {
background-color: #141414;
border-top: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 0 0 8px 8px;
padding: 4px;
}
.win10-footer-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #c0c0c0;
padding: 8px 12px;
}
.win10-footer-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.win10-footer-icon {
padding-right: 4px;
}
.win10-footer-label {
color: #c0c0c0;
font-size: 13px;
margin-left: 6px;
}
/* Power popup */
.win10-power-popup {
background-color: #1e1e1e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 4px;
min-width: 168px;
}
.win10-power-item {
background-color: transparent;
border: none;
border-radius: 4px;
color: #e0e0e0;
padding: 8px 12px;
}
.win10-power-item:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-power-icon {
padding-right: 2px;
}
.win10-power-label {
font-size: 13px;
margin-left: 8px;
}

View File

@@ -39,6 +39,7 @@ import * as AppIcons from './appIcons.js'
import * as PanelManager from './panelManager.js'
import * as PanelSettings from './panelSettings.js'
import * as Pos from './panelPositions.js'
import * as StartMenu from './startMenu.js'
import * as Utils from './utils.js'
import * as WindowPreview from './windowPreview.js'
import { SETTINGS, tracker } from './extension.js'
@@ -248,6 +249,9 @@ export const Taskbar = class extends EventEmitter {
this._showAppsIcon.icon.setIconSize(this.iconSize)
this._hookUpLabel(this._showAppsIcon, this._showAppsIconWrapper)
this._startMenu = new StartMenu.Win10StartMenu(panel)
this._ignoreShowAppsToggle = false
this._container.add_child(new St.Widget({ width: 0, reactive: false }))
this._container.add_child(this._scrollView)
@@ -396,6 +400,11 @@ export const Taskbar = class extends EventEmitter {
this._waitIdleId = 0
}
if (this._startMenu) {
this._startMenu.destroy()
this._startMenu = null
}
this._timeoutsHandler.destroy()
this.iconAnimator.destroy()
@@ -1300,55 +1309,26 @@ export const Taskbar = class extends EventEmitter {
}
_onShowAppsButtonToggled() {
// Sync the status of the default appButtons. Only if the two statuses are
// different, that means the user interacted with the extension provided
// application button, cutomize the behaviour. Otherwise the shell has changed the
// status (due to the _syncShowAppsButtonToggled function below) and it
// has already performed the desired action.
// Guard against re-entry when we programmatically uncheck the button.
if (this._ignoreShowAppsToggle) return
// Only act when the user interacted with our button (statuses differ).
// When the shell itself changes the status _syncShowAppsButtonToggled
// keeps them in sync, so we don't need to do anything here.
let selector = SearchController
if (
selector._showAppsButton &&
selector._showAppsButton.checked !== this.showAppsButton.checked
) {
// find visible view
if (this.showAppsButton.checked) {
//override escape key to return to the desktop when entering the overview using the showapps button
SearchController._onStageKeyPress = function (actor, event) {
if (
Main.modalCount == 1 &&
event.get_key_symbol() === Clutter.KEY_Escape
) {
this._searchActive ? this.reset() : Main.overview.hide()
// Uncheck the toggle immediately the start menu manages its own
// open/closed state independently of the button's checked property.
this._ignoreShowAppsToggle = true
this.showAppsButton.checked = false
this._ignoreShowAppsToggle = false
return Clutter.EVENT_STOP
}
return Object.getPrototypeOf(this)._onStageKeyPress.call(
this,
actor,
event,
)
}
let overviewHiddenId = Main.overview.connect('hidden', () => {
Main.overview.disconnect(overviewHiddenId)
delete SearchController._onStageKeyPress
})
// force exiting overview if needed
if (!Main.overview._shown) {
this.forcedOverview = true
}
//temporarily use as primary the monitor on which the showapps btn was clicked, this is
//restored by the panel when exiting the overview
this.dtpPanel.panelManager.setFocusedMonitor(this.dtpPanel.monitor)
// Finally show the overview
selector._showAppsButton.checked = true
Main.overview.show(2 /*APP_GRID*/)
this._startMenu.toggle(this.showAppsButton)
} else {
if (this.forcedOverview) {
// force exiting overview if needed

162
src/themes/win10-dark.css Normal file
View File

@@ -0,0 +1,162 @@
/* Windows 10 — Dark */
.win10-start-menu {
background-color: #202020;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.win10-search-entry {
background-color: #2d2d2d;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 4px;
color: #ffffff;
font-size: 13px;
margin: 12px 12px 4px;
padding: 8px 12px;
caret-color: #0078d4;
}
.win10-search-entry:focus {
border-color: #0078d4;
}
.win10-section-header {
padding: 8px 12px 4px;
}
.win10-section-label {
color: #b0b0b0;
font-size: 12px;
font-weight: bold;
}
.win10-link-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #b0b0b0;
font-size: 12px;
padding: 4px 8px;
}
.win10-link-button:hover {
background-color: rgba(255, 255, 255, 0.08);
color: #ffffff;
}
.win10-back-icon {
padding-right: 4px;
}
.win10-app-grid {
padding: 4px 0;
}
.win10-grid-app-btn {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 8px 4px;
}
.win10-grid-app-btn:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-grid-app-label {
color: #e0e0e0;
font-size: 11px;
margin-top: 4px;
text-align: center;
max-width: 107px;
}
.win10-list-app-btn {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 6px 12px;
}
.win10-list-app-btn:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-list-app-label {
color: #e0e0e0;
font-size: 13px;
margin-left: 10px;
}
.win10-alpha-sep {
color: #0078d4;
font-size: 13px;
font-weight: bold;
padding: 8px 12px 2px;
}
.win10-no-results {
color: #888888;
font-size: 13px;
padding: 24px 12px;
}
.win10-footer {
background-color: #141414;
border-top: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 0 0 4px 4px;
padding: 4px;
}
.win10-footer-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #c0c0c0;
padding: 8px 12px;
}
.win10-footer-button:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.win10-footer-icon {
padding-right: 4px;
}
.win10-footer-label {
color: #c0c0c0;
font-size: 13px;
margin-left: 6px;
}
.win10-power-popup {
background-color: #1e1e1e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 4px;
min-width: 168px;
}
.win10-power-item {
background-color: transparent;
border: none;
border-radius: 4px;
color: #e0e0e0;
padding: 8px 12px;
}
.win10-power-item:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.win10-power-icon {
padding-right: 2px;
}
.win10-power-label {
font-size: 13px;
margin-left: 8px;
}

162
src/themes/win10-light.css Normal file
View File

@@ -0,0 +1,162 @@
/* Windows 10 — Light */
.win10-start-menu {
background-color: #f0f0f0;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.12);
}
.win10-search-entry {
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.18);
border-radius: 4px;
color: #1a1a1a;
font-size: 13px;
margin: 12px 12px 4px;
padding: 8px 12px;
caret-color: #0078d4;
}
.win10-search-entry:focus {
border-color: #0078d4;
}
.win10-section-header {
padding: 8px 12px 4px;
}
.win10-section-label {
color: #444444;
font-size: 12px;
font-weight: bold;
}
.win10-link-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #444444;
font-size: 12px;
padding: 4px 8px;
}
.win10-link-button:hover {
background-color: rgba(0, 0, 0, 0.07);
color: #1a1a1a;
}
.win10-back-icon {
padding-right: 4px;
}
.win10-app-grid {
padding: 4px 0;
}
.win10-grid-app-btn {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 8px 4px;
}
.win10-grid-app-btn:hover {
background-color: rgba(0, 0, 0, 0.07);
}
.win10-grid-app-label {
color: #1a1a1a;
font-size: 11px;
margin-top: 4px;
text-align: center;
max-width: 107px;
}
.win10-list-app-btn {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 6px 12px;
}
.win10-list-app-btn:hover {
background-color: rgba(0, 0, 0, 0.07);
}
.win10-list-app-label {
color: #1a1a1a;
font-size: 13px;
margin-left: 10px;
}
.win10-alpha-sep {
color: #0078d4;
font-size: 13px;
font-weight: bold;
padding: 8px 12px 2px;
}
.win10-no-results {
color: #666666;
font-size: 13px;
padding: 24px 12px;
}
.win10-footer {
background-color: #dcdcdc;
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 0 0 4px 4px;
padding: 4px;
}
.win10-footer-button {
background-color: transparent;
border: none;
border-radius: 4px;
color: #2a2a2a;
padding: 8px 12px;
}
.win10-footer-button:hover {
background-color: rgba(0, 0, 0, 0.08);
color: #000000;
}
.win10-footer-icon {
padding-right: 4px;
}
.win10-footer-label {
color: #2a2a2a;
font-size: 13px;
margin-left: 6px;
}
.win10-power-popup {
background-color: #e8e8e8;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
padding: 4px;
min-width: 168px;
}
.win10-power-item {
background-color: transparent;
border: none;
border-radius: 4px;
color: #1a1a1a;
padding: 8px 12px;
}
.win10-power-item:hover {
background-color: rgba(0, 0, 0, 0.07);
}
.win10-power-icon {
padding-right: 2px;
}
.win10-power-label {
font-size: 13px;
margin-left: 8px;
}

165
src/themes/win11-dark.css Normal file
View File

@@ -0,0 +1,165 @@
/* Windows 11 — Dark
* Approximates the Win11 Mica/acrylic aesthetic: rounded corners,
* layered semi-transparent surfaces, and the lighter blue accent. */
.win10-start-menu {
background-color: #272727;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.win10-search-entry {
background-color: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
color: #ffffff;
font-size: 13px;
margin: 14px 14px 6px;
padding: 9px 16px;
caret-color: #60cdff;
}
.win10-search-entry:focus {
border-color: #60cdff;
background-color: rgba(255, 255, 255, 0.09);
}
.win10-section-header {
padding: 10px 14px 4px;
}
.win10-section-label {
color: #a0a0a0;
font-size: 12px;
font-weight: bold;
}
.win10-link-button {
background-color: transparent;
border: none;
border-radius: 6px;
color: #a0a0a0;
font-size: 12px;
padding: 5px 10px;
}
.win10-link-button:hover {
background-color: rgba(255, 255, 255, 0.07);
color: #ffffff;
}
.win10-back-icon {
padding-right: 4px;
}
.win10-app-grid {
padding: 4px 2px;
}
.win10-grid-app-btn {
background-color: transparent;
border: none;
border-radius: 8px;
padding: 10px 4px;
}
.win10-grid-app-btn:hover {
background-color: rgba(255, 255, 255, 0.07);
}
.win10-grid-app-label {
color: #e8e8e8;
font-size: 11px;
margin-top: 5px;
text-align: center;
max-width: 107px;
}
.win10-list-app-btn {
background-color: transparent;
border: none;
border-radius: 6px;
padding: 7px 14px;
}
.win10-list-app-btn:hover {
background-color: rgba(255, 255, 255, 0.07);
}
.win10-list-app-label {
color: #e8e8e8;
font-size: 13px;
margin-left: 12px;
}
.win10-alpha-sep {
color: #60cdff;
font-size: 13px;
font-weight: bold;
padding: 10px 14px 2px;
}
.win10-no-results {
color: #777777;
font-size: 13px;
padding: 28px 14px;
}
.win10-footer {
background-color: rgba(0, 0, 0, 0.25);
border-top: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 0 0 12px 12px;
padding: 6px;
}
.win10-footer-button {
background-color: transparent;
border: none;
border-radius: 6px;
color: #c8c8c8;
padding: 9px 14px;
}
.win10-footer-button:hover {
background-color: rgba(255, 255, 255, 0.09);
color: #ffffff;
}
.win10-footer-icon {
padding-right: 4px;
}
.win10-footer-label {
color: #c8c8c8;
font-size: 13px;
margin-left: 6px;
}
.win10-power-popup {
background-color: #2f2f2f;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 6px;
min-width: 176px;
}
.win10-power-item {
background-color: transparent;
border: none;
border-radius: 6px;
color: #e8e8e8;
padding: 9px 14px;
}
.win10-power-item:hover {
background-color: rgba(255, 255, 255, 0.07);
}
.win10-power-icon {
padding-right: 2px;
}
.win10-power-label {
font-size: 13px;
margin-left: 8px;
}

165
src/themes/win11-light.css Normal file
View File

@@ -0,0 +1,165 @@
/* Windows 11 — Light
* Approximates the Win11 Mica/acrylic aesthetic: rounded corners,
* layered semi-transparent surfaces, and the standard blue accent. */
.win10-start-menu {
background-color: #f3f3f3;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.win10-search-entry {
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 20px;
color: #1a1a1a;
font-size: 13px;
margin: 14px 14px 6px;
padding: 9px 16px;
caret-color: #0067c0;
}
.win10-search-entry:focus {
border-color: #0067c0;
background-color: #ffffff;
}
.win10-section-header {
padding: 10px 14px 4px;
}
.win10-section-label {
color: #555555;
font-size: 12px;
font-weight: bold;
}
.win10-link-button {
background-color: transparent;
border: none;
border-radius: 6px;
color: #555555;
font-size: 12px;
padding: 5px 10px;
}
.win10-link-button:hover {
background-color: rgba(0, 0, 0, 0.06);
color: #1a1a1a;
}
.win10-back-icon {
padding-right: 4px;
}
.win10-app-grid {
padding: 4px 2px;
}
.win10-grid-app-btn {
background-color: transparent;
border: none;
border-radius: 8px;
padding: 10px 4px;
}
.win10-grid-app-btn:hover {
background-color: rgba(0, 0, 0, 0.06);
}
.win10-grid-app-label {
color: #1a1a1a;
font-size: 11px;
margin-top: 5px;
text-align: center;
max-width: 107px;
}
.win10-list-app-btn {
background-color: transparent;
border: none;
border-radius: 6px;
padding: 7px 14px;
}
.win10-list-app-btn:hover {
background-color: rgba(0, 0, 0, 0.06);
}
.win10-list-app-label {
color: #1a1a1a;
font-size: 13px;
margin-left: 12px;
}
.win10-alpha-sep {
color: #0067c0;
font-size: 13px;
font-weight: bold;
padding: 10px 14px 2px;
}
.win10-no-results {
color: #888888;
font-size: 13px;
padding: 28px 14px;
}
.win10-footer {
background-color: rgba(0, 0, 0, 0.05);
border-top: 1px solid rgba(0, 0, 0, 0.07);
border-radius: 0 0 12px 12px;
padding: 6px;
}
.win10-footer-button {
background-color: transparent;
border: none;
border-radius: 6px;
color: #2a2a2a;
padding: 9px 14px;
}
.win10-footer-button:hover {
background-color: rgba(0, 0, 0, 0.07);
color: #000000;
}
.win10-footer-icon {
padding-right: 4px;
}
.win10-footer-label {
color: #2a2a2a;
font-size: 13px;
margin-left: 6px;
}
.win10-power-popup {
background-color: #ebebeb;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding: 6px;
min-width: 176px;
}
.win10-power-item {
background-color: transparent;
border: none;
border-radius: 6px;
color: #1a1a1a;
padding: 9px 14px;
}
.win10-power-item:hover {
background-color: rgba(0, 0, 0, 0.06);
}
.win10-power-icon {
padding-right: 2px;
}
.win10-power-label {
font-size: 13px;
margin-left: 8px;
}