Check-in [f2fcdbc505]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Use the "plunk" sound for audiable alert in chat. Back out the "ping" processing logic.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: f2fcdbc505c3836af9d1295426d5d9027f6ce369fdc3a7fa6f0c25feedb528ea
User & Date: drh 2021-01-03 16:40:25.074
Context
2021-01-03
16:48
Fix the "fossil chat" command so that it works on Windows. check-in: f572b62f22 user: drh tags: trunk
16:40
Use the "plunk" sound for audiable alert in chat. Back out the "ping" processing logic. check-in: f2fcdbc505 user: drh tags: trunk
15:30
Fix the /file page so that it is able to play sound files that are checked into the repository. Example: /file/src/sounds/plunk.wav check-in: 6643d4a0c1 user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
    if( system(zBrowser)<0 ){
      fossil_warning("cannot start browser: %s\n", zBrowser);
    }
  }
  while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
    while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
      if( wait(0)>=0 ) nchildren--;







|







2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
    if( fossil_system(zBrowser)<0 ){
      fossil_warning("cannot start browser: %s\n", zBrowser);
    }
  }
  while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
    while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
      if( wait(0)>=0 ) nchildren--;
Changes to src/chat.c.
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
** point a web-browser at /chat and the screen fills with the latest
** chat messages, and waits for new one.
**
** Other /chat-OP pages are used by XHR requests from this page to
** send new chat message, delete older messages, or poll for changes.
*/
void chat_webpage(void){
  int iPingTcp;
  login_check_credentials();
  if( !g.perm.Chat ){
    login_needed(g.anon.Chat);
    return;
  }
  iPingTcp = atoi(PD("ping","0"));
  if( iPingTcp<1000 || iPingTcp>65535 ) iPingTcp = 0;
  if( iPingTcp ) style_disable_csp();
  style_set_current_feature("chat");
  style_header("Chat");
  @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="Type message here." autocomplete="off">







<





<
<
<







99
100
101
102
103
104
105

106
107
108
109
110



111
112
113
114
115
116
117
** point a web-browser at /chat and the screen fills with the latest
** chat messages, and waits for new one.
**
** Other /chat-OP pages are used by XHR requests from this page to
** send new chat message, delete older messages, or poll for changes.
*/
void chat_webpage(void){

  login_check_credentials();
  if( !g.perm.Chat ){
    login_needed(g.anon.Chat);
    return;
  }



  style_set_current_feature("chat");
  style_header("Chat");
  @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off">
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="chat-input-single" \
  @      placeholder="Type message here." autocomplete="off">
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
  @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
  /* We need an onload handler to ensure that window.fossil is
     initialized before the chat init code runs. */
  @ window.addEventListener('load', function(){
  @ document.body.classList.add('chat')
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   pingTcp: %d(iPingTcp),
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };
  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>








|







147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
  @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
  /* We need an onload handler to ensure that window.fossil is
     initialized before the chat init code runs. */
  @ window.addEventListener('load', function(){
  @ document.body.classList.add('chat')
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @ };
  cgi_append_content(builtin_text("chat.js"),-1);
  @ }, false);
  @ </script>

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718

719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
    " VALUES(julianday('now'), %Q, %d);\n"
    "COMMIT;",
    mdel, g.zLogin, mdel
  );
}

/*
** WEBPAGE: chat-ping
**
** HTTP requests coming to this page from a loopback IP address cause
** a single \007 (bel) character to be written on the controlling TTY.
** This is used to implement an audiable alert by local web clients.
*/
void chat_ping_webpage(void){
  const char *zIpAddr = PD("REMOTE_ADDR","nil");
  if( cgi_is_loopback(zIpAddr) ){
    cgi_append_header("Access-Control-Allow-Origin: *\r\n");
    fputc(7, stderr);
  }
}

