extension.js 33 KB

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