color.pl 5.0 KB

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