color.pl 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use File::Temp qw( tempfile );
  5. use Time::HiRes qw( usleep sleep time );
  6. use IPC::System::Simple qw( system capture );
  7. # Local modules
  8. use Cwd qw(abs_path);
  9. use File::Basename qw(dirname);
  10. use lib dirname(abs_path($0));
  11. use DMX;
  12. # Config
  13. my $DISPLAY = 1;
  14. my %PROFILES = (
  15. 'HIGH' => 'Epson-High',
  16. 'LOW' => 'Epson-Low'
  17. );
  18. # App config
  19. my $DATA_DIR = DMX::dataDir();
  20. my $OUTPUT_FILE = $DATA_DIR . 'COLOR';
  21. my $STATE_SOCK = $OUTPUT_FILE . '.socket';
  22. my $PUSH_TIMEOUT = 20;
  23. my $PULL_TIMEOUT = $PUSH_TIMEOUT * 3;
  24. my $DELAY = $PULL_TIMEOUT / 2;
  25. # Prototypes
  26. sub runApplescript($);
  27. sub getProfile($);
  28. sub setProfile($$);
  29. sub profileExists($$);
  30. # Debug
  31. my $DEBUG = 0;
  32. if ($ENV{'DEBUG'}) {
  33. $DEBUG = 1;
  34. }
  35. # Sockets
  36. DMX::stateSocket($STATE_SOCK);
  37. DMX::stateSubscribe($STATE_SOCK);
  38. # State
  39. my $state = 'OFF';
  40. my $stateLast = $state;
  41. my $color = 'OFF';
  42. my $colorLast = $color;
  43. my %exists = ();
  44. my $pushLast = 0;
  45. my $pullLast = time();
  46. my $update = 0;
  47. # Validate our profiles
  48. foreach my $profile (keys(%PROFILES)) {
  49. if (!profileExists($DISPLAY, $PROFILES{$profile})) {
  50. die('Invalid profile: ' . $profile . ' => ' . $PROFILES{$profile} . "\n");
  51. }
  52. }
  53. # Loop forever
  54. while (1) {
  55. # Grab the current color profile
  56. $colorLast = $color;
  57. $color = getProfile($DISPLAY);
  58. # If the color profile has changed, save the state to disk
  59. if ($colorLast ne $color) {
  60. if ($DEBUG) {
  61. print STDERR 'New color profile: ' . $colorLast . ' => ' . $color . "\n";
  62. }
  63. my ($fh, $tmp) = tempfile($OUTPUT_FILE . '.XXXXXXXX', 'UNLINK' => 0);
  64. print $fh $color . "\n";
  65. close($fh);
  66. rename($tmp, $OUTPUT_FILE);
  67. }
  68. # State is calculated; use newState to gather data
  69. my $newState = $state;
  70. # Wait for state updates
  71. my $cmdState = DMX::readState($DELAY, \%exists, undef(), undef());
  72. if (defined($cmdState)) {
  73. $newState = $cmdState;
  74. $pullLast = time();
  75. }
  76. # Die if we don't see regular updates
  77. if (time() - $pullLast > $PULL_TIMEOUT) {
  78. die('No update on state socket in past ' . $PULL_TIMEOUT . " seconds. Exiting...\n");
  79. }
  80. # Calculate the new state
  81. $stateLast = $state;
  82. if ($exists{'PROJECTOR_COLOR'} eq 'DYNAMIC') {
  83. $state = 'HIGH';
  84. } else {
  85. $state = 'LOW';
  86. }
  87. # Force updates on a periodic basis
  88. if (!$update && time() - $pushLast > $PUSH_TIMEOUT) {
  89. # Not for the color profile
  90. #if ($DEBUG) {
  91. # print STDERR "Forcing periodic update\n";
  92. #}
  93. #$update = 1;
  94. }
  95. # Force updates when there is a physical state mistmatch
  96. if (!$update && $PROFILES{$state} ne $color) {
  97. if ($DEBUG) {
  98. print STDERR 'State mismatch: ' . $color . ' => ' . $PROFILES{$state} . "\n";
  99. }
  100. $update = 1;
  101. }
  102. # Update the color profile
  103. if ($update) {
  104. # Update
  105. if ($DEBUG) {
  106. print STDERR 'Setting output to: ' . $PROFILES{$state} . "\n";
  107. }
  108. setProfile($DISPLAY, $PROFILES{$state});
  109. # Update the push time
  110. $pushLast = time();
  111. # Clear the update flag
  112. $update = 0;
  113. }
  114. }
  115. sub runApplescript($) {
  116. my ($script) = @_;
  117. if ($DEBUG) {
  118. print STDERR 'Running AppleScript: ' . $script . "\n";
  119. }
  120. my $retval = capture('osascript', '-e', $script);
  121. if ($DEBUG) {
  122. print STDERR "\tAppleScript result: " . $retval . "\n";
  123. }
  124. return $retval;
  125. }
  126. sub getProfile($) {
  127. my ($display) = @_;
  128. if ($DEBUG) {
  129. print STDERR "getProfile()\n";
  130. }
  131. # Validate the input
  132. $display = int($display);
  133. if (!$display) {
  134. die('Invalid display ID: ' . $display . "\n");
  135. }
  136. # Ask SwitchResX what color profile is installed
  137. my $profile = runApplescript('tell application "SwitchResX Daemon" to get display profile of display ' . $display);
  138. # Cleanup the name
  139. $profile =~ s/^\s*profile //;
  140. $profile =~ s/\s+$//;
  141. return $profile;
  142. }
  143. sub setProfile($$) {
  144. my ($display, $profile) = @_;
  145. if ($DEBUG) {
  146. print STDERR "setProfile()\n";
  147. }
  148. # Validate the input
  149. $profile =~ s/\"/\\\"/g;
  150. $profile = '"' . $profile . '"';
  151. $display = int($display);
  152. if (!$display) {
  153. die('Invalid display ID: ' . $display . "\n");
  154. }
  155. # Tell SwitchResX to change our color profile
  156. my @cmd = ('tell application "SwitchResX Daemon"');
  157. push(@cmd, 'set prof to profile ' . $profile . ' of display ' . $display);
  158. push(@cmd, 'set display profile of display ' . $display . ' to prof');
  159. push(@cmd, 'end tell');
  160. return runApplescript(join("\n", @cmd));
  161. }
  162. sub profileExists($$) {
  163. my ($display, $profile) = @_;
  164. if ($DEBUG) {
  165. print STDERR "profileExists()\n";
  166. }
  167. # Validate the input
  168. $profile =~ s/\"/\\\"/g;
  169. $profile = '"' . $profile . '"';
  170. $display = int($display);
  171. if (!$display) {
  172. die('Invalid display ID: ' . $display . "\n");
  173. }
  174. # Ask SwitchResX if the named color profile exists
  175. my @cmd = ('tell application "SwitchResX Daemon"');
  176. push(@cmd, 'set profs to get name of every profile of display ' . $display);
  177. push(@cmd, 'get profs contains ' . $profile);
  178. push(@cmd, 'end tell');
  179. my $result = runApplescript(join("\n", @cmd));
  180. if ($result =~ /true/i) {
  181. return 1;
  182. }
  183. return 0;
  184. }