Tracker: store default add-ons shortcuts in resource

* Default shortcuts for add-ons are now stored within the binary as a resource
(it was previously appended to the file name, as Open Terminal-T, for example)
* Use ~/config/shortcuts_settings to override those default shortcuts
(editable with Shortcuts preflet)
* Tracker avoid rescanning the add-ons directories when unnecessary
* Monitor the shortcuts_settings to apply changes on the fly
* Fallback to default shortcuts whenever appropriate (settings file deleted, etc.)
* Should fix #4446 (with resource rather than attributes)
This commit is contained in:
Philippe Saint-Pierre
2013-07-11 12:38:41 -04:00
parent 41e6527c1f
commit d058a4aed2
5 changed files with 308 additions and 206 deletions
+43 -141
View File
@@ -145,6 +145,8 @@ const int32 kWindowStaggerBy = 17;
BRect BContainerWindow::sNewWindRect(85, 50, 548, 280); BRect BContainerWindow::sNewWindRect(85, 50, 548, 280);
LockingList<AddonShortcut>* BContainerWindow::fAddonsList
= new LockingList<struct AddonShortcut>(10, true);
namespace BPrivate { namespace BPrivate {
@@ -165,48 +167,6 @@ ActivateWindowFilter(BMessage*, BHandler** target, BMessageFilter*)
} }
static void
StripShortcut(const Model* model, char* result, uint32 &shortcut)
{
// model name (possibly localized) for the menu item label
strlcpy(result, model->Name(), B_FILE_NAME_LENGTH);
// check if there is a shortcut in the model name
uint32 length = strlen(result);
if (length > 2 && result[length - 2] == '-') {
shortcut = result[length - 1];
result[length - 2] = '\0';
return;
}
// check if there is a shortcut in the filename
char* refName = model->EntryRef()->name;
length = strlen(refName);
if (length > 2 && refName[length - 2] == '-') {
shortcut = refName[length - 1];
return;
}
shortcut = '\0';
}
static const Model*
MatchOne(const Model* model, void* castToName)
{
char buffer[B_FILE_NAME_LENGTH];
uint32 dummy;
StripShortcut(model, buffer, dummy);
if (strcmp(buffer, (const char*)castToName) == 0) {
// found match, bail out
return model;
}
return 0;
}
int int
CompareLabels(const BMenuItem* item1, const BMenuItem* item2) CompareLabels(const BMenuItem* item1, const BMenuItem* item2)
{ {
@@ -217,8 +177,8 @@ CompareLabels(const BMenuItem* item1, const BMenuItem* item2)
static bool static bool
AddOneAddon(const Model* model, const char* name, uint32 shortcut, AddOneAddon(const Model* model, const char* name, uint32 shortcut,
bool primary, void* context) uint32 modifiers, bool primary, void* context)
{ {
AddOneAddonParams* params = (AddOneAddonParams*)context; AddOneAddonParams* params = (AddOneAddonParams*)context;
@@ -226,7 +186,7 @@ AddOneAddon(const Model* model, const char* name, uint32 shortcut,
message->AddRef("refs", model->EntryRef()); message->AddRef("refs", model->EntryRef());
ModelMenuItem* item = new ModelMenuItem(model, name, message, ModelMenuItem* item = new ModelMenuItem(model, name, message,
(char)shortcut, B_OPTION_KEY); (char)shortcut, modifiers);
if (primary) if (primary)
params->primaryList->AddItem(item); params->primaryList->AddItem(item);
@@ -2919,114 +2879,56 @@ BContainerWindow::AddTrashContextMenus(BMenu* menu)
void void
BContainerWindow::EachAddon(bool (*eachAddon)(const Model*, const char*, BContainerWindow::EachAddon(bool (*eachAddon)(const Model*, const char*,
uint32 shortcut, bool primary, void* context), void* passThru, uint32 shortcut, uint32 modifiers, bool primary, void* context),
BObjectList<BString> &mimeTypes) void* passThru, BObjectList<BString> &mimeTypes)
{ {
BObjectList<Model> uniqueList(10, true); AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
BPath path; if (lock.IsLocked()) {
bool bail = false; for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) {
if (find_directory(B_BEOS_ADDONS_DIRECTORY, &path) == B_OK) struct AddonShortcut* item = fAddonsList->ItemAt(i);
bail = EachAddon(path, eachAddon, &uniqueList, passThru, mimeTypes); bool primary = false;
if (!bail && find_directory(B_USER_ADDONS_DIRECTORY, &path) == B_OK) if (mimeTypes.CountItems()) {
bail = EachAddon(path, eachAddon, &uniqueList, passThru, mimeTypes); BFile file(item->model->EntryRef(), B_READ_ONLY);
if (file.InitCheck() == B_OK) {
BAppFileInfo info(&file);
if (info.InitCheck() == B_OK) {
bool secondary = true;
if (!bail && find_directory(B_COMMON_ADDONS_DIRECTORY, &path) == B_OK) // does this add-on has types set at all?
EachAddon(path, eachAddon, &uniqueList, passThru, mimeTypes); BMessage message;
} if (info.GetSupportedTypes(&message) == B_OK) {
type_code type;
int32 count;
if (message.GetInfo("types", &type,
&count) == B_OK)
secondary = false;
}
// check all supported types if it has some set
bool if (!secondary) {
BContainerWindow::EachAddon(BPath &path, bool (*eachAddon)(const Model*, for (int32 i = mimeTypes.CountItems();
const char*, uint32 shortcut, bool primary, void*), !primary && i-- > 0;) {
BObjectList<Model>* uniqueList, void* params, BString* type = mimeTypes.ItemAt(i);
BObjectList<BString> &mimeTypes) if (info.IsSupportedType(type->String())) {
{ BMimeType mimeType(type->String());
path.Append("Tracker"); if (info.Supports(&mimeType))
primary = true;
BDirectory dir; else
BEntry entry; secondary = true;
}
if (dir.SetTo(path.Path()) != B_OK)
return false;
dir.Rewind();
while (dir.GetNextEntry(&entry) == B_OK) {
Model* model = new Model(&entry);
if (model->InitCheck() == B_OK && model->IsSymLink()) {
// resolve symlinks
Model* resolved = new Model(model->EntryRef(), true, true);
if (resolved->InitCheck() == B_OK)
model->SetLinkTo(resolved);
else
delete resolved;
}
if (model->InitCheck() != B_OK
|| !model->ResolveIfLink()->IsExecutable()) {
delete model;
continue;
}
// check if it supports at least one of the selected entries
bool primary = false;
if (mimeTypes.CountItems()) {
BFile file(&entry, B_READ_ONLY);
if (file.InitCheck() == B_OK) {
BAppFileInfo info(&file);
if (info.InitCheck() == B_OK) {
bool secondary = true;
// does this add-on has types set at all?
BMessage message;
if (info.GetSupportedTypes(&message) == B_OK) {
type_code type;
int32 count;
if (message.GetInfo("types", &type, &count) == B_OK)
secondary = false;
}
// check all supported types if it has some set
if (!secondary) {
for (int32 i = mimeTypes.CountItems();
!primary && i-- > 0;) {
BString* type = mimeTypes.ItemAt(i);
if (info.IsSupportedType(type->String())) {
BMimeType mimeType(type->String());
if (info.Supports(&mimeType))
primary = true;
else
secondary = true;
} }
} }
}
if (!secondary && !primary) { if (!secondary && !primary)
delete model; continue;
continue;
} }
} }
} }
((eachAddon)(item->model, item->model->Name(), item->key,
item->modifiers, primary, passThru));
} }
char name[B_FILE_NAME_LENGTH];
uint32 key;
StripShortcut(model, name, key);
// do a uniqueness check
if (uniqueList->EachElement(MatchOne, name)) {
// found one already in the list
delete model;
continue;
}
uniqueList->AddItem(model);
if ((eachAddon)(model, name, key, primary, params))
return true;
} }
return false;
} }
+11 -1
View File
@@ -72,6 +72,13 @@ enum {
kRestoreDecor = 0x4 kRestoreDecor = 0x4
}; };
struct AddonShortcut {
Model* model;
char key;
char defaultKey;
uint32 modifiers;
};
class BContainerWindow : public BWindow { class BContainerWindow : public BWindow {
public: public:
BContainerWindow(LockingList<BWindow>* windowList, BContainerWindow(LockingList<BWindow>* windowList,
@@ -173,7 +180,8 @@ class BContainerWindow : public BWindow {
// add-on iteration // add-on iteration
void EachAddon(bool (*)(const Model*, const char*, uint32 shortcut, void EachAddon(bool (*)(const Model*, const char*, uint32 shortcut,
bool primary, void*), void*, BObjectList<BString> &); uint32 modifiers, bool primary, void*), void*,
BObjectList<BString> &);
BPopUpMenu* ContextMenu(); BPopUpMenu* ContextMenu();
@@ -291,6 +299,8 @@ class BContainerWindow : public BWindow {
uint32 fContainerWindowFlags; uint32 fContainerWindowFlags;
BackgroundImage* fBackgroundImage; BackgroundImage* fBackgroundImage;
static LockingList<struct AddonShortcut>* fAddonsList;
private: private:
BRect fSavedZoomRect; BRect fSavedZoomRect;
BRect fPreviousBounds; BRect fPreviousBounds;
+245 -54
View File
@@ -38,7 +38,9 @@ All rights reserved.
#include <Locale.h> #include <Locale.h>
#include <NodeMonitor.h> #include <NodeMonitor.h>
#include <Path.h> #include <Path.h>
#include <PathMonitor.h>
#include <PopUpMenu.h> #include <PopUpMenu.h>
#include <Resources.h>
#include <Roster.h> #include <Roster.h>
#include <Screen.h> #include <Screen.h>
#include <Volume.h> #include <Volume.h>
@@ -55,6 +57,7 @@ All rights reserved.
#include "DeskWindow.h" #include "DeskWindow.h"
#include "FSUtils.h" #include "FSUtils.h"
#include "IconMenuItem.h" #include "IconMenuItem.h"
#include "KeyInfos.h"
#include "MountMenu.h" #include "MountMenu.h"
#include "PoseView.h" #include "PoseView.h"
#include "Tracker.h" #include "Tracker.h"
@@ -64,47 +67,120 @@ All rights reserved.
const char* kShelfPath = "tracker_shelf"; const char* kShelfPath = "tracker_shelf";
// replicant support // replicant support
const char* kShortcutsSettings = "shortcuts_settings";
const char* kDefaultShortcut = "default_shortcut";
const uint32 kDefaultModifiers = B_OPTION_KEY | B_COMMAND_KEY;
static struct AddonShortcut*
MatchOne(struct AddonShortcut* item, void* castToName)
{
if (strcmp(item->model->Name(), (const char*)castToName) == 0) {
// found match, bail out
return item;
}
return 0;
}
static void static void
WatchAddOnDir(directory_which dirName, BDeskWindow* window) AddOneShortcut(Model* model, char key, uint32 modifiers, BDeskWindow* window)
{
if (key == '\0')
return;
BMessage* runAddon = new BMessage(kLoadAddOn);
runAddon->AddRef("refs", model->EntryRef());
window->AddShortcut(key, modifiers, runAddon);
}
static struct AddonShortcut*
RevertToDefault(struct AddonShortcut* item, void* castToWindow)
{
if (item->key != item->defaultKey || item->modifiers != kDefaultModifiers) {
BDeskWindow* window = static_cast<BDeskWindow*>(castToWindow);
if (window != NULL) {
window->RemoveShortcut(item->key, item->modifiers);
item->key = item->defaultKey;
item->modifiers = kDefaultModifiers;
AddOneShortcut(item->model, item->key, item->modifiers, window);
}
}
return 0;
}
static struct AddonShortcut*
FindElement(struct AddonShortcut* item, void* castToOther)
{
Model* other = static_cast<Model*>(castToOther);
if (*item->model->EntryRef() == *other->EntryRef())
return item;
return 0;
}
static void
LoadAddOnDir(directory_which dirName, BDeskWindow* window,
LockingList<AddonShortcut>* list)
{ {
BPath path; BPath path;
if (find_directory(dirName, &path) == B_OK) { if (find_directory(dirName, &path) == B_OK) {
path.Append("Tracker"); path.Append("Tracker");
BDirectory dir;
BEntry entry;
if (dir.SetTo(path.Path()) != B_OK)
return;
while (dir.GetNextEntry(&entry) == B_OK) {
Model* model = new Model(&entry);
if (model->InitCheck() == B_OK && model->IsSymLink()) {
// resolve symlinks
Model* resolved = new Model(model->EntryRef(), true, true);
if (resolved->InitCheck() == B_OK)
model->SetLinkTo(resolved);
else
delete resolved;
}
if (model->InitCheck() != B_OK
|| !model->ResolveIfLink()->IsExecutable()) {
delete model;
continue;
}
char* name = strdup(model->Name());
if (!list->EachElement(MatchOne, name)) {
struct AddonShortcut* item = new struct AddonShortcut;
item->model = model;
BResources resources(model->ResolveIfLink()->EntryRef());
size_t size;
char* shortcut = (char*)resources.LoadResource(B_STRING_TYPE,
kDefaultShortcut, &size);
if (shortcut == NULL || strlen(shortcut) > 1)
item->key = '\0';
else
item->key = shortcut[0];
AddOneShortcut(model, item->key, kDefaultModifiers, window);
item->defaultKey = item->key;
list->AddItem(item);
}
free(name);
}
BNode node(path.Path()); BNode node(path.Path());
node_ref nodeRef; node_ref nodeRef;
node.GetNodeRef(&nodeRef); node.GetNodeRef(&nodeRef);
TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window); TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window);
} }
} }
struct AddOneShortcutParams {
BDeskWindow* window;
std::set<uint32>* currentAddonShortcuts;
};
static bool
AddOneShortcut(const Model* model, const char*, uint32 shortcut,
bool /*primary*/, void* context)
{
if (!shortcut)
// no shortcut, bail
return false;
AddOneShortcutParams* params = (AddOneShortcutParams*)context;
BMessage* runAddon = new BMessage(kLoadAddOn);
runAddon->AddRef("refs", model->EntryRef());
params->window->AddShortcut(shortcut, B_OPTION_KEY | B_COMMAND_KEY,
runAddon);
params->currentAddonShortcuts->insert(shortcut);
PRINT(("adding new shortcut %c\n", (char)shortcut));
return false;
}
// #pragma mark - // #pragma mark -
#undef B_TRANSLATION_CONTEXT #undef B_TRANSLATION_CONTEXT
@@ -118,7 +194,8 @@ BDeskWindow::BDeskWindow(LockingList<BWindow>* windowList)
| B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS, B_ALL_WORKSPACES), | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS, B_ALL_WORKSPACES),
fDeskShelf(0), fDeskShelf(0),
fTrashContextMenu(0), fTrashContextMenu(0),
fShouldUpdateAddonShortcuts(true) fNodeRef(NULL),
fShortcutsSettings(NULL)
{ {
// Add icon view switching shortcuts. These are displayed in the context // Add icon view switching shortcuts. These are displayed in the context
// menu, although they obviously don't work from those menu items. // menu, although they obviously don't work from those menu items.
@@ -173,43 +250,133 @@ BDeskWindow::Init(const BMessage*)
if (fDeskShelf) if (fDeskShelf)
fDeskShelf->SetDisplaysZombies(true); fDeskShelf->SetDisplaysZombies(true);
} }
InitKeyIndices();
// watch add-on directories so that we can track the addons with InitAddonsList(false);
// corresponding shortcuts ApplyShortcutPreferences(false);
WatchAddOnDir(B_USER_ADDONS_DIRECTORY, this);
WatchAddOnDir(B_COMMON_ADDONS_DIRECTORY, this);
WatchAddOnDir(B_SYSTEM_ADDONS_DIRECTORY, this);
_inherited::Init(); _inherited::Init();
} }
void void
BDeskWindow::MenusBeginning() BDeskWindow::InitAddonsList(bool update)
{ {
_inherited::MenusBeginning(); AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
if (lock.IsLocked()) {
if (fShouldUpdateAddonShortcuts) { if (update) {
PRINT(("updating addon shortcuts\n")); for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) {
fShouldUpdateAddonShortcuts = false; AddonShortcut* item = fAddonsList->ItemAt(i);
RemoveShortcut(item->key, B_OPTION_KEY | B_COMMAND_KEY);
// remove all current addon shortcuts }
for (std::set<uint32>::iterator it= fCurrentAddonShortcuts.begin(); fAddonsList->MakeEmpty(true);
it != fCurrentAddonShortcuts.end(); it++) {
PRINT(("removing shortcut %c\n", (int)*it));
RemoveShortcut(*it, B_OPTION_KEY | B_COMMAND_KEY);
} }
fCurrentAddonShortcuts.clear(); LoadAddOnDir(B_USER_ADDONS_DIRECTORY, this, fAddonsList);
LoadAddOnDir(B_COMMON_ADDONS_DIRECTORY, this, fAddonsList);
LoadAddOnDir(B_SYSTEM_ADDONS_DIRECTORY, this, fAddonsList);
}
}
AddOneShortcutParams params;
params.window = this;
params.currentAddonShortcuts = &fCurrentAddonShortcuts;
BObjectList<BString> mimeTypes(10, true); void
BuildMimeTypeList(mimeTypes); BDeskWindow::ApplyShortcutPreferences(bool update)
{
AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
if (lock.IsLocked()) {
if (!update) {
BPath path;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
BPathMonitor::StartWatching(path.Path(), B_WATCH_STAT | B_WATCH_FILES_ONLY, this);
path.Append(kShortcutsSettings);
fShortcutsSettings = new char[strlen(path.Path()) + 1];
strcpy(fShortcutsSettings, path.Path());
}
}
EachAddon(&AddOneShortcut, &params, mimeTypes); fAddonsList->EachElement(RevertToDefault, this);
BFile shortcutSettings(fShortcutsSettings, B_READ_ONLY);
BMessage fileMsg;
if (shortcutSettings.InitCheck() != B_OK
|| fileMsg.Unflatten(&shortcutSettings) != B_OK) {
fNodeRef = NULL;
return;
}
shortcutSettings.GetNodeRef(fNodeRef);
int i = 0;
BMessage message;
while (fileMsg.FindMessage("spec", i++, &message) == B_OK) {
int32 key;
BMessage actMsg;
if (message.FindInt32("key", &key) == B_OK
&& message.FindMessage("act", &actMsg) == B_OK) {
// only handle shortcuts referring add-ons
BString command;
if (actMsg.FindString("largv", &command) != B_OK)
continue;
BPath path;
bool isInAddons = false;
if (find_directory(B_SYSTEM_ADDONS_DIRECTORY, &path)
== B_OK) {
path.Append("Tracker/");
isInAddons = command.FindFirst(path.Path()) == 0;
}
if (!isInAddons
&& (find_directory(B_COMMON_ADDONS_DIRECTORY, &path)
== B_OK)) {
path.Append("Tracker/");
isInAddons = command.FindFirst(path.Path()) == 0;
}
if (!isInAddons
&& (find_directory(B_USER_ADDONS_DIRECTORY, &path)
== B_OK)) {
path.Append("Tracker/");
isInAddons = command.FindFirst(path.Path()) == 0;
}
if (!isInAddons)
continue;
BEntry entry(command);
if (entry.InitCheck() != B_OK)
continue;
const char* shortcut = GetKeyName(key);
if (strlen(shortcut) != 1)
continue;
uint32 modifiers = 0;
int32 value;
if (message.FindInt32("mcidx", 0, &value) == B_OK)
modifiers |= (value != 0 ? B_SHIFT_KEY : 0);
if (message.FindInt32("mcidx", 1, &value) == B_OK)
modifiers |= (value != 0 ? B_CONTROL_KEY : 0);
if (message.FindInt32("mcidx", 2, &value) == B_OK)
modifiers |= (value != 0 ? B_COMMAND_KEY : 0);
if (message.FindInt32("mcidx", 3, &value) == B_OK)
modifiers |= (value != 0 ? B_OPTION_KEY : 0);
if (modifiers == 0)
modifiers = kDefaultModifiers;
Model model(&entry);
AddonShortcut* item = fAddonsList->EachElement(FindElement,
&model);
if (item != NULL) {
if (item->key != '\0')
RemoveShortcut(item->key, item->modifiers);
item->key = shortcut[0];
item->modifiers = modifiers;
AddOneShortcut(&model, item->key, item->modifiers, this);
}
}
}
} }
} }
@@ -228,6 +395,11 @@ BDeskWindow::Quit()
fNavigationItem = 0; fNavigationItem = 0;
} }
while (fAddonsList->CountItems())
fAddonsList->RemoveItem(0);
delete fAddonsList;
delete fTrashContextMenu; delete fTrashContextMenu;
fTrashContextMenu = NULL; fTrashContextMenu = NULL;
@@ -482,9 +654,28 @@ BDeskWindow::MessageReceived(BMessage* message)
} }
switch (message->what) { switch (message->what) {
case B_PATH_MONITOR:
{
const char* path = "";
if (!(message->FindString("path", &path) == B_OK
&& strcmp(path, fShortcutsSettings) == 0)) {
dev_t device;
ino_t node;
if (fNodeRef == NULL
|| message->FindInt32("device", &device) != B_OK
|| message->FindInt64("node", &node) != B_OK
|| device != fNodeRef->device
|| node != fNodeRef->node)
break;
}
ApplyShortcutPreferences(true);
break;
}
case B_NODE_MONITOR: case B_NODE_MONITOR:
PRINT(("will update addon shortcuts\n")); PRINT(("will update addon shortcuts\n"));
fShouldUpdateAddonShortcuts = true; InitAddonsList(true);
ApplyShortcutPreferences(true);
break; break;
default: default:
+5 -9
View File
@@ -75,24 +75,20 @@ protected:
virtual BPoseView* NewPoseView(Model*, BRect, uint32); virtual BPoseView* NewPoseView(Model*, BRect, uint32);
virtual void WorkspaceActivated(int32, bool); virtual void WorkspaceActivated(int32, bool);
virtual void MenusBeginning();
virtual void MessageReceived(BMessage*); virtual void MessageReceived(BMessage*);
private: private:
void InitAddonsList(bool);
void ApplyShortcutPreferences(bool);
BShelf* fDeskShelf; BShelf* fDeskShelf;
// shelf for replicant support // shelf for replicant support
BPopUpMenu* fTrashContextMenu; BPopUpMenu* fTrashContextMenu;
BRect fOldFrame; BRect fOldFrame;
// in the desktop window addon shortcuts have to be added by AddShortcut node_ref* fNodeRef;
// and we don't always get the MenusBeginning call to check for new char* fShortcutsSettings;
// addons/update the shortcuts -- instead we need to node monitor the
// addon directory and keep a dirty flag that triggers shortcut
// reinstallation
bool fShouldUpdateAddonShortcuts;
std::set<uint32> fCurrentAddonShortcuts;
// keeps track of which shortcuts are installed for Tracker addons
typedef BContainerWindow _inherited; typedef BContainerWindow _inherited;
}; };
+4 -1
View File
@@ -7,6 +7,8 @@ UsePrivateHeaders interface mount shared storage support tracker ;
AddResources libtracker.so : TrackerIcons.rdef libtracker.rdef ; AddResources libtracker.so : TrackerIcons.rdef libtracker.rdef ;
SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src add-ons input_server filters shortcut_catcher ] ;
SubDirC++Flags SubDirC++Flags
-D_BUILDING_tracker=1 -D_BUILDING_tracker=1
# -D_INCLUDES_CLASS_DEVICE_MAP=1 # -D_INCLUDES_CLASS_DEVICE_MAP=1
@@ -89,7 +91,8 @@ SharedLibrary libtracker.so :
VolumeWindow.cpp VolumeWindow.cpp
WidgetAttributeText.cpp WidgetAttributeText.cpp
: be translation $(vector_icon_libs) $(TARGET_LIBSTDC++) $(HAIKU_LOCALE_LIBS) libshared.a : be translation $(vector_icon_libs) $(TARGET_LIBSTDC++) $(HAIKU_LOCALE_LIBS) libshared.a
libshortcuts_shared.a
; ;
DoCatalogs libtracker.so : DoCatalogs libtracker.so :