Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ checks:pylint:
stage: checks
before_script:
- sudo dnf install -y python3-gobject gtk3 xorg-x11-server-Xvfb
python3-pip python3-mypy python3-pyxdg gtk-layer-shell
python3-pip python3-mypy python3-pyxdg gtk-layer-shell cairo-devel
- pip3 install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
script:
Expand All @@ -25,7 +25,7 @@ checks:tests:
- "PATH=$PATH:$HOME/.local/bin"
- sudo dnf install -y python3-gobject gtk3 python3-pytest python3-pytest-asyncio
python3-coverage xorg-x11-server-Xvfb python3-inotify sequoia-sqv
python3-pip python3-pyxdg gtk-layer-shell
python3-pip python3-pyxdg gtk-layer-shell cairo-devel
- pip3 install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
- git clone https://github.com/QubesOS/qubes-desktop-linux-manager ~/desktop-linux-manager
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ notes=FIXME,FIX,XXX,TODO
[FORMAT]

# Maximum number of characters on a single line.
max-line-length=80
max-line-length=88

# Maximum number of lines in a module
max-module-lines=3000
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ install-icons:
cp icons/qappmenu-top-right.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-top-right.svg
cp icons/qappmenu-bottom-left.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-bottom-left.svg
cp icons/qappmenu-bottom-right.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-bottom-right.svg
cp icons/qappmenu-az.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-az.svg
cp icons/qappmenu-za.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-za.svg
cp icons/qappmenu-qube-az.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-qube-az.svg
cp icons/qappmenu-qube-za.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-qube-za.svg
cp icons/settings-*.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/

install-autostart:
Expand Down
1 change: 1 addition & 0 deletions icons/qappmenu-az.svg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the icons are swapped - this looks more like a Z-A order. And similarly the ones for qube sorting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

icons for sorting by qube name look disabled (gray, compared to black for sorting by application)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also a bit weird that sorting by both have application arrow in the same direction, but sorting by qube has in opposite for reversed sorting. But that's a minor thing

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions icons/qappmenu-qube-az.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions icons/qappmenu-qube-za.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/qappmenu-za.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 60 additions & 44 deletions qubes_menu/app_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,26 @@
from typing import Optional, List
from functools import reduce

from .custom_widgets import (LimitedWidthLabel, SelfAwareMenu, HoverEventBox,
FavoritesMenu)
from .custom_widgets import (
LimitedWidthLabel,
SelfAwareMenu,
HoverEventBox,
FavoritesMenu,
)
from .desktop_file_manager import ApplicationInfo
from .vm_manager import VMManager, VMEntry
from .utils import load_icon, text_search, highlight_words, remove_from_feature
from . import constants

import gi
gi.require_version('Gtk', '3.0')

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk


logger = logging.getLogger('qubes-appmenu')
logger = logging.getLogger("qubes-appmenu")

DISP_TEXT = 'new Disposable Qube from '
DISP_TEXT = "new Disposable Qube from "


class AppEntry(Gtk.ListBoxRow):
Expand All @@ -53,6 +58,7 @@ class AppEntry(Gtk.ListBoxRow):
- supports running an application on click; after click signals to the
complete menu it might need hiding
"""

def __init__(self, app_info: ApplicationInfo, **properties):
"""
:param app_info: ApplicationInfo obj with data about related app file
Expand All @@ -61,23 +67,21 @@ def __init__(self, app_info: ApplicationInfo, **properties):
super().__init__(**properties)
self.app_info = app_info
self.app_info.entries.append(self)
self.vm_name = app_info.vm.name if app_info.vm else 'dom0'
self.vm_name = app_info.vm.name if app_info.vm else "dom0"

self.menu = SelfAwareMenu()

self.event_box = HoverEventBox(focus_widget=self)
self.add(self.event_box)
self.event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.event_box.connect('button-press-event', self.show_menu)
self.event_box.connect("button-press-event", self.show_menu)

self.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
self.drag_source_add_uri_targets()
self.connect("drag-data-get", self._on_drag_data_get)

def _on_drag_data_get(self, _widget, _drag_context, data, _info, _time):
data.set_uris(['file://' +
urllib.parse.quote(str(self.app_info.file_path))])
data.set_uris(["file://" + urllib.parse.quote(str(self.app_info.file_path))])

def show_menu(self, _widget, event):
"""
Expand All @@ -100,13 +104,17 @@ def run_app(self, vm):
# pylint: disable=consider-using-with
command = self.app_info.get_command_for_vm(vm)
subprocess.Popen(command, stdin=subprocess.DEVNULL)
self.get_toplevel().get_application().emit(
"app-started", self.app_info.file_path.name
)
self.get_toplevel().get_application().hide_menu()


class BaseAppEntry(AppEntry):
"""
A 'normal' Application row, used by main applications menu and system tools.
"""

def __init__(self, app_info: ApplicationInfo, **properties):
"""
:param app_info: ApplicationInfo obj with data about related app file
Expand All @@ -115,7 +123,7 @@ def __init__(self, app_info: ApplicationInfo, **properties):
super().__init__(app_info, **properties)
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.event_box.add(self.box)
self.get_style_context().add_class('app_entry')
self.get_style_context().add_class("app_entry")
self.menu = FavoritesMenu(lambda: self.app_info)

