extension.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. // extension.js
  2. // GPLv3
  3. const Clutter = imports.gi.Clutter;
  4. const Gio = imports.gi.Gio;
  5. const St = imports.gi.St;
  6. const Main = imports.ui.main;
  7. const AppDisplay = imports.ui.appDisplay;
  8. const PopupMenu = imports.ui.popupMenu;
  9. const Meta = imports.gi.Meta;
  10. const Mainloop = imports.mainloop;
  11. const ExtensionUtils = imports.misc.extensionUtils;
  12. const Me = ExtensionUtils.getCurrentExtension();
  13. const Convenience = Me.imports.convenience;
  14. const AppfolderDialog = Me.imports.appfolderDialog;
  15. const DragAndDrop = Me.imports.dragAndDrop;
  16. const Gettext = imports.gettext.domain('appfolders-manager');
  17. const _ = Gettext.gettext;
  18. let FOLDER_SCHEMA;
  19. let FOLDER_LIST;
  20. let INIT_TIME;
  21. function init () {
  22. Convenience.initTranslations();
  23. INIT_TIME = getTimeStamp();
  24. }
  25. function getTimeStamp () {
  26. let today = new Date();
  27. let str = today.getDate() + '' + today.getHours() + '' + today.getMinutes()
  28. + '' + today.getSeconds();
  29. return parseInt(str);
  30. }
  31. //------------------------------------------------------------------------------
  32. /* do not edit this section */
  33. function injectToFunction(parent, name, func) {
  34. let origin = parent[name];
  35. parent[name] = function() {
  36. let ret;
  37. ret = origin.apply(this, arguments);
  38. if (ret === undefined)
  39. ret = func.apply(this, arguments);
  40. return ret;
  41. }
  42. return origin;
  43. }
  44. function removeInjection(object, injection, name) {
  45. if (injection[name] === undefined)
  46. delete object[name];
  47. else
  48. object[name] = injection[name];
  49. }
  50. var injections=[];
  51. //------------------------------------------------------------------------------
  52. /* this function injects items (1 or 2 submenus) in AppIconMenu's _redisplay method. */
  53. function injectionInAppsMenus() {
  54. injections['_redisplay'] = injectToFunction(AppDisplay.AppIconMenu.prototype, '_redisplay', function() {
  55. if (Main.overview.viewSelector.getActivePage() == 2
  56. || Main.overview.viewSelector.getActivePage() == 3) {
  57. //ok
  58. } else {
  59. return;
  60. }
  61. this._appendSeparator(); //TODO injecter ailleurs dans le menu?
  62. let mainAppView = Main.overview.viewSelector.appDisplay._views[1].view;
  63. FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
  64. //------------------------------------------------------------------
  65. let addto = new PopupMenu.PopupSubMenuMenuItem(_("Add to"));
  66. let newAppFolder = new PopupMenu.PopupMenuItem('+ ' + _("New AppFolder"));
  67. newAppFolder.connect('activate', () => {
  68. this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
  69. // XXX broken scrolling ??
  70. // We can't popdown the folder immediately because the
  71. // AppDisplay.AppFolderPopup.popdown() method tries to ungrab
  72. // the global focus from the folder's popup actor, which isn't
  73. // having the focus since the menu is still open. Menus' animation
  74. // last ~0.25s so we will wait 0.30s before doing anything.
  75. let a = Mainloop.timeout_add(300, () => {
  76. if (mainAppView._currentPopup) {
  77. mainAppView._currentPopup.popdown();
  78. }
  79. createNewFolder(this._source);
  80. mainAppView._redisplay();
  81. Mainloop.source_remove(a);
  82. });
  83. });
  84. addto.menu.addMenuItem(newAppFolder);
  85. for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
  86. let _folder = FOLDER_LIST[i];
  87. let shouldShow = !isInFolder( this._source.app.get_id(), _folder );
  88. let iFolderSchema = folderSchema(_folder);
  89. let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
  90. if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
  91. shouldShow = true; //TODO ??? et l'exclusion ?
  92. }
  93. if(shouldShow) {
  94. item.connect('activate', () => {
  95. this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
  96. // XXX broken scrolling ??
  97. // We can't popdown the folder immediatly because the
  98. // AppDisplay.AppFolderPopup.popdown() method tries to
  99. // ungrab the global focus from the folder's popup actor,
  100. // which isn't having the focus since the menu is still
  101. // open. Menus' animation last ~0.25s so we will wait 0.30s
  102. // before doing anything.
  103. let a = Mainloop.timeout_add(300, () => {
  104. if (mainAppView._currentPopup) {
  105. mainAppView._currentPopup.popdown();
  106. }
  107. addToFolder(this._source, _folder);
  108. mainAppView._redisplay();
  109. Mainloop.source_remove(a);
  110. });
  111. });
  112. addto.menu.addMenuItem(item);
  113. }
  114. }
  115. this.addMenuItem(addto);
  116. //----------------------------------------------------------------------
  117. let removeFrom = new PopupMenu.PopupSubMenuMenuItem(_("Remove from"));
  118. let shouldShow2 = false;
  119. for (var i = 0 ; i < FOLDER_LIST.length ; i++) {
  120. let _folder = FOLDER_LIST[i];
  121. let appId = this._source.app.get_id();
  122. let shouldShow = isInFolder(appId, _folder);
  123. let iFolderSchema = folderSchema(_folder);
  124. let item = new PopupMenu.PopupMenuItem( AppDisplay._getFolderName(iFolderSchema) );
  125. if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug') ) {
  126. shouldShow = true; //FIXME ??? et l'exclusion ?
  127. }
  128. if(shouldShow) {
  129. item.connect('activate', () => {
  130. this._source._menuManager._grabHelper.ungrab({ actor: this.actor });
  131. // XXX broken scrolling ??
  132. // We can't popdown the folder immediatly because the
  133. // AppDisplay.AppFolderPopup.popdown() method tries to
  134. // ungrab the global focus from the folder's popup actor,
  135. // which isn't having the focus since the menu is still
  136. // open. Menus' animation last ~0.25s so we will wait 0.30s
  137. // before doing anything.
  138. let a = Mainloop.timeout_add(300, () => {
  139. if (mainAppView._currentPopup) {
  140. mainAppView._currentPopup.popdown();
  141. }
  142. removeFromFolder(appId, _folder);
  143. mainAppView._redisplay();
  144. Mainloop.source_remove(a);
  145. });
  146. });
  147. removeFrom.menu.addMenuItem(item);
  148. shouldShow2 = true;
  149. }
  150. }
  151. if (shouldShow2) {
  152. this.addMenuItem(removeFrom);
  153. }
  154. });
  155. }
  156. //------------------------------------------------
  157. function injectionInIcons() {
  158. // Right-click on a FolderIcon launches a new AppfolderDialog
  159. AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
  160. constructor (id, path, parentView) {
  161. super(id, path, parentView);
  162. if (!this.isCustom) {
  163. this.actor.connect('button-press-event', this._onButtonPress.bind(this));
  164. }
  165. this.isCustom = true;
  166. }
  167. _onButtonPress (actor, event) {
  168. let button = event.get_button();
  169. if (button == 3) {
  170. let tmp = new Gio.Settings({
  171. schema_id: 'org.gnome.desktop.app-folders.folder',
  172. path: '/org/gnome/desktop/app-folders/folders/' + this.id + '/'
  173. });
  174. let dialog = new AppfolderDialog.AppfolderDialog(tmp, null, this.id);
  175. dialog.open();
  176. }
  177. return Clutter.EVENT_PROPAGATE;
  178. }
  179. };
  180. // Dragging an AppIcon triggers the DND mode
  181. AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
  182. constructor (app, params) {
  183. super(app, params);
  184. if (!this.isCustom) {
  185. this._draggable.connect('drag-begin', this.onDragBeginExt.bind(this));
  186. this._draggable.connect('drag-cancelled', this.onDragCancelledExt.bind(this));
  187. this._draggable.connect('drag-end', this.onDragEndExt.bind(this));
  188. }
  189. this.isCustom = true;
  190. }
  191. onDragBeginExt () {
  192. if (Main.overview.viewSelector.getActivePage() != 2) {
  193. return;
  194. }
  195. this._removeMenuTimeout(); // why ?
  196. Main.overview.beginItemDrag(this);
  197. DragAndDrop.OVERLAY_MANAGER.on_drag_begin();
  198. }
  199. onDragEndExt () {
  200. Main.overview.endItemDrag(this);
  201. DragAndDrop.OVERLAY_MANAGER.on_drag_end();
  202. }
  203. onDragCancelledExt () {
  204. Main.overview.cancelledItemDrag(this);
  205. DragAndDrop.OVERLAY_MANAGER.on_drag_cancelled();
  206. }
  207. };
  208. }
  209. //------------------------------------------------------------------------------
  210. //---------------------------------- Generic -----------------------------------
  211. //--------------------------------- functions ----------------------------------
  212. //------------------------------------------------------------------------------
  213. /* These functions perform the requested actions but do not care about popdowning
  214. * open menu/open folder, nor about hiding/showing/activating dropping areas, nor
  215. * about redisplaying the view.
  216. */
  217. function removeFromFolder (app_id, folder_id) {
  218. let folder_schema = folderSchema(folder_id);
  219. if ( isInFolder(app_id, folder_id) ) {
  220. let pastContent = folder_schema.get_strv('apps');
  221. let presentContent = [];
  222. for(var i=0; i<pastContent.length; i++){
  223. if(pastContent[i] != app_id) {
  224. presentContent.push(pastContent[i]);
  225. }
  226. }
  227. folder_schema.set_strv('apps', presentContent);
  228. } else {
  229. let content = folder_schema.get_strv('excluded-apps');
  230. content.push(app_id);
  231. folder_schema.set_strv('excluded-apps', content);
  232. }
  233. return true;
  234. }
  235. //------------------------------------------------------------------------------
  236. function deleteFolder (folder_id) {
  237. Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
  238. let tmp = [];
  239. FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
  240. for(var j=0;j < FOLDER_LIST.length;j++){
  241. if(FOLDER_LIST[j] != folder_id) {
  242. tmp.push(FOLDER_LIST[j]);
  243. }
  244. }
  245. FOLDER_LIST = tmp;
  246. FOLDER_SCHEMA.set_strv('folder-children', FOLDER_LIST);
  247. // ?? XXX (ne fonctionne pas mieux hors du meta.later_add)
  248. if ( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('total-deletion') ) {
  249. let folder_schema = folderSchema (folder_id);
  250. folder_schema.reset('apps'); // génère un bug volumineux ?
  251. folder_schema.reset('categories'); // génère un bug volumineux ?
  252. folder_schema.reset('excluded-apps'); // génère un bug volumineux ?
  253. folder_schema.reset('name'); // génère un bug volumineux ?
  254. }
  255. });
  256. return true;
  257. }
  258. //------------------------------------------------------------------------------
  259. function mergeFolders (folder_staying_id, folder_dying_id) { //unused XXX
  260. let folder_dying_schema = folderSchema (folder_dying_id);
  261. let folder_staying_schema = folderSchema (folder_staying_id);
  262. let newerContent = folder_dying_schema.get_strv('categories');
  263. let presentContent = folder_staying_schema.get_strv('categories');
  264. for(var i=0;i<newerContent.length;i++){
  265. if(presentContent.indexOf(newerContent[i]) == -1) {
  266. presentContent.push(newerContent[i]);
  267. }
  268. }
  269. folder_staying_schema.set_strv('categories', presentContent);
  270. newerContent = folder_dying_schema.get_strv('excluded-apps');
  271. presentContent = folder_staying_schema.get_strv('excluded-apps');
  272. for(var i=0;i<newerContent.length;i++){
  273. if(presentContent.indexOf(newerContent[i]) == -1) {
  274. presentContent.push(newerContent[i]);
  275. }
  276. }
  277. folder_staying_schema.set_strv('excluded-apps', presentContent);
  278. newerContent = folder_dying_schema.get_strv('apps');
  279. presentContent = folder_staying_schema.get_strv('apps');
  280. for(var i=0;i<newerContent.length;i++){
  281. if(presentContent.indexOf(newerContent[i]) == -1) {
  282. // if(!isInFolder(newerContent[i], folder_staying_id)) {
  283. presentContent.push(newerContent[i]);
  284. //TODO utiliser addToFolder malgré ses paramètres chiants
  285. }
  286. }
  287. folder_staying_schema.set_strv('apps', presentContent);
  288. deleteFolder(folder_dying_id);
  289. return true;
  290. }
  291. //------------------------------------------------------------------------------
  292. function createNewFolder (app_source) {
  293. let id = app_source.app.get_id();
  294. let dialog = new AppfolderDialog.AppfolderDialog(null , id);
  295. dialog.open();
  296. return true;
  297. }
  298. //------------------------------------------------------------------------------
  299. function addToFolder (app_source, folder_id) {
  300. let id = app_source.app.get_id();
  301. let folder_schema = folderSchema (folder_id);
  302. //un-exclude the application if it was excluded TODO else don't do it at all
  303. let pastExcluded = folder_schema.get_strv('excluded-apps');
  304. let presentExcluded = [];
  305. for(let i=0; i<pastExcluded.length; i++){
  306. if(pastExcluded[i] != id) {
  307. presentExcluded.push(pastExcluded[i]);
  308. }
  309. }
  310. if (presentExcluded.length > 0) {
  311. folder_schema.set_strv('excluded-apps', presentExcluded);
  312. }
  313. //actually add the app
  314. let content = folder_schema.get_strv('apps');
  315. content.push(id);
  316. folder_schema.set_strv('apps', content); //XXX verbose errors
  317. //update icons in the ugliest possible way
  318. let icons = Main.overview.viewSelector.appDisplay._views[1].view.folderIcons;
  319. for (let i=0; i<icons.length; i++) {
  320. let size = icons[i].icon._iconBin.width;
  321. icons[i].icon.icon = icons[i]._createIcon(size);
  322. icons[i].icon._iconBin.child = icons[i].icon.icon;
  323. }
  324. return true;
  325. }
  326. //------------------------------------------------------------------------------
  327. function isInFolder (app_id, folder_id) {
  328. let folder_schema = folderSchema(folder_id);
  329. let isIn = false;
  330. let content_ = folder_schema.get_strv('apps');
  331. for(var j=0; j<content_.length; j++) {
  332. if(content_[j] == app_id) {
  333. isIn = true;
  334. }
  335. }
  336. return isIn;
  337. }
  338. //------------------------------------------------------------------------------
  339. function folderSchema (folder_id) {
  340. let a = new Gio.Settings({
  341. schema_id: 'org.gnome.desktop.app-folders.folder',
  342. path: '/org/gnome/desktop/app-folders/folders/' + folder_id + '/'
  343. });
  344. return a;
  345. } // TODO et AppDisplay._getFolderName ??
  346. //------------------------------------------------------------------------------
  347. //------------------------------------------------------------------------------
  348. function enable() {
  349. FOLDER_SCHEMA = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
  350. FOLDER_LIST = FOLDER_SCHEMA.get_strv('folder-children');
  351. injectionInIcons();
  352. if( Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('extend-menus') ) {
  353. injectionInAppsMenus();
  354. }
  355. DragAndDrop.initDND();
  356. // Reload the view if the user load the extension at least a minute after
  357. // opening the session. XXX works like shit
  358. let delta = getTimeStamp() - INIT_TIME;
  359. if (delta < 0 || delta > 105) {
  360. Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
  361. }
  362. }
  363. function disable() {
  364. AppDisplay.FolderIcon.prototype._onButtonPress = null;
  365. AppDisplay.FolderIcon.prototype.popupMenu = null;
  366. removeInjection(AppDisplay.AppIconMenu.prototype, injections, '_redisplay');
  367. // Overwrite my shit for FolderIcon
  368. AppDisplay.FolderIcon = class extends AppDisplay.FolderIcon {
  369. _onButtonPress (actor, event) {
  370. return Clutter.EVENT_PROPAGATE;
  371. }
  372. };
  373. // Overwrite my shit for AppIcon
  374. AppDisplay.AppIcon = class extends AppDisplay.AppIcon {
  375. onDragBeginExt () {}
  376. onDragEndExt () {}
  377. onDragCancelledExt () {}
  378. };
  379. DragAndDrop.OVERLAY_MANAGER.destroy();
  380. }
  381. //------------------------------------------------------------------------------