extension.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. /*
  2. Copyright (C) 2013 Borsato Ivano
  3. The JavaScript code in this page is free software: you can
  4. redistribute it and/or modify it under the terms of the GNU
  5. General Public License (GNU GPL) as published by the Free Software
  6. Foundation, either version 3 of the License, or (at your option)
  7. any later version. The code is distributed WITHOUT ANY WARRANTY;
  8. without even the implied warranty of MERCHANTABILITY or FITNESS
  9. FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
  10. */
  11. 'use strict';
  12. import GObject from 'gi://GObject';
  13. import St from 'gi://St';
  14. import Meta from 'gi://Meta';
  15. import Shell from 'gi://Shell';
  16. // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/panelMenu.js
  17. import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
  18. import Clutter from 'gi://Clutter';
  19. import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
  20. import * as Slider from 'resource:///org/gnome/shell/ui/slider.js';
  21. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  22. import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
  23. import * as Lib from './convenience.js';
  24. import * as Settings from './settings.js';
  25. import * as Time from './timer.js';
  26. import * as UtilRecorder from './utilrecorder.js';
  27. import * as UtilAudio from './utilaudio.js';
  28. import * as UtilWebcam from './utilwebcam.js';
  29. import * as UtilNotify from './utilnotify.js';
  30. import * as Selection from './selection.js';
  31. import * as UtilExeCmd from './utilexecmd.js';
  32. var Indicator;
  33. let timerD = null;
  34. let timerC = null;
  35. let isActive = false;
  36. let pathFile = '';
  37. let keybindingConfigured = false;
  38. /**
  39. * @type {EasyScreenCastIndicator}
  40. */
  41. const EasyScreenCastIndicator = GObject.registerClass({
  42. GTypeName: 'EasyScreenCast_Indicator',
  43. }, class EasyScreenCastIndicator extends PanelMenu.Button {
  44. constructor(extension) {
  45. super(null, 'EasyScreenCast_Indicator');
  46. this._extension = extension;
  47. this._settings = new Settings.Settings(this._extension.getSettings());
  48. Lib.setDebugEnabled(this._settings.getOption('b', Settings.VERBOSE_DEBUG_SETTING_KEY));
  49. this._settings._settings.connect(
  50. `changed::${Settings.VERBOSE_DEBUG_SETTING_KEY}`,
  51. () => {
  52. Lib.setDebugEnabled(this._settings.getOption('b', Settings.VERBOSE_DEBUG_SETTING_KEY));
  53. }
  54. );
  55. this.CtrlAudio = new UtilAudio.MixerAudio();
  56. this.CtrlWebcam = new UtilWebcam.HelperWebcam(_('Unspecified webcam'));
  57. this.CtrlNotify = new UtilNotify.NotifyManager();
  58. this.CtrlExe = new UtilExeCmd.ExecuteStuff(this);
  59. // load indicator icons
  60. this._icons = {
  61. on: Lib.loadIcon(this._extension.dir, 'icon_recording.svg'),
  62. onSel: Lib.loadIcon(this._extension.dir, 'icon_recordingSel.svg'),
  63. off: Lib.loadIcon(this._extension.dir, 'icon_default.svg'),
  64. offSel: Lib.loadIcon(this._extension.dir, 'icon_defaultSel.svg'),
  65. };
  66. // check audio
  67. if (!this.CtrlAudio.checkAudio()) {
  68. Lib.TalkativeLog('-*-disable audio recording');
  69. this._settings.setOption(Settings.INPUT_AUDIO_SOURCE_SETTING_KEY, 0);
  70. this._settings.setOption(
  71. Settings.ACTIVE_CUSTOM_GSP_SETTING_KEY,
  72. Settings.getGSPstd(false)
  73. );
  74. }
  75. // add enter/leave/click event
  76. this.connect('enter_event', () => this.refreshIndicator(true));
  77. this.connect('leave_event', () => this.refreshIndicator(false));
  78. this.connect('button_press_event', (actor, event) =>
  79. this._onButtonPress(actor, event)
  80. );
  81. // prepare setting var
  82. if (this._settings.getOption('i', Settings.TIME_DELAY_SETTING_KEY) > 0)
  83. this.isDelayActive = true;
  84. else
  85. this.isDelayActive = false;
  86. // Add the title bar icon and label for time display
  87. this.indicatorBox = new St.BoxLayout();
  88. this.indicatorIcon = new St.Icon({
  89. gicon: this._icons.off,
  90. icon_size: 16,
  91. });
  92. this.timeLabel = new St.Label({
  93. text: '',
  94. style_class: 'time-label',
  95. y_expand: true,
  96. y_align: Clutter.ActorAlign.CENTER,
  97. });
  98. this.indicatorBox.add_child(this.timeLabel);
  99. this.indicatorBox.add_child(this.indicatorIcon);
  100. // init var
  101. this.recorder = new UtilRecorder.CaptureVideo();
  102. this.AreaSelected = null;
  103. this.TimeSlider = null;
  104. this._initMainMenu();
  105. }
  106. /**
  107. * @private
  108. */
  109. _initMainMenu() {
  110. this._addStartStopMenuEntry();
  111. this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
  112. this.sub_menu_audio_recording = new PopupMenu.PopupSubMenuMenuItem(_('No audio source'), true);
  113. this.sub_menu_audio_recording.icon.icon_name = 'audio-input-microphone-symbolic';
  114. this.menu.addMenuItem(this.sub_menu_audio_recording);
  115. this._addAudioRecordingSubMenu();
  116. // add sub menu webcam recording
  117. this._addSubMenuWebCam();
  118. this._addAreaRecordingSubMenu();
  119. this._addRecordingDelaySubMenu();
  120. this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
  121. this.menu_item_options = new PopupMenu.PopupMenuItem(_('Options'));
  122. this.menu_item_options.actor.insert_child_at_index(
  123. new St.Icon({
  124. style_class: 'popup-menu-icon',
  125. icon_name: 'preferences-other-symbolic',
  126. }),
  127. 1
  128. );
  129. this.menu.addMenuItem(this.menu_item_options);
  130. this.menu_item_options.connect('activate', () => this._openExtensionPreferences());
  131. }
  132. /**
  133. * Set a new value for the time label. Integers are
  134. * converted to seconds, minutes, hours. All other
  135. * values are converted to strings.
  136. *
  137. * @param {string|number} newValue new value of the label. if a number, then it's the seconds passed.
  138. * @returns {string}
  139. */
  140. updateTimeLabel(newValue) {
  141. /**
  142. * @param {number} number a number
  143. */
  144. function padZeros(number) {
  145. if (number < 10)
  146. number = `0${number}`;
  147. return number.toString();
  148. }
  149. if (typeof newValue === 'number') {
  150. let hours = Math.floor(newValue / 3600);
  151. newValue -= hours * 3600;
  152. let minutes = Math.floor(newValue / 60);
  153. newValue -= minutes * 60;
  154. newValue = `${padZeros(hours)}:${padZeros(minutes)}:${padZeros(newValue)}`;
  155. }
  156. this.timeLabel.set_text(newValue.toString());
  157. }
  158. /**
  159. * Left clicking on the icon toggles the recording
  160. * options menu. Any other mouse button will start
  161. * the recording.
  162. * Some submenus are refreshed to account for new
  163. * sources.
  164. *
  165. * @param {Clutter.Actor} actor the actor
  166. * @param {Clutter.Event} event a Clutter.Event
  167. */
  168. _onButtonPress(actor, event) {
  169. let button = event.get_button();
  170. if (button === 1) {
  171. Lib.TalkativeLog('-*-left click indicator');
  172. this._setupExtensionMenu();
  173. } else {
  174. Lib.TalkativeLog('-*-right click indicator');
  175. if (this.menu.isOpen)
  176. this.menu.close();
  177. this.isShowNotify = this._settings.getOption('b', Settings.SHOW_NOTIFY_ALERT_SETTING_KEY);
  178. this._doRecording();
  179. }
  180. }
  181. /**
  182. * Sets up the menu when the user opens it.
  183. */
  184. _setupExtensionMenu() {
  185. this._addAudioRecordingSubMenu();
  186. this._addWebcamSubMenu();
  187. }
  188. /**
  189. * Sets up all the options for web-cams. Should only run the
  190. * first time the icon is clicked an the CtrlWebcam is still
  191. * null.
  192. */
  193. _addWebcamSubMenu() {
  194. if (this.CtrlWebcam === null)
  195. this.CtrlWebcam = new UtilWebcam.HelperWebcam(_('Unspecified webcam'));
  196. // add sub menu webcam recording
  197. this._populateSubMenuWebcam();
  198. // start monitoring inputvideo
  199. this.CtrlWebcam.startMonitor();
  200. }
  201. /**
  202. * Adds individual webcam items to the webcam menu.
  203. */
  204. _populateSubMenuWebcam() {
  205. let arrMI = this._createMIWebCam();
  206. this.smWebCam.menu.removeAll();
  207. for (let element in arrMI)
  208. this.smWebCam.menu.addMenuItem(arrMI[element]);
  209. let i = this._settings.getOption('i', Settings.DEVICE_INDEX_WEBCAM_SETTING_KEY);
  210. Lib.TalkativeLog(`-*-populated submenuwebcam. Settings i=${i}`);
  211. this.smWebCam.label.text = this.WebCamDevice[i];
  212. }
  213. /**
  214. * @private
  215. */
  216. _addStartStopMenuEntry() {
  217. this.imRecordAction = new PopupMenu.PopupBaseMenuItem();
  218. this.RecordingLabel = new St.Label({
  219. text: _('Start recording'),
  220. style_class: 'RecordAction-label',
  221. content_gravity: Clutter.ContentGravity.CENTER,
  222. x_expand: true,
  223. x_align: Clutter.ActorAlign.CENTER,
  224. });
  225. this.imRecordAction.actor.add_child(this.RecordingLabel);
  226. this.imRecordAction.x_expand = true;
  227. this.imRecordAction.x_fill = true;
  228. this.imRecordAction.x_align = Clutter.ActorAlign.CENTER;
  229. this.imRecordAction.connect('activate', () => {
  230. this.isShowNotify = this._settings.getOption('b', Settings.SHOW_NOTIFY_ALERT_SETTING_KEY);
  231. this._doRecording();
  232. });
  233. this.menu.addMenuItem(this.imRecordAction);
  234. }
  235. /**
  236. * Refreshes the submenu for audio recording sources.
  237. */
  238. _addAudioRecordingSubMenu() {
  239. Lib.TalkativeLog('-*-reset the sub menu audio');
  240. // remove old menu items
  241. this.sub_menu_audio_recording.menu.removeAll();
  242. Lib.TalkativeLog('-*-add new items to sub menu audio');
  243. var arrMI = this._createMIAudioRec();
  244. for (var ele in arrMI)
  245. this.sub_menu_audio_recording.menu.addMenuItem(arrMI[ele]);
  246. }
  247. /**
  248. * @private
  249. */
  250. _addSubMenuWebCam() {
  251. this.smWebCam = new PopupMenu.PopupSubMenuMenuItem('', true);
  252. this.smWebCam.icon.icon_name = 'camera-web-symbolic';
  253. this.menu.addMenuItem(this.smWebCam);
  254. }
  255. /**
  256. * @private
  257. */
  258. _addAreaRecordingSubMenu() {
  259. this.sub_menu_area_recording = new PopupMenu.PopupSubMenuMenuItem('', true);
  260. this.sub_menu_area_recording.icon.icon_name = 'view-fullscreen-symbolic';
  261. var arrMI = this._createMIAreaRec();
  262. for (var ele in arrMI)
  263. this.sub_menu_area_recording.menu.addMenuItem(arrMI[ele]);
  264. this.sub_menu_area_recording.label.text = this.AreaType[this._settings.getOption('i', Settings.AREA_SCREEN_SETTING_KEY)];
  265. this.menu.addMenuItem(this.sub_menu_area_recording);
  266. }
  267. /**
  268. * @private
  269. */
  270. _addRecordingDelaySubMenu() {
  271. this.smDelayRec = new PopupMenu.PopupSubMenuMenuItem('', true);
  272. this.smDelayRec.icon.icon_name = 'alarm-symbolic';
  273. var arrMI = this._createMIInfoDelayRec();
  274. for (var ele in arrMI)
  275. this.smDelayRec.menu.addMenuItem(arrMI[ele]);
  276. var secDelay = this._settings.getOption('i', Settings.TIME_DELAY_SETTING_KEY);
  277. if (secDelay > 0) {
  278. this.smDelayRec.label.text =
  279. secDelay + _(' sec. delay before recording');
  280. } else {
  281. this.smDelayRec.label.text = _('Start recording immediately');
  282. }
  283. this.menu.addMenuItem(this.smDelayRec);
  284. }
  285. /**
  286. * @returns {Array}
  287. * @private
  288. */
  289. _createMIAreaRec() {
  290. this.AreaType = [
  291. _('Record all desktop'),
  292. _('Record a selected monitor'),
  293. _('Record a selected window'),
  294. _('Record a selected area'),
  295. ];
  296. this.AreaMenuItem = new Array(this.AreaType.length);
  297. for (var i = 0; i < this.AreaMenuItem.length; i++) {
  298. this.AreaMenuItem[i] = new PopupMenu.PopupMenuItem(
  299. this.AreaType[i],
  300. {
  301. reactive: true,
  302. activate: true,
  303. hover: true,
  304. can_focus: true,
  305. }
  306. );
  307. (function (areaSetting, arr, item, settings) {
  308. this.connectMI = function () {
  309. this.connect('activate', () => {
  310. Lib.TalkativeLog(`-*-set area recording to ${areaSetting} ${arr[areaSetting]}`);
  311. settings.setOption(Settings.AREA_SCREEN_SETTING_KEY, areaSetting);
  312. item.label.text = arr[areaSetting];
  313. });
  314. };
  315. this.connectMI();
  316. }.call(
  317. this.AreaMenuItem[i],
  318. i,
  319. this.AreaType,
  320. this.sub_menu_area_recording,
  321. this._settings
  322. ));
  323. }
  324. return this.AreaMenuItem;
  325. }
  326. /**
  327. * @returns {Array}
  328. * @private
  329. */
  330. _createMIWebCam() {
  331. this.WebCamDevice = [_('No WebCam recording')];
  332. // add menu item webcam device from GST
  333. const devices = this.CtrlWebcam.getDevicesIV();
  334. this.WebCamDevice.push(...this.CtrlWebcam.getNameDevices());
  335. Lib.TalkativeLog(`-*-webcam list: ${this.WebCamDevice}`);
  336. this.AreaMenuItem = new Array(this.WebCamDevice.length);
  337. for (var i = 0; i < this.AreaMenuItem.length; i++) {
  338. let devicePath = '';
  339. // i === 0 is "No Webcam selected"
  340. if (i > 0) {
  341. const device = devices[i - 1];
  342. devicePath = device.get_properties().get_string('device.path');
  343. Lib.TalkativeLog(`-*-webcam i=${i} devicePath: ${devicePath}`);
  344. }
  345. Lib.TalkativeLog(`-*-webcam i=${i} menu-item-text: ${this.WebCamDevice[i]}`);
  346. this.AreaMenuItem[i] = new PopupMenu.PopupMenuItem(
  347. this.WebCamDevice[i],
  348. {
  349. reactive: true,
  350. activate: true,
  351. hover: true,
  352. can_focus: true,
  353. }
  354. );
  355. (function (index, devPath, arr, item, settings) {
  356. this.connectMI = function () {
  357. this.connect('activate', () => {
  358. Lib.TalkativeLog(`-*-set webcam device to ${index} ${arr[index]} devicePath=${devPath}`);
  359. settings.setOption(Settings.DEVICE_INDEX_WEBCAM_SETTING_KEY, index);
  360. settings.setOption(Settings.DEVICE_WEBCAM_SETTING_KEY, devPath);
  361. item.label.text = arr[index];
  362. });
  363. };
  364. this.connectMI();
  365. }.call(
  366. this.AreaMenuItem[i],
  367. i,
  368. devicePath,
  369. this.WebCamDevice,
  370. this.smWebCam,
  371. this._settings
  372. ));
  373. }
  374. return this.AreaMenuItem;
  375. }
  376. /**
  377. * @returns {Array}
  378. * @private
  379. */
  380. _createMIAudioRec() {
  381. // add std menu item
  382. this.AudioChoice = [
  383. {
  384. desc: _('No audio source'),
  385. name: 'N/A',
  386. port: 'N/A',
  387. sortable: true,
  388. resizeable: true,
  389. },
  390. {
  391. desc: _('Default audio source'),
  392. name: 'N/A',
  393. port: 'N/A',
  394. sortable: true,
  395. resizeable: true,
  396. },
  397. ];
  398. // add menu item audio source from PA
  399. var audioList = this.CtrlAudio.getListInputAudio();
  400. for (var index in audioList)
  401. this.AudioChoice.push(audioList[index]);
  402. this.AudioMenuItem = new Array(this.AudioChoice.length);
  403. for (var i = 0; i < this.AudioChoice.length; i++) {
  404. // create label menu
  405. let labelMenu = this.AudioChoice[i].desc;
  406. if (i >= 2) {
  407. labelMenu +=
  408. _('\n - Port: ') +
  409. this.AudioChoice[i].port +
  410. _('\n - Name: ') +
  411. this.AudioChoice[i].name;
  412. }
  413. // create submenu
  414. this.AudioMenuItem[i] = new PopupMenu.PopupMenuItem(labelMenu, {
  415. reactive: true,
  416. activate: true,
  417. hover: true,
  418. can_focus: true,
  419. });
  420. // add icon on submenu
  421. this.AudioMenuItem[i].actor.insert_child_at_index(
  422. new St.Icon({
  423. style_class: 'popup-menu-icon',
  424. icon_name: 'audio-card-symbolic',
  425. }),
  426. 1
  427. );
  428. // update choice audio from pref
  429. if (i === this._settings.getOption('i', Settings.INPUT_AUDIO_SOURCE_SETTING_KEY)) {
  430. Lib.TalkativeLog(`-*-get audio choice from pref ${i}`);
  431. this.sub_menu_audio_recording.label.text = this.AudioChoice[i].desc;
  432. }
  433. // add action on menu item
  434. (function (audioIndex, arr, item, settings) {
  435. this.connectMI = function () {
  436. this.connect('activate', () => {
  437. Lib.TalkativeLog(`-*-set audio choice to ${audioIndex}`);
  438. settings.setOption(Settings.INPUT_AUDIO_SOURCE_SETTING_KEY, audioIndex);
  439. item.label.text = arr[audioIndex].desc;
  440. });
  441. };
  442. this.connectMI();
  443. }.call(
  444. this.AudioMenuItem[i],
  445. i,
  446. this.AudioChoice,
  447. this.sub_menu_audio_recording,
  448. this._settings
  449. ));
  450. }
  451. return this.AudioMenuItem;
  452. }
  453. /**
  454. * @returns {Array}
  455. * @private
  456. */
  457. _createMIInfoDelayRec() {
  458. this.DelayTimeTitle = new PopupMenu.PopupMenuItem(_('Delay Time'), {
  459. reactive: false,
  460. });
  461. this.DelayTimeLabel = new St.Label({
  462. text:
  463. Math.floor(
  464. this._settings.getOption('i', Settings.TIME_DELAY_SETTING_KEY)
  465. ).toString() + _(' Sec'),
  466. });
  467. this.DelayTimeTitle.actor.add_child(this.DelayTimeLabel);
  468. // TODO this.DelayTimeTitle.align = St.Align.END;
  469. this.imSliderDelay = new PopupMenu.PopupBaseMenuItem({
  470. activate: false,
  471. });
  472. this.TimeSlider = new Slider.Slider(this._settings.getOption('i', Settings.TIME_DELAY_SETTING_KEY) / 100);
  473. this.TimeSlider.x_expand = true;
  474. this.TimeSlider.y_expand = true;
  475. this.TimeSlider.connect('notify::value', item => {
  476. this.DelayTimeLabel.set_text(
  477. Math.floor(item.value * 100).toString() + _(' Sec')
  478. );
  479. });
  480. this.TimeSlider.connect('drag-end', () => this._onDelayTimeChanged());
  481. this.TimeSlider.connect('scroll-event', () =>
  482. this._onDelayTimeChanged()
  483. );
  484. this.imSliderDelay.actor.add_child(this.TimeSlider);
  485. return [this.DelayTimeTitle, this.imSliderDelay];
  486. }
  487. /**
  488. * @private
  489. */
  490. _enable() {
  491. // enable key binding
  492. this._enableKeybindings();
  493. // immediately activate/deactive shortcut on settings change
  494. this._settings._settings.connect(
  495. `changed::${Settings.ACTIVE_SHORTCUT_SETTING_KEY}`,
  496. () => {
  497. if (this._settings.getOption('b', Settings.ACTIVE_SHORTCUT_SETTING_KEY)) {
  498. Lib.TalkativeLog('-^-shortcut changed - enabling');
  499. this._enableKeybindings();
  500. } else {
  501. Lib.TalkativeLog('-^-shortcut changed - disabling');
  502. this._removeKeybindings();
  503. }
  504. }
  505. );
  506. // start monitoring inputvideo
  507. this.CtrlWebcam.startMonitor();
  508. // add indicator
  509. this.add_child(this.indicatorBox);
  510. }
  511. /**
  512. * @private
  513. */
  514. _disable() {
  515. // remove key binding
  516. this._removeKeybindings();
  517. // stop monitoring inputvideo
  518. this.CtrlWebcam.stopMonitor();
  519. // unregister mixer control
  520. this.CtrlAudio.destroy();
  521. // remove indicator
  522. this.remove_child(this.indicatorBox);
  523. }
  524. /**
  525. * @private
  526. */
  527. _doDelayAction() {
  528. if (this.isDelayActive) {
  529. Lib.TalkativeLog(`-*-delay recording called | delay= ${this.TimeSlider.value}`);
  530. timerD = new Time.TimerDelay(
  531. Math.floor(this.TimeSlider.value * 100),
  532. this._doPreCommand,
  533. this
  534. );
  535. timerD.begin();
  536. } else {
  537. Lib.TalkativeLog('-*-instant recording called');
  538. // start recording
  539. this._doPreCommand();
  540. }
  541. }
  542. /**
  543. * @private
  544. */
  545. _doPreCommand() {
  546. if (this._settings.getOption('b', Settings.ACTIVE_PRE_CMD_SETTING_KEY)) {
  547. Lib.TalkativeLog('-*-execute pre command');
  548. const PreCmd = this._settings.getOption('s', Settings.PRE_CMD_SETTING_KEY);
  549. this.CtrlExe.Execute(
  550. PreCmd,
  551. false,
  552. res => {
  553. Lib.TalkativeLog(`-*-pre command final: ${res}`);
  554. if (res === true) {
  555. Lib.TalkativeLog('-*-pre command OK');
  556. this.recorder.start();
  557. } else {
  558. Lib.TalkativeLog('-*-pre command ERROR');
  559. this.CtrlNotify.createNotify(
  560. _('ERROR PRE COMMAND - See logs for more info'),
  561. this._icons.off
  562. );
  563. }
  564. },
  565. line => {
  566. Lib.TalkativeLog(`-*-pre command output: ${line}`);
  567. }
  568. );
  569. } else {
  570. this.recorder.start();
  571. }
  572. }
  573. /**
  574. * @private
  575. */
  576. _doRecording() {
  577. // start/stop record screen
  578. if (isActive === false) {
  579. Lib.TalkativeLog('-*-start recording');
  580. pathFile = '';
  581. // get selected area
  582. const optArea = this._settings.getOption('i', Settings.AREA_SCREEN_SETTING_KEY);
  583. if (optArea > 0) {
  584. Lib.TalkativeLog(`-*-type of selection of the area to record: ${optArea}`);
  585. switch (optArea) {
  586. case 3:
  587. new Selection.SelectionArea();
  588. break;
  589. case 2:
  590. new Selection.SelectionWindow();
  591. break;
  592. case 1:
  593. new Selection.SelectionDesktop();
  594. break;
  595. }
  596. } else {
  597. Lib.TalkativeLog('-*-recording full area');
  598. this._doDelayAction();
  599. }
  600. } else {
  601. Lib.TalkativeLog('-*-stop recording');
  602. isActive = false;
  603. this.recorder.stop();
  604. if (timerC !== null) {
  605. // stop counting rec
  606. timerC.halt();
  607. timerC = null;
  608. }
  609. // execute post-command
  610. this._doPostCommand();
  611. }
  612. this.refreshIndicator(false);
  613. }
  614. /**
  615. * @private
  616. */
  617. _doPostCommand() {
  618. if (this._settings.getOption('b', Settings.ACTIVE_POST_CMD_SETTING_KEY)) {
  619. Lib.TalkativeLog('-*-execute post command');
  620. // launch cmd after registration
  621. const tmpCmd = `/usr/bin/sh -c "${this._settings.getOption('s', Settings.POST_CMD_SETTING_KEY)}"`;
  622. const mapObj = {
  623. _fpath: pathFile,
  624. _dirpath: pathFile.substr(0, pathFile.lastIndexOf('/')),
  625. _fname: pathFile.substr(
  626. pathFile.lastIndexOf('/') + 1,
  627. pathFile.length
  628. ),
  629. };
  630. const Cmd = tmpCmd.replace(/_fpath|_dirpath|_fname/gi, match => {
  631. return mapObj[match];
  632. });
  633. Lib.TalkativeLog(`-*-post command:${Cmd}`);
  634. // execute post command
  635. this.CtrlExe.Spawn(Cmd);
  636. }
  637. }
  638. /**
  639. * @param {boolean} result whether the recording was successful
  640. * @param {string} file file path of the recorded file
  641. */
  642. doRecResult(result, file) {
  643. if (result) {
  644. isActive = true;
  645. Lib.TalkativeLog('-*-record OK');
  646. // update indicator
  647. const indicators = this._settings.getOption('i', Settings.STATUS_INDICATORS_SETTING_KEY);
  648. this._replaceStdIndicator(indicators === 1 || indicators === 3);
  649. if (this.isShowNotify) {
  650. Lib.TalkativeLog('-*-show notify');
  651. // create counting notify
  652. this.notifyCounting = this.CtrlNotify.createNotify(
  653. _('Start Recording'),
  654. this._icons.on
  655. );
  656. this.notifyCounting.connect('destroy', () => {
  657. Lib.TalkativeLog('-*-notification destroyed');
  658. this.notifyCounting = null;
  659. });
  660. // start counting rec
  661. timerC = new Time.TimerCounting((secpassed, alertEnd) => {
  662. this._refreshNotify(secpassed, alertEnd);
  663. }, this);
  664. timerC.begin();
  665. }
  666. // update path file video
  667. pathFile = file;
  668. Lib.TalkativeLog(`-*-update abs file path -> ${pathFile}`);
  669. } else {
  670. Lib.TalkativeLog('-*-record ERROR');
  671. pathFile = '';
  672. if (this.isShowNotify) {
  673. Lib.TalkativeLog('-*-show error notify');
  674. this.CtrlNotify.createNotify(
  675. _('ERROR RECORDER - See logs for more info'),
  676. this._icons.off
  677. );
  678. }
  679. }
  680. this.refreshIndicator(false);
  681. }
  682. /**
  683. * @param {number} sec the seconds passed
  684. * @param {boolean} alertEnd whether the timer is ending
  685. */
  686. _refreshNotify(sec, alertEnd) {
  687. if (this.notifyCounting !== null && this.notifyCounting !== undefined && this.isShowNotify) {
  688. if (alertEnd) {
  689. this.CtrlNotify.updateNotify(
  690. this.notifyCounting,
  691. _(`Finish Recording / Seconds : ${sec}`),
  692. this._icons.off,
  693. true
  694. );
  695. } else {
  696. this.CtrlNotify.updateNotify(
  697. this.notifyCounting,
  698. _('Recording ... / Seconds passed : ') + sec,
  699. this._icons.on,
  700. false
  701. );
  702. }
  703. }
  704. }
  705. /**
  706. * @private
  707. */
  708. _openExtensionPreferences() {
  709. try {
  710. this._extension.openPreferences();
  711. } catch (e) {
  712. Lib.TalkativeLog(`Failed to open preferences: ${e}`);
  713. }
  714. }
  715. /**
  716. * @private
  717. */
  718. _onDelayTimeChanged() {
  719. const secDelay = Math.floor(this.TimeSlider.value * 100);
  720. this._settings.setOption(Settings.TIME_DELAY_SETTING_KEY, secDelay);
  721. if (secDelay > 0)
  722. this.smDelayRec.label.text = secDelay + _(' sec. delay before recording');
  723. else
  724. this.smDelayRec.label.text = _('Start recording immediately');
  725. }
  726. /**
  727. * @param {boolean} focus selects the correct icon depending on the focus state
  728. */
  729. refreshIndicator(focus) {
  730. Lib.TalkativeLog(`-*-refresh indicator -A ${isActive} -F ${focus}`);
  731. const indicators = this._settings.getOption('i', Settings.STATUS_INDICATORS_SETTING_KEY);
  732. if (isActive === true) {
  733. if (indicators === 0 || indicators === 1) {
  734. if (focus === true)
  735. this.indicatorIcon.set_gicon(this._icons.onSel);
  736. else
  737. this.indicatorIcon.set_gicon(this._icons.on);
  738. } else if (this._settings.getOption('b', Settings.ACTIVE_SHORTCUT_SETTING_KEY)) {
  739. this.indicatorIcon.set_gicon(null);
  740. } else if (focus === true) {
  741. this.indicatorIcon.set_gicon(this._icons.onSel);
  742. } else {
  743. this.indicatorIcon.set_gicon(this._icons.on);
  744. }
  745. this.RecordingLabel.set_text(_('Stop recording'));
  746. } else {
  747. if (focus === true)
  748. this.indicatorIcon.set_gicon(this._icons.offSel);
  749. else
  750. this.indicatorIcon.set_gicon(this._icons.off);
  751. this.RecordingLabel.set_text(_('Start recording'));
  752. }
  753. }
  754. /**
  755. * @param {boolean} OPTtemp whether to replace the standard indicator or use it
  756. * @private
  757. */
  758. _replaceStdIndicator(OPTtemp) {
  759. if (Main.panel.statusArea === undefined) {
  760. Lib.TalkativeLog('-*-no Main.panel.statusArea found');
  761. return;
  762. }
  763. var stdMenu = Main.panel.statusArea.quickSettings;
  764. if (stdMenu === undefined) {
  765. Lib.TalkativeLog('-*-no quickSettings or aggregateMenu in Main.panel.statusArea');
  766. return;
  767. }
  768. if (stdMenu._remoteAccess === undefined) {
  769. Lib.TalkativeLog('-*-no _remoteAccess indicator applet found');
  770. return;
  771. }
  772. var indicator = stdMenu._remoteAccess._indicator;
  773. if (indicator === undefined) {
  774. Lib.TalkativeLog('-*-no _indicator or _recordingIndicator found');
  775. return;
  776. }
  777. if (OPTtemp) {
  778. Lib.TalkativeLog('-*-replace STD indicator');
  779. indicator.visible = false;
  780. } else {
  781. Lib.TalkativeLog('-*-use STD indicator');
  782. indicator.visible = isActive;
  783. }
  784. }
  785. /**
  786. * @private
  787. */
  788. _enableKeybindings() {
  789. if (this._settings.getOption('b', Settings.ACTIVE_SHORTCUT_SETTING_KEY)) {
  790. Lib.TalkativeLog('-*-enable keybinding');
  791. Main.wm.addKeybinding(
  792. Settings.SHORTCUT_KEY_SETTING_KEY,
  793. this._settings._settings,
  794. Meta.KeyBindingFlags.NONE,
  795. // available modes: https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/src/shell-action-modes.h
  796. Shell.ActionMode.NORMAL |
  797. Shell.ActionMode.OVERVIEW |
  798. Shell.ActionMode.POPUP |
  799. Shell.ActionMode.SYSTEM_MODAL,
  800. () => {
  801. Lib.TalkativeLog('-*-intercept key combination');
  802. this._doRecording();
  803. }
  804. );
  805. keybindingConfigured = true;
  806. }
  807. }
  808. /**
  809. * @private
  810. */
  811. _removeKeybindings() {
  812. if (keybindingConfigured) {
  813. Lib.TalkativeLog('-*-remove keybinding');
  814. Main.wm.removeKeybinding(Settings.SHORTCUT_KEY_SETTING_KEY);
  815. keybindingConfigured = false;
  816. }
  817. }
  818. getSelectedRect() {
  819. var recX = this._settings.getOption('i', Settings.X_POS_SETTING_KEY);
  820. var recY = this._settings.getOption('i', Settings.Y_POS_SETTING_KEY);
  821. var recW = this._settings.getOption('i', Settings.WIDTH_SETTING_KEY);
  822. var recH = this._settings.getOption('i', Settings.HEIGHT_SETTING_KEY);
  823. return [recX, recY, recW, recH];
  824. }
  825. saveSelectedRect(x, y, h, w) {
  826. this._settings.setOption(Settings.X_POS_SETTING_KEY, x);
  827. this._settings.setOption(Settings.Y_POS_SETTING_KEY, y);
  828. this._settings.setOption(Settings.HEIGHT_SETTING_KEY, h);
  829. this._settings.setOption(Settings.WIDTH_SETTING_KEY, w);
  830. }
  831. getSettings() {
  832. return this._settings;
  833. }
  834. getAudioSource() {
  835. return this.CtrlAudio.getAudioSource();
  836. }
  837. /**
  838. * Destroy indicator
  839. */
  840. destroy() {
  841. Lib.TalkativeLog('-*-destroy indicator called');
  842. if (isActive)
  843. isActive = false;
  844. if (this._settings) {
  845. this._settings.destroy();
  846. this._settings = null;
  847. }
  848. super.destroy();
  849. }
  850. });
  851. export default class EasyScreenCast extends Extension {
  852. /**
  853. *
  854. */
  855. enable() {
  856. Lib.TalkativeLog('-*-enableExtension called');
  857. Lib.TalkativeLog(`-*-version: ${this.metadata.version}`);
  858. Lib.TalkativeLog(`-*-install path: ${this.path}`);
  859. Lib.TalkativeLog(`-*-version (package.json): ${Lib.getFullVersion()}`);
  860. if (Indicator === null || Indicator === undefined) {
  861. Lib.TalkativeLog('-*-create indicator');
  862. Indicator = new EasyScreenCastIndicator(this);
  863. Main.panel.addToStatusArea('EasyScreenCast-indicator', Indicator);
  864. }
  865. Indicator._enable();
  866. }
  867. /**
  868. *
  869. */
  870. disable() {
  871. Lib.TalkativeLog('-*-disableExtension called');
  872. if (timerD !== null) {
  873. Lib.TalkativeLog('-*-timerD stopped');
  874. timerD.stop();
  875. timerD = null;
  876. }
  877. // this might happen, if the extension is disabled during recording
  878. if (timerC !== null) {
  879. // stop counting rec
  880. timerC.halt();
  881. timerC = null;
  882. }
  883. if (Indicator !== null) {
  884. Lib.TalkativeLog('-*-indicator call destroy');
  885. Indicator._disable();
  886. Indicator.destroy();
  887. Indicator = null;
  888. }
  889. }
  890. }
  891. export {Indicator};