self.icon = Gtk.Image()
Expand All @@ -137,25 +145,29 @@ def show_menu(self, widget, event):
def update_contents(self):
"""Update icon and app name."""
self.icon.set_from_pixbuf(
load_icon(self.app_info.app_icon, Gtk.IconSize.LARGE_TOOLBAR))
load_icon(self.app_info.app_icon, Gtk.IconSize.LARGE_TOOLBAR)
)
self.label.set_label(self.app_info.app_name)
self.show_all()


class VMIcon(Gtk.Image):
"""Helper class for displaying and auto-updating"""

def __init__(self, vm_entry: Optional[VMEntry]):
super().__init__()
self.vm_entry = vm_entry
if self.vm_entry:
self.vm_entry.entries.append(self)
self.update_contents(update_label=True)

def update_contents(self,
update_power_state=False,
update_label=False,
update_has_network=False,
update_type=False):
def update_contents(
self,
update_power_state=False,
update_label=False,
update_has_network=False,
update_type=False,
):
# pylint: disable=unused-argument
"""
Update own contents (or related widgets, if applicable) based on state
Expand All @@ -168,19 +180,18 @@ def update_contents(self,
:return:
"""
if update_label and self.vm_entry:
vm_icon = load_icon(self.vm_entry.vm_icon_name,
Gtk.IconSize.LARGE_TOOLBAR)
vm_icon = load_icon(self.vm_entry.vm_icon_name, Gtk.IconSize.LARGE_TOOLBAR)
self.set_from_pixbuf(vm_icon)
self.show_all()


class AppEntryWithVM(AppEntry):
"""Application Gtk.ListBoxRow with VM description underneath; to be
used in Search and Favorites."""
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
**properties):

def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):
super().__init__(app_info, **properties)
self.get_style_context().add_class('favorite_entry')
self.get_style_context().add_class("favorite_entry")
self.grid = Gtk.Grid()
self.event_box.add(self.grid)

Expand All @@ -192,8 +203,8 @@ def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.pack_start(self.vm_icon, False, False, 5)
box.pack_start(self.vm_label, False, False, 5)
self.vm_label.get_style_context().add_class('favorite_vm_name')
self.app_label.get_style_context().add_class('favorite_app_name')
self.vm_label.get_style_context().add_class("favorite_vm_name")
self.app_label.get_style_context().add_class("favorite_app_name")
self.app_label.set_halign(Gtk.Align.START)

self.grid.attach(self.app_icon, 0, 0, 1, 2)
Expand All @@ -210,8 +221,7 @@ def update_contents(self):
self.app_icon.set_from_pixbuf(app_icon)

if self.app_info.disposable:
self.vm_label.set_text(
DISP_TEXT + str(self.app_info.vm))
self.vm_label.set_text(DISP_TEXT + str(self.app_info.vm))
elif self.app_info.vm:
self.vm_label.set_text(str(self.app_info.vm))
else:
Expand All @@ -228,11 +238,11 @@ class FavoritesAppEntry(AppEntryWithVM):
constants.py, as a space-separated list containing a subset of menu-items
feature.
"""
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
**properties):

def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):
super().__init__(app_info, vm_manager, **properties)
self.remove_item = Gtk.MenuItem(label='Remove from favorites')
self.remove_item.connect('activate', self._remove_from_favorites)
self.remove_item = Gtk.MenuItem(label="Remove from favorites")
self.remove_item.connect("activate", self._remove_from_favorites)
self.menu.add(self.remove_item)
self.menu.show_all()

Expand All @@ -241,16 +251,17 @@ def _remove_from_favorites(self, *_args, **_kwargs):
feature"""
if not self.app_info.entry_name:
return # there is nothing to remove
vm = self.app_info.vm or self.app_info.qapp.domains[
self.app_info.qapp.local_name]
remove_from_feature(vm, constants.FAVORITES_FEATURE,
self.app_info.entry_name)
vm = (
self.app_info.vm
or self.app_info.qapp.domains[self.app_info.qapp.local_name]
)
remove_from_feature(vm, constants.FAVORITES_FEATURE, self.app_info.entry_name)


class SearchAppEntry(AppEntryWithVM):
"""Entry for apps listed on the Search tab."""
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
**properties):

def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):

super().__init__(app_info, vm_manager, **properties)
self.menu = FavoritesMenu(lambda: self.app_info)
Expand All @@ -269,17 +280,21 @@ def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,

if self.app_info.vm:
self.search_words.extend(
self.app_info.vm.name.lower().replace('_', '-').split('-'))
self.app_info.vm.name.lower().replace("_", "-").split("-")
)
else:
self.search_words.append('dom0')
self.search_words.append("dom0")

if self.app_info.disposable:
self.search_words.extend(DISP_TEXT.lower().split())

if self.app_info.app_name:
self.search_words.extend(
self.app_info.app_name.lower().replace(
'_', ' ').replace('-', ' ').split())
self.app_info.app_name.lower()
.replace("_", " ")
.replace("-", " ")
.split()
)

if self.app_info.keywords:
self.search_words.extend(k.lower() for k in self.app_info.keywords)
Expand All @@ -293,9 +308,10 @@ def find_text(self, search_words: List[str]):
return self.last_search_result

if search_words:
result = reduce(lambda x, y: x*y,
[text_search(word, self.search_words)
for word in search_words])
result = reduce(
lambda x, y: x * y,
[text_search(word, self.search_words) for word in search_words],
)
else:
result = 0

Expand Down
Loading