New menu system for I38 that no longer relies on sgtk-menu.
This commit is contained in:
parent
b813753968
commit
f5b34aa89c
@ -3,7 +3,7 @@
|
|||||||
Accessibility setup script for the i3 window manager.
|
Accessibility setup script for the i3 window manager.
|
||||||
|
|
||||||
## i38.sh
|
## i38.sh
|
||||||
Released under the terms of the WTFPL License: http://www.wtfpl.net
|
Released under the terms of the GPL License Version 3: http://www.wtfpl.net
|
||||||
This is a Stormux project: https://stormux.org
|
This is a Stormux project: https://stormux.org
|
||||||
|
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ An uppercase I looks like a 1, 3 from i3, and 8 because the song [We Are 138](ht
|
|||||||
- ocrdesktop: For getting contents of the current window with OCR.
|
- ocrdesktop: For getting contents of the current window with OCR.
|
||||||
- pamixer: for the mute-unmute script
|
- pamixer: for the mute-unmute script
|
||||||
- playerctl: music controls
|
- playerctl: music controls
|
||||||
|
- python-gobject: for applications menu.
|
||||||
- python-i3ipc: for sounds etc.
|
- python-i3ipc: for sounds etc.
|
||||||
- remind: [optional]For reminder notifications, Requires notify-daemon and notify-send for automatic reminders.
|
- remind: [optional]For reminder notifications, Requires notify-daemon and notify-send for automatic reminders.
|
||||||
- sgtk-menu: for applications menu
|
|
||||||
- sox: for sounds.
|
- sox: for sounds.
|
||||||
- transfersh: [optional] for file sharing GUI
|
- transfersh: [optional] for file sharing GUI
|
||||||
- udiskie: [optional] for automatically mounting removable storage
|
- udiskie: [optional] for automatically mounting removable storage
|
||||||
@ -70,7 +70,7 @@ You can apply the same configuration to GTK2 appss. Create or edit ~/.gtkrc-2.0
|
|||||||
## Usage:
|
## Usage:
|
||||||
|
|
||||||
- With no arguments, create the i3 configuration.
|
- With no arguments, create the i3 configuration.
|
||||||
- -h: This help screen.
|
- -h: Help screen.
|
||||||
- -u: Copy over the latest version of scripts.
|
- -u: Copy over the latest version of scripts.
|
||||||
- -x: Generate ~/.xinitrc and ~/.xprofile.
|
- -x: Generate ~/.xinitrc and ~/.xprofile.
|
||||||
- -X: Generate ~/.xprofile only.
|
- -X: Generate ~/.xprofile only.
|
||||||
|
4
i38.sh
4
i38.sh
@ -19,7 +19,7 @@ sensibleTerminal="i3-sensible-terminal"
|
|||||||
export DIALOGOPTS='--no-lines --visit-items'
|
export DIALOGOPTS='--no-lines --visit-items'
|
||||||
|
|
||||||
# Check to make sure minimum requirements are installed.
|
# Check to make sure minimum requirements are installed.
|
||||||
for i in dialog jq sgtk-menu yad ; do
|
for i in dialog jq yad ; do
|
||||||
if ! command -v "$i" &> /dev/null ; then
|
if ! command -v "$i" &> /dev/null ; then
|
||||||
missing+=("$i")
|
missing+=("$i")
|
||||||
fi
|
fi
|
||||||
@ -463,7 +463,7 @@ bindsym \$mod+Return exec $sensibleTerminal
|
|||||||
bindsym \$mod+F4 kill
|
bindsym \$mod+F4 kill
|
||||||
|
|
||||||
# Applications menu
|
# Applications menu
|
||||||
bindsym \$mod+F1 exec --no-startup-id sgtk-menu -f
|
bindsym \$mod+F1 exec --no-startup-id "${i3Path}/scripts/menu.py"
|
||||||
|
|
||||||
# Desktop icons
|
# Desktop icons
|
||||||
bindsym \$mod+Control+d exec --no-startup-id ${i3Path}/scripts/desktop.sh
|
bindsym \$mod+Control+d exec --no-startup-id ${i3Path}/scripts/desktop.sh
|
||||||
|
167
scripts/menu.py
Executable file
167
scripts/menu.py
Executable file
@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is part of I38.
|
||||||
|
|
||||||
|
# I38 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 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import configparser
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import defaultdict
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
|
def read_desktop_files(paths):
|
||||||
|
desktop_entries = []
|
||||||
|
for path in paths:
|
||||||
|
for file in Path(path).rglob('*.desktop'):
|
||||||
|
config = configparser.ConfigParser(interpolation=None)
|
||||||
|
config.read(file)
|
||||||
|
desktop_entries.append(config)
|
||||||
|
return desktop_entries
|
||||||
|
|
||||||
|
user_applications_path = Path.home() / '.local/share/applications'
|
||||||
|
system_applications_path = Path('/usr/share/applications')
|
||||||
|
|
||||||
|
desktop_entries = read_desktop_files([user_applications_path, system_applications_path])
|
||||||
|
|
||||||
|
# Combine some of the categories
|
||||||
|
category_map = {
|
||||||
|
"2DGraphics": "Office",
|
||||||
|
"Accessibility": "Settings",
|
||||||
|
"Audio": "Audio and Video",
|
||||||
|
"AudioVideo": "Audio and Video",
|
||||||
|
"AudioVideoEditing": "Audio and Video",
|
||||||
|
"ActionGame": "Games",
|
||||||
|
"Building": "Development",
|
||||||
|
"Calendar": "Office",
|
||||||
|
"Chat": "Communication",
|
||||||
|
"Database": "Office",
|
||||||
|
"DesktopSettings": "Settings",
|
||||||
|
"Emulator": "Games",
|
||||||
|
"FlowChart": "Office",
|
||||||
|
"Game": "Games",
|
||||||
|
"HardwareSettings": "Settings",
|
||||||
|
"IDE": "Development",
|
||||||
|
"InstantMessaging": "Communication",
|
||||||
|
"Math": "Education",
|
||||||
|
"Midi": "Audio and Video",
|
||||||
|
"Mixer": "Audio and Video",
|
||||||
|
"Player": "Audio and Video",
|
||||||
|
"Presentation": "Office",
|
||||||
|
"Recorder": "Audio and Video",
|
||||||
|
"Science": "Education",
|
||||||
|
"Spreadsheet": "Office",
|
||||||
|
"Telephony": "Communication",
|
||||||
|
"Terminal": "Utilities",
|
||||||
|
"TerminalEmulator": "Utilities",
|
||||||
|
"TV": "Audio and Video",
|
||||||
|
"Utility": "Utilities",
|
||||||
|
"VectorGraphics": "Office",
|
||||||
|
"Video": "Audio and Video",
|
||||||
|
"WebDevelopment": "Development",
|
||||||
|
"WordProcessor": "Office",
|
||||||
|
"X-Alsa": "Audio and Video",
|
||||||
|
"X-Fedora": "Utilities",
|
||||||
|
"X-Jack": "Audio and Video",
|
||||||
|
"X-LXDE-Settings": "Settings",
|
||||||
|
"X-MATE-NetworkSettings": "Settings",
|
||||||
|
"X-MATE-PersonalSettings": "Settings",
|
||||||
|
"X-Red-Hat-Base": "Utilities",
|
||||||
|
"X-SuSE-Core-Office": "Office",
|
||||||
|
"X-XFCE": "XFCE",
|
||||||
|
"X-XFCE-HardwareSettings": "Settings",
|
||||||
|
"X-XFCE-PersonalSettings": "Settings",
|
||||||
|
"X-XFCE-SettingsDialog": "Settings",
|
||||||
|
"X-Xfce": "XFCE",
|
||||||
|
"X-Xfce-Toplevel": "XFCE",
|
||||||
|
}
|
||||||
|
|
||||||
|
categories = defaultdict(set)
|
||||||
|
for entry in desktop_entries:
|
||||||
|
try:
|
||||||
|
entry_categories = entry.get('Desktop Entry', 'Categories').split(';')
|
||||||
|
for category in entry_categories:
|
||||||
|
combined_category = category_map.get(category, category)
|
||||||
|
name = entry.get('Desktop Entry', 'Name')
|
||||||
|
exec_command = entry.get('Desktop Entry', 'Exec')
|
||||||
|
# Use a tuple of name and exec_command as a unique identifier
|
||||||
|
if (name, exec_command) not in categories[combined_category]:
|
||||||
|
categories[combined_category].add((name, exec_command))
|
||||||
|
except configparser.NoOptionError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
class XdgMenuWindow(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(title="I38 Menu")
|
||||||
|
self.set_default_size(400, 300)
|
||||||
|
self.set_border_width(10)
|
||||||
|
|
||||||
|
self.store = Gtk.TreeStore(str, str) # Columns: Category/Application Name, Exec Command
|
||||||
|
|
||||||
|
for category, entries in categories.items():
|
||||||
|
if category == "":
|
||||||
|
continue
|
||||||
|
category_iter = self.store.append(parent=None, row=[category, None])
|
||||||
|
sorted_entries = sorted(entries, key=lambda e: e[0]) # Sort entries by name
|
||||||
|
for name, exec_command in sorted_entries:
|
||||||
|
self.store.append(parent=category_iter, row=[name, exec_command])
|
||||||
|
|
||||||
|
self.treeview = Gtk.TreeView(model=self.store)
|
||||||
|
renderer = Gtk.CellRendererText()
|
||||||
|
column = Gtk.TreeViewColumn("Applications", renderer, text=0)
|
||||||
|
self.treeview.append_column(column)
|
||||||
|
self.treeview.set_headers_visible(False)
|
||||||
|
|
||||||
|
self.treeview.connect("row-activated", self.on_row_activated)
|
||||||
|
self.treeview.connect("key-press-event", self.on_key_press)
|
||||||
|
|
||||||
|
self.add(self.treeview)
|
||||||
|
self.connect("key-press-event", self.on_key_press)
|
||||||
|
self.treeview.connect("focus-out-event", self.on_focus_out)
|
||||||
|
|
||||||
|
self.treeview.grab_focus()
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def on_row_activated(self, treeview, path, column):
|
||||||
|
model = treeview.get_model()
|
||||||
|
iter = model.get_iter(path)
|
||||||
|
exec_command = model.get_value(iter, 1)
|
||||||
|
if exec_command:
|
||||||
|
os.system(exec_command)
|
||||||
|
|
||||||
|
def on_focus_out(self, widget, event):
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
def on_key_press(self, widget, event):
|
||||||
|
keyval = event.keyval
|
||||||
|
state = event.state
|
||||||
|
if keyval == Gdk.KEY_Escape:
|
||||||
|
Gtk.main_quit()
|
||||||
|
elif state & Gdk.ModifierType.SHIFT_MASK and keyval == Gdk.KEY_Left:
|
||||||
|
path = self.treeview.get_cursor()[0]
|
||||||
|
if path:
|
||||||
|
iter = self.store.get_iter(path)
|
||||||
|
if self.treeview.row_expanded(path):
|
||||||
|
self.treeview.collapse_row(path)
|
||||||
|
else:
|
||||||
|
parent_iter = self.store.iter_parent(iter)
|
||||||
|
if parent_iter:
|
||||||
|
parent_path = self.store.get_path(parent_iter)
|
||||||
|
self.treeview.collapse_row(parent_path)
|
||||||
|
else:
|
||||||
|
self.treeview.collapse_row(path)
|
||||||
|
|
||||||
|
win = XdgMenuWindow()
|
||||||
|
win.connect("destroy", Gtk.main_quit)
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user