Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The button Settings on the dock becomes inaccessible with too many devices or on lower display resolutions #1640

Open
bekopharm opened this issue Jun 12, 2023 · 8 comments
Labels
shell-extension An issue related to the GNOME Shell extension upstream KDE Connect, Android or supporting library UX User Experience

Comments

@bekopharm
Copy link

Describe the bug

The button Settings at the bottom of the expanded device list on the dock becomes inaccessible if too many devices are connected. The expanded list of devices has no scrollbar. The individual devices can not be collapsed to make space.

Workaround is to use the gear symbol in extension-manager to configure GSConnect again. That's a lot of extra clicks though and I use that button a lot to configure which device gets the clipboard synced and which not.

Steps to reproduce

  1. Go to dock
  2. Click on GSConnect to expand connected devices

Expected behavior

A scrollbar should appear on mouse wheel or scroll gesture or listed devices should be able to collapse so the Settings button at the bottom show up again.

GSConnect version

54

Installed from

GNOME Extensions website

GNOME Shell version

43.5

Linux distribution/release

Fedora 37

Paired device(s)

many

KDE Connect app version

any

Plugin(s)

No response

Support log

No response

Screenshots

gnome-shell-screenshot-g3zins

Notes

No response

@github-actions github-actions bot added the triage An issue that needs confirmation and labeling label Jun 12, 2023
@frikisama
Copy link

frikisama commented Jun 29, 2023

You don't even need that many devices, the menu is cut with only one too, if the resolution is low and the menu full enough. Here is a screenshot of my device, with its 1600p display at 2x desktop scale, Gnome 44. I guess 800p would have the same problem.

image

@bekopharm
Copy link
Author

Always boggles my mind that someone would indeed run Gnome on such low resolutions but then I remember that some use this indeed on phones and similar nowadays.

Yes, that's exactly the same problem 👍

@bekopharm bekopharm changed the title The button Settings on the dock becomes inaccessible with too many devices The button Settings on the dock becomes inaccessible with too many devices or on lower display resolutions Jun 29, 2023
@frikisama
Copy link

Oh, this is not a phone, it is just a high DPI display (2560x1600 at 10"), and actually my main device. And I love how crisp and easily readable everything is! But yeah, in some cases, like this one, you see how the focus is on 1080p (or 4k @ 2x scale) and up, currently, and this is just a quick reminder that we may be few, but still here :)

@frikisama
Copy link

Quick update: there is a related issue on the Gnome Shell issue tracker.

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6056

@ferdnyc
Copy link
Member

ferdnyc commented Jul 6, 2023

I thought there was already an older issue open about this, but I can't find it.

As @frikisama notes, it's largely a GNOME issue — the menu should be scrollable if its contents are taller than the screen, but it's not. That aspect, I don't think we could solve in GSConnect.

What might be possible is to make the individual device submenus collapsible so that they take up less space. But unless this is addressed upstream, I think that's really the only possible fix — workaround, really — for this issue.

(And that's assuming it is possible, now that the device menus are children of the QuickSettings item, in GNOME 44. None of the built-in QuickSettings have collapsible submenus, so it's a maybe at best.)

@ferdnyc ferdnyc added upstream KDE Connect, Android or supporting library UX User Experience shell-extension An issue related to the GNOME Shell extension and removed triage An issue that needs confirmation and labeling labels Jul 6, 2023
@bekopharm
Copy link
Author

Collapsing could be possible. I had my ticket overview from Redmine in there and I'm pretty sure it had collapsible entries but that has been years 🤔

@ferdnyc
Copy link
Member

ferdnyc commented Jul 6, 2023

@bekopharm

Mmm, and really I suppose anything is possible, if you're willing to write the code. And given how much of GSConnect's menu support is custom code already, as things stand collapsible device sections would have to be custom-coded too.

I was able to make the entire Devices list collapsible easily enough, by wrapping it in a standard PopupSubMenuMenuItem. (...Who comes up with these names!?) But while collapsing the entire Devices list does ensure you can always get to the Mobile Settings item, it doesn't do a lot of good if you have multiple devices paired that don't fit on the screen, because you can still only reach the first one.

The issue with collapsing the devices separately, though, is that each Device's list of commands (the thing we'd want to collapse beneath its status line) is a custom submenu implementation built entirely in GSConnect, with items that aren't subclassed from the standard PopupMenuItem. That was necessary to implement the panning submenu functionality for commands that had their own individual submenus. (For instance, the Files menu that replaces Mount, after it's activated, in this animation:)

