Window.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, Hoa community. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace Hoa\Console;
  36. use Hoa\Event;
  37. /**
  38. * Class \Hoa\Console\Window.
  39. *
  40. * Allow to manipulate the window.
  41. *
  42. * We can listen the event channel hoa://Event/Console/Window:resize to detect
  43. * if the window has been resized. Please, see the constructor documentation to
  44. * get more informations.
  45. *
  46. * @copyright Copyright © 2007-2017 Hoa community
  47. * @license New BSD License
  48. */
  49. class Window implements Event\Source
  50. {
  51. /**
  52. * Singleton (only for events).
  53. *
  54. * @var \Hoa\Console\Window
  55. */
  56. private static $_instance = null;
  57. /**
  58. * Set the event channel.
  59. * We need to declare(ticks = 1) in the main script to ensure that the event
  60. * is fired. Also, we need the pcntl_signal() function enabled.
  61. *
  62. */
  63. private function __construct()
  64. {
  65. Event::register(
  66. 'hoa://Event/Console/Window:resize',
  67. $this
  68. );
  69. return;
  70. }
  71. /**
  72. * Singleton.
  73. *
  74. * @return \Hoa\Console\Window
  75. */
  76. public static function getInstance()
  77. {
  78. if (null === static::$_instance) {
  79. static::$_instance = new static();
  80. }
  81. return static::$_instance;
  82. }
  83. /**
  84. * Set size to X lines and Y columns.
  85. *
  86. * @param int $x X coordinate.
  87. * @param int $y Y coordinate.
  88. * @return void
  89. */
  90. public static function setSize($x, $y)
  91. {
  92. if (OS_WIN) {
  93. return;
  94. }
  95. Console::getOutput()->writeAll("\033[8;" . $y . ";" . $x . "t");
  96. return;
  97. }
  98. /**
  99. * Get current size (x and y) of the window.
  100. *
  101. * @return array
  102. */
  103. public static function getSize()
  104. {
  105. if (OS_WIN) {
  106. $modecon = explode("\n", ltrim(Processus::execute('mode con')));
  107. $_y = trim($modecon[2]);
  108. preg_match('#[^:]+:\s*([0-9]+)#', $_y, $matches);
  109. $y = (int) $matches[1];
  110. $_x = trim($modecon[3]);
  111. preg_match('#[^:]+:\s*([0-9]+)#', $_x, $matches);
  112. $x = (int) $matches[1];
  113. return [
  114. 'x' => $x,
  115. 'y' => $y
  116. ];
  117. }
  118. $term = '';
  119. if (isset($_SERVER['TERM'])) {
  120. $term = 'TERM="' . $_SERVER['TERM'] . '" ';
  121. }
  122. $command = $term . 'tput cols && ' . $term . 'tput lines';
  123. $tput = Processus::execute($command, false);
  124. if (!empty($tput)) {
  125. list($x, $y) = explode("\n", $tput);
  126. return [
  127. 'x' => intval($x),
  128. 'y' => intval($y)
  129. ];
  130. }
  131. // DECSLPP.
  132. Console::getOutput()->writeAll("\033[18t");
  133. $input = Console::getInput();
  134. // Read \033[8;y;xt.
  135. $input->read(4); // skip \033, [, 8 and ;.
  136. $x = null;
  137. $y = null;
  138. $handle = &$y;
  139. do {
  140. $char = $input->readCharacter();
  141. switch ($char) {
  142. case ';':
  143. $handle = &$x;
  144. break;
  145. case 't':
  146. break 2;
  147. default:
  148. if (false === ctype_digit($char)) {
  149. break 2;
  150. }
  151. $handle .= $char;
  152. }
  153. } while (true);
  154. if (null === $x || null === $y) {
  155. return [
  156. 'x' => 0,
  157. 'y' => 0
  158. ];
  159. }
  160. return [
  161. 'x' => (int) $x,
  162. 'y' => (int) $y
  163. ];
  164. }
  165. /**
  166. * Move to X and Y (in pixels).
  167. *
  168. * @param int $x X coordinate.
  169. * @param int $y Y coordinate.
  170. * @return void
  171. */
  172. public static function moveTo($x, $y)
  173. {
  174. if (OS_WIN) {
  175. return;
  176. }
  177. // DECSLPP.
  178. Console::getOutput()->writeAll("\033[3;" . $x . ";" . $y . "t");
  179. return;
  180. }
  181. /**
  182. * Get current position (x and y) of the window (in pixels).
  183. *
  184. * @return array
  185. */
  186. public static function getPosition()
  187. {
  188. if (OS_WIN) {
  189. return;
  190. }
  191. // DECSLPP.
  192. Console::getOutput()->writeAll("\033[13t");
  193. $input = Console::getInput();
  194. // Read \033[3;x;yt.
  195. $input->read(4); // skip \033, [, 3 and ;.
  196. $x = null;
  197. $y = null;
  198. $handle = &$x;
  199. do {
  200. $char = $input->readCharacter();
  201. switch ($char) {
  202. case ';':
  203. $handle = &$y;
  204. break;
  205. case 't':
  206. break 2;
  207. default:
  208. $handle .= $char;
  209. }
  210. } while (true);
  211. return [
  212. 'x' => (int) $x,
  213. 'y' => (int) $y
  214. ];
  215. }
  216. /**
  217. * Scroll whole page.
  218. * Directions can be:
  219. * • u, up, ↑ : scroll whole page up;
  220. * • d, down, ↓ : scroll whole page down.
  221. * Directions can be concatenated by a single space.
  222. *
  223. * @param string $directions Directions.
  224. * @param int $repeat How many times do we scroll?
  225. * @return void
  226. */
  227. public static function scroll($directions, $repeat = 1)
  228. {
  229. if (OS_WIN) {
  230. return;
  231. }
  232. if (1 > $repeat) {
  233. return;
  234. } elseif (1 === $repeat) {
  235. $handle = explode(' ', $directions);
  236. } else {
  237. $handle = explode(' ', $directions, 1);
  238. }
  239. $tput = Console::getTput();
  240. $count = ['up' => 0, 'down' => 0];
  241. foreach ($handle as $direction) {
  242. switch ($direction) {
  243. case 'u':
  244. case 'up':
  245. case '↑':
  246. ++$count['up'];
  247. break;
  248. case 'd':
  249. case 'down':
  250. case '↓':
  251. ++$count['down'];
  252. break;
  253. }
  254. }
  255. $output = Console::getOutput();
  256. if (0 < $count['up']) {
  257. $output->writeAll(
  258. str_replace(
  259. '%p1%d',
  260. $count['up'] * $repeat,
  261. $tput->get('parm_index')
  262. )
  263. );
  264. }
  265. if (0 < $count['down']) {
  266. $output->writeAll(
  267. str_replace(
  268. '%p1%d',
  269. $count['down'] * $repeat,
  270. $tput->get('parm_rindex')
  271. )
  272. );
  273. }
  274. return;
  275. }
  276. /**
  277. * Minimize the window.
  278. *
  279. * @return void
  280. */
  281. public static function minimize()
  282. {
  283. if (OS_WIN) {
  284. return;
  285. }
  286. // DECSLPP.
  287. Console::getOutput()->writeAll("\033[2t");
  288. return;
  289. }
  290. /**
  291. * Restore the window (de-minimize).
  292. *
  293. * @return void
  294. */
  295. public static function restore()
  296. {
  297. if (OS_WIN) {
  298. return;
  299. }
  300. Console::getOutput()->writeAll("\033[1t");
  301. return;
  302. }
  303. /**
  304. * Raise the window to the front of the stacking order.
  305. *
  306. * @return void
  307. */
  308. public static function raise()
  309. {
  310. if (OS_WIN) {
  311. return;
  312. }
  313. Console::getOutput()->writeAll("\033[5t");
  314. return;
  315. }
  316. /**
  317. * Lower the window to the bottom of the stacking order.
  318. *
  319. * @return void
  320. */
  321. public static function lower()
  322. {
  323. if (OS_WIN) {
  324. return;
  325. }
  326. Console::getOutput()->writeAll("\033[6t");
  327. return;
  328. }
  329. /**
  330. * Set title.
  331. *
  332. * @param string $title Title.
  333. * @return void
  334. */
  335. public static function setTitle($title)
  336. {
  337. if (OS_WIN) {
  338. return;
  339. }
  340. // DECSLPP.
  341. Console::getOutput()->writeAll("\033]0;" . $title . "\033\\");
  342. return;
  343. }
  344. /**
  345. * Get title.
  346. *
  347. * @return string
  348. */
  349. public static function getTitle()
  350. {
  351. if (OS_WIN) {
  352. return;
  353. }
  354. // DECSLPP.
  355. Console::getOutput()->writeAll("\033[21t");
  356. $input = Console::getInput();
  357. $read = [$input->getStream()->getStream()];
  358. $write = [];
  359. $except = [];
  360. $out = null;
  361. if (0 === stream_select($read, $write, $except, 0, 50000)) {
  362. return $out;
  363. }
  364. // Read \033]l<title>\033\
  365. $input->read(3); // skip \033, ] and l.
  366. do {
  367. $char = $input->readCharacter();
  368. if ("\033" === $char) {
  369. $chaar = $input->readCharacter();
  370. if ('\\' === $chaar) {
  371. break;
  372. }
  373. $char .= $chaar;
  374. }
  375. $out .= $char;
  376. } while (true);
  377. return $out;
  378. }
  379. /**
  380. * Get label.
  381. *
  382. * @return string
  383. */
  384. public static function getLabel()
  385. {
  386. if (OS_WIN) {
  387. return;
  388. }
  389. // DECSLPP.
  390. Console::getOutput()->writeAll("\033[20t");
  391. $input = Console::getInput();
  392. $read = [$input->getStream()->getStream()];
  393. $write = [];
  394. $except = [];
  395. $out = null;
  396. if (0 === stream_select($read, $write, $except, 0, 50000)) {
  397. return $out;
  398. }
  399. // Read \033]L<label>\033\
  400. $input->read(3); // skip \033, ] and L.
  401. do {
  402. $char = $input->readCharacter();
  403. if ("\033" === $char) {
  404. $chaar = $input->readCharacter();
  405. if ('\\' === $chaar) {
  406. break;
  407. }
  408. $char .= $chaar;
  409. }
  410. $out .= $char;
  411. } while (true);
  412. return $out;
  413. }
  414. /**
  415. * Refresh the window.
  416. *
  417. * @return void
  418. */
  419. public static function refresh()
  420. {
  421. if (OS_WIN) {
  422. return;
  423. }
  424. // DECSLPP.
  425. Console::getOutput()->writeAll("\033[7t");
  426. return;
  427. }
  428. /**
  429. * Set clipboard value.
  430. *
  431. * @param string $data Data to copy.
  432. * @return void
  433. */
  434. public static function copy($data)
  435. {
  436. if (OS_WIN) {
  437. return;
  438. }
  439. $out = "\033]52;;" . base64_encode($data) . "\033\\";
  440. $output = Console::getOutput();
  441. $considerMultiplexer = $output->considerMultiplexer(true);
  442. $output->writeAll($out);
  443. $output->considerMultiplexer($considerMultiplexer);
  444. return;
  445. }
  446. }
  447. /**
  448. * Advanced interaction.
  449. */
  450. Console::advancedInteraction();
  451. /**
  452. * Event.
  453. */
  454. if (function_exists('pcntl_signal')) {
  455. Window::getInstance();
  456. pcntl_signal(
  457. SIGWINCH,
  458. function () {
  459. static $_window = null;
  460. if (null === $_window) {
  461. $_window = Window::getInstance();
  462. }
  463. Event::notify(
  464. 'hoa://Event/Console/Window:resize',
  465. $_window,
  466. new Event\Bucket([
  467. 'size' => Window::getSize()
  468. ])
  469. );
  470. }
  471. );
  472. }