/*
** WEBPAGE: chat-audio-received
**
** Responds with an audio stream suitable for use as a /chat
** new-message-arrived notification.
*/
void chat_audio_alert(void){
  Blob audio = empty_blob;
  int n = 0;
  const char * zAudio =
    (const char *)builtin_file("sounds/chat-received.wav", &n);
  blob_init(&audio, zAudio, n);
  cgi_set_content_type("audio/wav");
  cgi_set_content(&audio);  
}

/*
** COMMAND: chat
**
** Usage: %fossil chat ?URL?
**
** Bring up a window to the chatroom feature of the Fossil repository
** at URL.  Or if URL is not specified, use the default remote repository.
** Event notifications on this session cause the U+0007 character to
** be sent to the TTY on which the "fossil chat" command is run, thus
** causing an auditory notification.
*/
void chat_command(void){
  const char *zUrl = 0;
  size_t i;
  char *azArgv[5];
  db_find_and_open_repository(0,0);
  if( g.argc==3 ){
    zUrl = g.argv[2];
  }else if( g.argc!=2 ){
    usage("?URL?");
  }else{

    zUrl = db_get("last-sync-url",0);
    if( zUrl==0 ){
      fossil_fatal("no \"remote\" repository defined.  Use a URL argument");
    }
    url_parse(zUrl, 0);
    if( g.url.port==g.url.dfltPort ){
      zUrl = mprintf(
        "%s://%T%T",
        g.url.protocol, g.url.name, g.url.path
      );
    }else{
      zUrl = mprintf(
        "%s://%T:%d%T",
        g.url.protocol, g.url.name, g.url.port, g.url.path
      );
    }
  }
  if( strncmp(zUrl,"http://",7)!=0 && strncmp("https://",zUrl,8)!=0 ){
    fossil_fatal("Not a valid URL: %s", zUrl);
  }
  azArgv[0] = g.argv[0];
  azArgv[1] = "ui";
  azArgv[2] = "--internal-chat-url";
  i = strlen(zUrl);
  if( i && zUrl[i-1]=='/' ) i--;
  azArgv[3] = mprintf("%.*s/chat?ping=%%d", i, zUrl);
  azArgv[4] = 0;
  g.argv = azArgv;
  g.argc = 4;
  cmd_webserver();
}







|

<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<
<





|








|

|
|
<
<
<


|
|
|

|
<
<
|
<
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
|
<
<
<
<
<
|
|
<
<
<
|

657
658
659
660
661
662
663
664
665











666


667


668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685



686
687
688
689
690
691
692


693

694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710


711





712
713



714
715
    " VALUES(julianday('now'), %Q, %d);\n"
    "COMMIT;",
    mdel, g.zLogin, mdel
  );
}

/*
** WEBPAGE: chat-alert
**











** Return the sound file that should be played when a new chat message


** arrives.


*/
void chat_audio_alert(void){
  Blob audio = empty_blob;
  int n = 0;
  const char * zAudio =
    (const char *)builtin_file("sounds/plunk.wav", &n);
  blob_init(&audio, zAudio, n);
  cgi_set_content_type("audio/wav");
  cgi_set_content(&audio);  
}