gsconnect-subpanelanim

But the thing is... I'm not sure anything in GSConnect actually uses that functionality, anymore. And if it doesn't, then it may be possible to replace all of that custom code with a set of standard PopupMenuItem entries. In which case, it "should" be relatively simple to move them inside a collapsible PopupSubMenuMenuItem per device.

(The alternative would be, like I said at the start, to add custom collapsibility code on top of GSConnect's custom submenu implementation. And I'd personally prefer this class get shorter and diverge from the standard PopupMenu implementation less, not get longer and reinvent even more of it...

var ListBox = class ListBox extends PopupMenu.PopupMenuSection {
constructor(params) {
super();
Object.assign(this, params);
// Main Actor
this.actor = new St.BoxLayout({
x_expand: true,
clip_to_allocation: true,
});
this.actor._delegate = this;
// Item Box
this.box.clip_to_allocation = true;
this.box.x_expand = true;
this.box.add_style_class_name('gsconnect-list-box');
this.box.set_pivot_point(1, 1);
this.actor.add_child(this.box);
// Submenu Container
this.sub = new St.BoxLayout({
clip_to_allocation: true,
vertical: false,
visible: false,
x_expand: true,
});
this.sub.set_pivot_point(1, 1);
this.sub._delegate = this;
this.actor.add_child(this.sub);
// Handle transitions
this._boxTransitionsCompletedId = this.box.connect(
'transitions-completed',
this._onTransitionsCompleted.bind(this)
);
this._subTransitionsCompletedId = this.sub.connect(
'transitions-completed',
this._onTransitionsCompleted.bind(this)
);
// Handle keyboard navigation
this._submenuCloseKeyId = this.sub.connect(
'key-press-event',
this._onSubmenuCloseKey.bind(this)
);
// Refresh the menu when mapped
this._mappedId = this.actor.connect(
'notify::mapped',
this._onMapped.bind(this)
);
// Watch the model for changes
this._itemsChangedId = this.model.connect(
'items-changed',
this._onItemsChanged.bind(this)
);
this._onItemsChanged();
}
_onMapped(actor) {
if (actor.mapped) {
this._onItemsChanged();
// We use this instead of close() to avoid touching finalized objects
} else {
this.box.set_opacity(255);
this.box.set_width(-1);
this.box.set_height(-1);
this.box.visible = true;
this._submenu = null;
this.sub.set_opacity(0);
this.sub.set_width(0);
this.sub.set_height(0);
this.sub.visible = false;
this.sub.get_children().map(menu => menu.hide());
}
}
_onSubmenuCloseKey(actor, event) {
if (this.submenu && event.get_key_symbol() === Clutter.KEY_Left) {
this.submenu.submenu_for.setActive(true);
this.submenu = null;
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_onSubmenuOpenKey(actor, event) {
const item = actor._delegate;
if (item.submenu && event.get_key_symbol() === Clutter.KEY_Right) {
this.submenu = item.submenu;
item.submenu.firstMenuItem.setActive(true);
}
return Clutter.EVENT_PROPAGATE;
}
_onGMenuItemActivate(item, event) {
this.emit('activate', item);
if (item.submenu) {
this.submenu = item.submenu;
} else if (item.action_name) {
this.action_group.activate_action(
item.action_name,
item.action_target
);
this.itemActivated();
}
}
_addGMenuItem(info) {
const item = new PopupMenu.PopupMenuItem(info.label);
this.addMenuItem(item);
if (info.action !== undefined) {
item.action_name = info.action.split('.')[1];
item.action_target = info.target;
item.actor.visible = this.action_group.get_action_enabled(
item.action_name
);
}
item.connectObject(
'activate',
this._onGMenuItemActivate.bind(this),
this
);
return item;
}
_addGMenuSection(model) {
const section = new ListBox({
model: model,
action_group: this.action_group,
});
this.addMenuItem(section);
}
_addGMenuSubmenu(model, item) {
// Add an expander arrow to the item
const arrow = PopupMenu.arrowIcon(St.Side.RIGHT);
arrow.x_align = Clutter.ActorAlign.END;
arrow.x_expand = true;
item.actor.add_child(arrow);
// Mark it as an expandable and open on right-arrow
item.actor.add_accessible_state(Atk.StateType.EXPANDABLE);
item.actor.connect(
'key-press-event',
this._onSubmenuOpenKey.bind(this)
);
// Create the submenu
item.submenu = new ListBox({
model: model,
action_group: this.action_group,
submenu_for: item,
_parent: this,
});
item.submenu.actor.hide();
// Add to the submenu container
this.sub.add_child(item.submenu.actor);
}
_onItemsChanged(model, position, removed, added) {
// Clear the menu
this.removeAll();
this.sub.get_children().map(child => child.destroy());
for (let i = 0, len = this.model.get_n_items(); i < len; i++) {
const info = getItemInfo(this.model, i);
let item;
// A regular item
if (info.hasOwnProperty('label'))
item = this._addGMenuItem(info);
for (const link of info.links) {
// Submenu
if (link.name === 'submenu') {
this._addGMenuSubmenu(link.value, item);
// Section
} else if (link.name === 'section') {
this._addGMenuSection(link.value);
// len is length starting at 1
if (i + 1 < len)
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
}
}
}
// If this is a submenu of another item...
if (this.submenu_for) {
// Prepend an "<= Go Back" item, bold with a unicode arrow
const prev = new PopupMenu.PopupMenuItem(this.submenu_for.label.text);
prev.label.style = 'font-weight: bold;';
const prevArrow = PopupMenu.arrowIcon(St.Side.LEFT);
prev.replace_child(prev._ornamentLabel, prevArrow);
this.addMenuItem(prev, 0);
prev.connectObject('activate', (item, event) => {
this.emit('activate', item);
this._parent.submenu = null;
}, this);
}
}
_onTransitionsCompleted(actor) {
if (this.submenu) {
this.box.visible = false;
} else {
this.sub.visible = false;
this.sub.get_children().map(menu => menu.hide());
}
}
get submenu() {
return this._submenu || null;
}
set submenu(submenu) {
// Get the current allocation to hold the menu width
const allocation = this.actor.allocation;
const width = Math.max(0, allocation.x2 - allocation.x1);
// Prepare the appropriate child for tweening
if (submenu) {
this.sub.set_opacity(0);
this.sub.set_width(0);
this.sub.set_height(0);
this.sub.visible = true;
} else {
this.box.set_opacity(0);
this.box.set_width(0);
this.sub.set_height(0);
this.box.visible = true;
}
// Setup the animation
this.box.save_easing_state();
this.box.set_easing_mode(Clutter.AnimationMode.EASE_IN_OUT_CUBIC);
this.box.set_easing_duration(250);
this.sub.save_easing_state();
this.sub.set_easing_mode(Clutter.AnimationMode.EASE_IN_OUT_CUBIC);
this.sub.set_easing_duration(250);
if (submenu) {
submenu.actor.show();
this.sub.set_opacity(255);
this.sub.set_width(width);
this.sub.set_height(-1);
this.box.set_opacity(0);
this.box.set_width(0);
this.box.set_height(0);
} else {
this.box.set_opacity(255);
this.box.set_width(width);
this.box.set_height(-1);
this.sub.set_opacity(0);
this.sub.set_width(0);
this.sub.set_height(0);
}
// Reset the animation
this.box.restore_easing_state();
this.sub.restore_easing_state();
//
this._submenu = submenu;
}
destroy() {
this.actor.disconnect(this._mappedId);
this.box.disconnect(this._boxTransitionsCompletedId);
this.sub.disconnect(this._subTransitionsCompletedId);
this.sub.disconnect(this._submenuCloseKeyId);
this.model.disconnect(this._itemsChangedId);
super.destroy();
}
};

Even if the current code does still use that side-sliding submenu implementation, giving it up in exchange for collapsibility may still be a good trade. I think Mount is the only entry that's ever used it, and managing device mounts via the user menu was always a bit clunky.

@tsilvs
Copy link

tsilvs commented Apr 7, 2024

Same on Fedora 39. If more than 3 devices are connected, the overflow list doesn't scroll. Maybe all of the list elements and the list itself could be at least collapsible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
shell-extension An issue related to the GNOME Shell extension upstream KDE Connect, Android or supporting library UX User Experience
Projects
None yet
Development

No branches or pull requests

4 participants