/*
** COMMAND: chat
**
** Usage: %fossil chat
**
** Bring up a web-browser window to the chatroom of the default
** remote Fossil repository.



*/
void chat_command(void){
  const char *zUrl;
  const char *zBrowser;
  char *zCmd;
  db_find_and_open_repository(0,0);
  if( g.argc!=2 ){


    usage("");

  }
  zUrl = db_get("last-sync-url",0);
  if( zUrl==0 ){
    fossil_fatal("no \"remote\" repository defined");
  }
  url_parse(zUrl, 0);
  if( g.url.port==g.url.dfltPort ){
    zUrl = mprintf(
      "%s://%T%T",
      g.url.protocol, g.url.name, g.url.path
    );
  }else{
    zUrl = mprintf(
      "%s://%T:%d%T",
      g.url.protocol, g.url.name, g.url.port, g.url.path
    );
  }


  zBrowser = fossil_web_browser();





  if( zBrowser==0 ) return;
  zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);



  fossil_system(zCmd);
}
Changes to src/chat.js.
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
        getBool: (k,dflt)=>F.storage.getBool(k,dflt),
        set: (k,v)=>F.storage.set(k,v),
        defaults:{
          "images-inline": !!F.config.chat.imagesInline,
          "edit-multiline": false,
          "monospace-messages": false,
          "chat-only-mode": false,
          "audio-notification": true,
        }
      },
      /** Plays a new-message notification sound IF the audio-notification
          setting is true, else this is a no-op. Returns this.
      */
      playNewMessageSound: function f(){
        if(this.settings.getBool('audio-notification',false)){
          try{
            if(!f.audio) f.audio = new Audio(F.rootPath+"chat-audio-received");
            f.audio.currentTime = 0;
            f.audio.play();
          }catch(e){
            console.error("Audio playblack failed.",e);
          }
        }
        return this;







|


|



|

|







353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
        getBool: (k,dflt)=>F.storage.getBool(k,dflt),
        set: (k,v)=>F.storage.set(k,v),
        defaults:{
          "images-inline": !!F.config.chat.imagesInline,
          "edit-multiline": false,
          "monospace-messages": false,
          "chat-only-mode": false,
          "audible-alert": true,
        }
      },
      /** Plays a new-message notification sound IF the audible-alert
          setting is true, else this is a no-op. Returns this.
      */
      playNewMessageSound: function f(){
        if(this.settings.getBool('audible-alert',false)){
          try{
            if(!f.audio) f.audio = new Audio(F.rootPath+"chat-alert");
            f.audio.currentTime = 0;
            f.audio.play();
          }catch(e){
            console.error("Audio playblack failed.",e);
          }
        }
        return this;
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
      boolValue: ()=>Chat.settings.getBool('images-inline'),
      callback: function(){
        const v = Chat.settings.getBool('images-inline',true);
        Chat.settings.set('images-inline', !v);
        F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+".");
      }
    },{
      label: "Audio notifications",
      boolValue: ()=>Chat.settings.getBool('audio-notification'),
      callback: function(){
        const v = Chat.settings.getBool('audio-notification');
        Chat.settings.set('audio-notification', !v);
        if(!v){
          setTimeout(()=>Chat.playNewMessageSound(), 500);
        }
        F.toast.message("Audio notifications "+(v ? "disabled" : "enabled")+".");
      }
    }];

    /**
       Rebuild the menu each time it's shown so that the toggles can







|
|

|
|

|







932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
      boolValue: ()=>Chat.settings.getBool('images-inline'),
      callback: function(){
        const v = Chat.settings.getBool('images-inline',true);
        Chat.settings.set('images-inline', !v);
        F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+".");
      }
    },{
      label: "Audible alerts",
      boolValue: ()=>Chat.settings.getBool('audible-alert'),
      callback: function(){
        const v = Chat.settings.getBool('audible-alert');
        Chat.settings.set('audible-alert', !v);
        if(!v){
          setTimeout(()=>Chat.playNewMessageSound(), 50);
        }
        F.toast.message("Audio notifications "+(v ? "disabled" : "enabled")+".");
      }
    }];

    /**
       Rebuild the menu each time it's shown so that the toggles can
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
      }
    }else{
      Chat.changesSincePageHidden += jx.msgs.length;
      if(jx.msgs.length){
        Chat.e.pageTitle.innerText = '[*] '+Chat.pageTitleOrig;
      }
    }
    if(jx.msgs.length && F.config.chat.pingTcp){
      fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping");
    }
  }/*newcontent()*/;
  Chat.newContent = newcontent;

  (function(){
    /** Add toolbar for loading older messages. We use a FIELDSET here
        because a fieldset is the only parent element type which can
        automatically enable/disable its children by







<
<
<







1055
1056
1057
1058
1059
1060
1061



1062
1063
1064
1065
1066
1067
1068
      }
    }else{
      Chat.changesSincePageHidden += jx.msgs.length;
      if(jx.msgs.length){
        Chat.e.pageTitle.innerText = '[*] '+Chat.pageTitleOrig;
      }
    }



  }/*newcontent()*/;
  Chat.newContent = newcontent;

  (function(){
    /** Add toolbar for loading older messages. We use a FIELDSET here
        because a fieldset is the only parent element type which can
        automatically enable/disable its children by
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
      });
  }
  Chat._gotServerError = poll.running = false;
  poll(true);
  if(!Chat._gotServerError){
    Chat.intervalTimer = setInterval(poll, 1000);
  }
  if(/\bping=\d+/.test(window.location.search)){
    /* If we see the 'ping' parameter we're certain this was run via
       the 'fossil chat' CLI command, in which case we start up in
       chat-only mode. */
    Chat.chatOnlyMode(true);
  }

  F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();







|
<
<
<





1164
1165
1166
1167
1168
1169
1170
1171



1172
1173
1174
1175
1176
      });
  }
  Chat._gotServerError = poll.running = false;
  poll(true);
  if(!Chat._gotServerError){
    Chat.intervalTimer = setInterval(poll, 1000);
  }
  if( window.fossil.config.chat.fromcli ){



    Chat.chatOnlyMode(true);
  }

  F.page.chat = Chat/* enables testing the APIs via the dev tools */;
})();
Changes to src/checkin.c.
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
  const char *zEditor;
  char *zCmd;
  char *zFile;
  Blob reply, line;
  char *zComment;
  int i;

  zEditor = fossil_text_editor();db_get("editor", 0);
  if( zEditor==0 ){
    if( blob_size(pPrompt)>0 ){
      blob_append(pPrompt,
         "#\n"
         "# Since no default text editor is set using EDITOR or VISUAL\n"
         "# environment variables or the \"fossil set editor\" command,\n"
         "# and because no comment was specified using the \"-m\" or \"-M\"\n"







|







1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
  const char *zEditor;
  char *zCmd;
  char *zFile;
  Blob reply, line;
  char *zComment;
  int i;

  zEditor = fossil_text_editor();
  if( zEditor==0 ){
    if( blob_size(pPrompt)>0 ){
      blob_append(pPrompt,
         "#\n"
         "# Since no default text editor is set using EDITOR or VISUAL\n"
         "# environment variables or the \"fossil set editor\" command,\n"
         "# and because no comment was specified using the \"-m\" or \"-M\"\n"
Changes to src/main.c.
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
  }else{
    cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
    cgi_handle_http_request(0);
    process_one_web_page(0, 0, 1);
  }
}

#if !defined(_WIN32)
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.
*/
static int binaryOnPath(const char *zBinary){
  const char *zPath = fossil_getenv("PATH");
  char *zFull;
  int i;
  int bExists;
  while( zPath && zPath[0] ){
    while( zPath[0]==':' ) zPath++;
    for(i=0; zPath[i] && zPath[i]!=':'; i++){}
    zFull = mprintf("%.*s/%s", i, zPath, zBinary);
    bExists = file_access(zFull, X_OK);
    fossil_free(zFull);
    if( bExists==0 ) return 1;
    zPath += i;
  }
  return 0;
}
#endif
#endif

/*
** Respond to a SIGALRM by writing a message to the error log (if there
** is one) and exiting.
*/
#ifndef _WIN32
static void sigalrm_handler(int x){
  fossil_panic("TIMEOUT");







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







2655
2656
2657
2658
2659
2660
2661

























2662
2663
2664
2665
2666
2667
2668
  }else{
    cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
    cgi_handle_http_request(0);
    process_one_web_page(0, 0, 1);
  }
}


























/*
** Respond to a SIGALRM by writing a message to the error log (if there
** is one) and exiting.
*/
#ifndef _WIN32
static void sigalrm_handler(int x){
  fossil_panic("TIMEOUT");
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
#endif
  int allowRepoList;         /* List repositories on URL "/" */
  const char *zAltBase;      /* Argument to the --baseurl option */
  const char *zFileGlob;     /* Static content must match this */
  char *zIpAddr = 0;         /* Bind to this IP address */
  int fCreate = 0;           /* The --create flag */
  const char *zInitPage = 0; /* Start on this page.  --page option */
  const char *zChat = 0;     /* Remote chat URL.  (undocumented) */

#if defined(_WIN32)
  const char *zStopperFile;    /* Name of file used to terminate server */
  zStopperFile = find_option("stopper", 0, 1);
#endif

  if( g.zErrlog==0 ){







<







2780
2781
2782
2783
2784
2785
2786

2787
2788
2789
2790
2791
2792
2793
#endif
  int allowRepoList;         /* List repositories on URL "/" */
  const char *zAltBase;      /* Argument to the --baseurl option */
  const char *zFileGlob;     /* Static content must match this */
  char *zIpAddr = 0;         /* Bind to this IP address */
  int fCreate = 0;           /* The --create flag */
  const char *zInitPage = 0; /* Start on this page.  --page option */


#if defined(_WIN32)
  const char *zStopperFile;    /* Name of file used to terminate server */
  zStopperFile = find_option("stopper", 0, 1);
#endif

  if( g.zErrlog==0 ){
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
  g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd;
  if( find_option("https",0,0)!=0 ){
    cgi_replace_parameter("HTTPS","on");
  }
  if( find_option("localhost", 0, 0)!=0 ){
    flags |= HTTP_SERVER_LOCALHOST;
  }
  zChat = find_option("internal-chat-url",0,1);

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  if( isUiCmd ){
    flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;







<







2827
2828
2829
2830
2831
2832
2833

2834
2835
2836
2837
2838
2839
2840
  g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd;
  if( find_option("https",0,0)!=0 ){
    cgi_replace_parameter("HTTPS","on");
  }
  if( find_option("localhost", 0, 0)!=0 ){
    flags |= HTTP_SERVER_LOCALHOST;
  }


  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  if( isUiCmd ){
    flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
  }else{
    iPort = db_get_int("http-port", 8080);
    mxPort = iPort+100;
  }
#if !defined(_WIN32)
  /* Unix implementation */
  if( isUiCmd ){
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
    zBrowser = db_get("web-browser", 0);
    if( zBrowser==0 ){
      static const char *const azBrowserProg[] =
          { "xdg-open", "gnome-open", "firefox", "google-chrome" };
      int i;
      zBrowser = "echo";
      for(i=0; i<count(azBrowserProg); i++){
        if( binaryOnPath(azBrowserProg[i]) ){
          zBrowser = azBrowserProg[i];
          break;
        }
      }
    }
#else
    zBrowser = db_get("web-browser", "open");
#endif
    if( zChat ){
      zBrowserCmd = mprintf("%s \"%s\" &", zBrowser, zChat);
    }else if( zIpAddr==0 ){
      zBrowserCmd = mprintf("%s \"http://localhost:%%d/%s\" &",
                            zBrowser, zInitPage);
    }else if( strchr(zIpAddr,':') ){
      zBrowserCmd = mprintf("%s \"http://[%s]:%%d/%s\" &",
                            zBrowser, zIpAddr, zInitPage);
    }else{
      zBrowserCmd = mprintf("%s \"http://%s:%%d/%s\" &",







<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
|







2866
2867
2868
2869
2870
2871
2872






2873












2874
2875
2876
2877
2878
2879
2880
2881
  }else{
    iPort = db_get_int("http-port", 8080);
    mxPort = iPort+100;
  }
#if !defined(_WIN32)
  /* Unix implementation */
  if( isUiCmd ){






    zBrowser = fossil_web_browser();












    if( zIpAddr==0 ){
      zBrowserCmd = mprintf("%s \"http://localhost:%%d/%s\" &",
                            zBrowser, zInitPage);
    }else if( strchr(zIpAddr,':') ){
      zBrowserCmd = mprintf("%s \"http://[%s]:%%d/%s\" &",
                            zBrowser, zIpAddr, zInitPage);
    }else{
      zBrowserCmd = mprintf("%s \"http://%s:%%d/%s\" &",
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
  if( g.fAnyTrace ){
    fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n",
            getpid());
  }
#else
  /* Win32 implementation */
  if( isUiCmd ){
    zBrowser = db_get("web-browser", "start");
    if( zChat ){
      zBrowserCmd = mprintf("%s %s &", zBrowser, zChat);
    }else if( zIpAddr==0 ){
      zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
                            zBrowser, zInitPage);
    }else if( strchr(zIpAddr,':') ){
      zBrowserCmd = mprintf("%s http://[%s]:%%d/%s &",
                            zBrowser, zIpAddr, zInitPage);
    }else{
      zBrowserCmd = mprintf("%s http://%s:%%d/%s &",







|
<
<
|







2929
2930
2931
2932
2933
2934
2935
2936


2937
2938
2939
2940
2941
2942
2943
2944
  if( g.fAnyTrace ){
    fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n",
            getpid());
  }
#else
  /* Win32 implementation */
  if( isUiCmd ){
    zBrowser = fossil_web_browser();


    if( zIpAddr==0 ){
      zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
                            zBrowser, zInitPage);
    }else if( strchr(zIpAddr,':') ){
      zBrowserCmd = mprintf("%s http://[%s]:%%d/%s &",
                            zBrowser, zIpAddr, zInitPage);
    }else{
      zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
Changes to src/main.mk.
261
262
263
264
265
266
267
268
269
270
271

272
273
274
275
276
277
278
  $(SRCDIR)/sounds/6.wav \
  $(SRCDIR)/sounds/7.wav \
  $(SRCDIR)/sounds/8.wav \
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/chat-received.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \

  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki








<



>







261
262
263
264
265
266
267

268
269
270
271
272
273
274
275
276
277
278
  $(SRCDIR)/sounds/6.wav \
  $(SRCDIR)/sounds/7.wav \
  $(SRCDIR)/sounds/8.wav \
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \

  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/sounds/plunk.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

Deleted src/sounds/chat-received.wav.

cannot compute difference between binary files

Changes to src/util.c.
762
763
764
765
766
767
768





















































** when formatting text.
*/
int fossil_num_digits(int n){
  return n<      10 ? 1 : n<      100 ? 2 : n<      1000 ? 3
       : n<   10000 ? 4 : n<   100000 ? 5 : n<   1000000 ? 6
       : n<10000000 ? 7 : n<100000000 ? 8 : n<1000000000 ? 9 : 10;
}




























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
** when formatting text.
*/
int fossil_num_digits(int n){
  return n<      10 ? 1 : n<      100 ? 2 : n<      1000 ? 3
       : n<   10000 ? 4 : n<   100000 ? 5 : n<   1000000 ? 6
       : n<10000000 ? 7 : n<100000000 ? 8 : n<1000000000 ? 9 : 10;
}

#if !defined(_WIN32)
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.
*/
static int binaryOnPath(const char *zBinary){
  const char *zPath = fossil_getenv("PATH");
  char *zFull;
  int i;
  int bExists;
  while( zPath && zPath[0] ){
    while( zPath[0]==':' ) zPath++;
    for(i=0; zPath[i] && zPath[i]!=':'; i++){}
    zFull = mprintf("%.*s/%s", i, zPath, zBinary);
    bExists = file_access(zFull, X_OK);
    fossil_free(zFull);
    if( bExists==0 ) return 1;
    zPath += i;
  }
  return 0;
}
#endif
#endif


/*
** Return the name of a command that will launch a web-browser.
*/
const char *fossil_web_browser(void){
  const char *zBrowser = 0;
#if defined(_WIN32)
  zBrowser = db_get("web-browser", "start");
#elif defined(__DARWIN__) || defined(__APPLE__) || defined(__HAIKU__)
  zBrowser = db_get("web-browser", "open");
#else
  zBrowser = db_get("web-browser", 0);
  if( zBrowser==0 ){
    static const char *const azBrowserProg[] =
        { "xdg-open", "gnome-open", "firefox", "google-chrome" };
    int i;
    zBrowser = "echo";
    for(i=0; i<count(azBrowserProg); i++){
      if( binaryOnPath(azBrowserProg[i]) ){
        zBrowser = azBrowserProg[i];
        break;
      }
    }
  }
#endif
  return zBrowser;
}
Changes to win/Makefile.mingw.
673
674
675
676
677
678
679
680
681
682
683

684
685
686
687
688
689
690
  $(SRCDIR)/sounds/6.wav \
  $(SRCDIR)/sounds/7.wav \
  $(SRCDIR)/sounds/8.wav \
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/chat-received.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \

  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki








<



>







673
674
675
676
677
678
679

680
681
682
683
684
685
686
687
688
689
690
  $(SRCDIR)/sounds/6.wav \
  $(SRCDIR)/sounds/7.wav \
  $(SRCDIR)/sounds/8.wav \
  $(SRCDIR)/sounds/9.wav \
  $(SRCDIR)/sounds/a.wav \
  $(SRCDIR)/sounds/b.wav \
  $(SRCDIR)/sounds/c.wav \

  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/sounds/plunk.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

Changes to win/Makefile.msc.
594
595
596
597
598
599
600
601
602
603
604

605
606
607
608
609
610
611
        "$(SRCDIR)\sounds\6.wav" \
        "$(SRCDIR)\sounds\7.wav" \
        "$(SRCDIR)\sounds\8.wav" \
        "$(SRCDIR)\sounds\9.wav" \
        "$(SRCDIR)\sounds\a.wav" \
        "$(SRCDIR)\sounds\b.wav" \
        "$(SRCDIR)\sounds\c.wav" \
        "$(SRCDIR)\sounds\chat-received.wav" \
        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \

        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.fileedit.css" \
        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"








<



>







594
595
596
597
598
599
600

601
602
603
604
605
606
607
608
609
610
611
        "$(SRCDIR)\sounds\6.wav" \
        "$(SRCDIR)\sounds\7.wav" \
        "$(SRCDIR)\sounds\8.wav" \
        "$(SRCDIR)\sounds\9.wav" \
        "$(SRCDIR)\sounds\a.wav" \
        "$(SRCDIR)\sounds\b.wav" \
        "$(SRCDIR)\sounds\c.wav" \

        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \
        "$(SRCDIR)\sounds\plunk.wav" \
        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.fileedit.css" \
        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214

1215
1216
1217
1218
1219
1220
1221
	echo "$(SRCDIR)\sounds/6.wav" >> $@
	echo "$(SRCDIR)\sounds/7.wav" >> $@
	echo "$(SRCDIR)\sounds/8.wav" >> $@
	echo "$(SRCDIR)\sounds/9.wav" >> $@
	echo "$(SRCDIR)\sounds/a.wav" >> $@
	echo "$(SRCDIR)\sounds/b.wav" >> $@
	echo "$(SRCDIR)\sounds/c.wav" >> $@
	echo "$(SRCDIR)\sounds/chat-received.wav" >> $@
	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@

	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@
	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@








<



>







1204
1205
1206
1207
1208
1209
1210

1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
	echo "$(SRCDIR)\sounds/6.wav" >> $@
	echo "$(SRCDIR)\sounds/7.wav" >> $@
	echo "$(SRCDIR)\sounds/8.wav" >> $@
	echo "$(SRCDIR)\sounds/9.wav" >> $@
	echo "$(SRCDIR)\sounds/a.wav" >> $@
	echo "$(SRCDIR)\sounds/b.wav" >> $@
	echo "$(SRCDIR)\sounds/c.wav" >> $@

	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@
	echo "$(SRCDIR)\sounds/plunk.wav" >> $@
	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@
	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@