{"id":109,"date":"2026-03-16T14:00:27","date_gmt":"2026-03-16T20:00:27","guid":{"rendered":"http:\/\/aa6kj.hopto.org\/wp\/?p=109"},"modified":"2026-03-16T14:01:13","modified_gmt":"2026-03-16T20:01:13","slug":"antenna-widget-in-gtk","status":"publish","type":"post","link":"http:\/\/aa6kj.hopto.org\/wp\/?p=109","title":{"rendered":"Antenna widget in gtk+"},"content":{"rendered":"\n<p>There is no documentation on libqt6c -> decided to switch to gtk+. Cleaned up the code a bit too.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#include &lt;stdio.h><br>#include &lt;string.h><br>#include &lt;stdlib.h><br>#include &lt;fcntl.h><br>#include &lt;unistd.h><br>#include &lt;termios.h><br>#include &lt;gtk\/gtk.h><br><br>\/\/ Define the antenna names<br>#define ANTENNA1 \"6m beam\"<br>#define ANTENNA2 \"WARC beam\"<br>#define ANTENNA3 \"20-15-10 stack\"<br>#define ANTENNA4 \"40m beam\"<br>#define ANTENNA5 \"No antenna\"<br>#define ANTENNA6 \"No antenna\"<br>#define ANTENNA7 \"No antenna\"<br>#define ANTENNA8 \"No antenna\"<br>#define ANTENNA9 \"No antenna\"<br>#define ANTENNA10 \"No antenna\"<br>#define ANTENNA11 \"No antenna\"<br>#define ANTENNA12 \"No antenna\"<br>#define MAX_ANTENNAS 12<br><br>\/\/ Device for BM5<br>#define DEVICE \"\/dev\/serial\/by-id\/usb-FTDI_FT232R_USB_UART_B003ISA1-if00-port0\"<br><br>\/\/ Update every 100 msec<br>#define UPDATE 100<br><br>char *antennas[] = {ANTENNA1, ANTENNA2, ANTENNA3, ANTENNA4, ANTENNA5, ANTENNA6, <br>                    ANTENNA7, ANTENNA8, ANTENNA9, ANTENNA10, ANTENNA11, ANTENNA12};<br><br>#define MAX_SM 11<br>char *stack_match[] = {\"No antenna\", \"Top antenna\", \"Mid antenna\", \"Top+mid antennas\",<br>                       \"Bottom antenna\", \"Top+bot antennas\", \"Mid+bot antennas\",<br>                       \"All antennas\", \"AUX\", \"Unknown\", \"Phase\"}; <br><br>int fd;<br>GtkWidget *ant, *sm;<br><br>struct packet {<br>  unsigned char cmd;<br>  unsigned char dst;<br>  unsigned char src;<br>  unsigned char channel;<br>  int len;<br>  unsigned char data[256];<br>};<br><br>int read_untilcr(int fd, char *buf, int maxlen) {<br>  <br>  int pos = 0, bytes_rd;<br>  <br>  while(1) {<br>    if((bytes_rd = read(fd, buf + pos, 1)) &lt; 0) return -1;<br>    if(bytes_rd == 0) {<br>      sleep(1); \/\/ could be less than 1sec<br>      continue;<br>    }<br>    if(!pos &amp;&amp; (*buf == '\\r' || *buf == '\\n')) continue;<br>    if(pos == maxlen) return -1;<br>    if(buf[pos] == '\\r') break;<br>    pos++;<br>  }<br>  buf[pos] = '\\0';<br>  return pos;<br>}<br><br>int parse_packet(char *str, struct packet *packet) {<br>  <br>  memset((void *) packet, 0, sizeof(struct packet));<br>  if(sscanf(str, \"[T%2xR%2x%2xH%2xM%[^]]\", &amp;(packet->cmd), &amp;(packet->dst), &amp;(packet->src), &amp;(packet->channel), &amp;(packet->data)) != 5) return -1;<br>  return 0;<br>}<br><br>void set_tty() {<br>  <br>  struct termios param;<br>  <br>  if(tcgetattr(fd, &amp;param) &lt; 0) {<br>    fprintf(stderr, \"Status get failed.\\n\");<br>    exit(1);<br>  }<br>  <br>  param.c_cflag &amp;= ~PARENB;<br>  param.c_cflag &amp;= ~CSTOPB;<br>  param.c_cflag |= CS8;<br>  param.c_cflag &amp;= ~CRTSCTS;<br>  param.c_cflag |= CREAD | CLOCAL;<br>  param.c_lflag &amp;= ~ICANON;<br>  param.c_lflag &amp;= ~ECHO;<br>  param.c_lflag &amp;= ~ECHOE;<br>  param.c_lflag &amp;= ~ECHONL;<br>  param.c_lflag &amp;= ~ISIG;<br>  param.c_iflag &amp;= ~(IXON | IXOFF | IXANY);<br>  param.c_iflag &amp;= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);  <br>  param.c_oflag &amp;= ~OPOST;<br>  param.c_oflag &amp;= ~ONLCR;<br>  cfsetispeed(&amp;param, B57600);<br>  cfsetospeed(&amp;param, B57600);<br>  <br>  if(tcsetattr(fd, TCSANOW, &amp;param) &lt; 0) {<br>    fprintf(stderr, \"Status set failed.\\n\");<br>    exit(1);<br>  }<br>}<br><br>static gboolean update_band_info(void *asd) {<br>  <br>  int len, antenna, relay;<br>  struct packet packet;<br>  char buf[512], tr = 0;<br>  <br>  if((len = read_untilcr(fd, buf, sizeof(buf))) &lt; 0) {<br>    fprintf(stderr, \"Read error.\\n\");<br>    exit(1);<br>  }<br>  <br>  if(parse_packet(buf, &amp;packet) &lt; 0) {<br>    printf(\"Incomplete packet -- skipping.\\n\");<br>    return 1;<br>  }<br>  <br>  switch(packet.cmd) {<br>  case 0xc4:  \/\/ C4 = Bandmaster 5 status<br>    if(sscanf(packet.data, \"0%1d01EEEEEEEEEEEE%4d\", &amp;antenna, &amp;relay) != 2) {<br>      printf(\"Error extracting antenna &amp; relay information.\");<br>      antenna = 99;<br>    }<br>    if(antenna > 0 &amp;&amp; antenna &lt; MAX_ANTENNAS)<br>      sprintf(buf, \"&lt;span color='blue'>%s (relays = %d)&lt;\/span>\", antennas[antenna-1], relay);<br>    else<br>      sprintf(buf, \"&lt;span color='red'>Unknown&lt;\/span>\");<br>    gtk_label_set_markup(GTK_LABEL(ant), buf);<br>    break;<br>  case 0x45: \/\/ Stack match<br>    sscanf(packet.data, \"%2x\", &amp;antenna);<br>    if(antenna >= 128) {<br>      antenna -= 128; \/\/ transmitting<br>      tr = 1;<br>    }<br>    if(antenna >= 0 &amp;&amp; antenna &lt; MAX_SM)<br>      sprintf(buf, \"&lt;span color='blue'>%s (%s)&lt;\/span>\", stack_match[antenna], tr?\"TX\":\"RX\");<br>    else<br>      strcpy(buf, \"&lt;span color='red'>Unknown state&lt;\/span>\");<br>    gtk_label_set_markup(GTK_LABEL(sm), buf);<br>    break;<br>  case 0x13: \/\/ decoder data<br>    printf(\"Decoder data ignored.\\n\");<br>    break;<br>  default:<br>    printf(\"Unknown packet (%x) - ignored.\\n\", packet.cmd);<br>  }<br>  return 1;<br>}<br><br>void closewin(GtkWidget *win, gpointer data) {<br><br>  gtk_main_quit();<br>}<br><br>int main(int argc, char *argv[]) {<br><br>  GtkWidget *window;<br>  GtkWidget *ant_label, *sm_label, *hbox1, *hbox2, *vbox;<br><br>  gtk_init(&amp;argc, &amp;argv);<br>  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);<br>  gtk_window_set_title(GTK_WINDOW(window), \"Antenna Status\");<br>  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);<br>  gtk_window_set_default_size(GTK_WINDOW(window), 350, 60);<br>  g_signal_connect(G_OBJECT(window), \"destroy\", G_CALLBACK(closewin), NULL);<br><br>  ant_label = gtk_label_new(\"Antenna:\");<br>  gtk_label_set_xalign(GTK_LABEL(ant_label), 0.0); \/\/ 0 = left, 1 = right<br>  sm_label =  gtk_label_new(\"Stack match:\");<br>  gtk_label_set_xalign(GTK_LABEL(sm_label), 0.0);<br>  ant = gtk_label_new(\"-----\");<br>  gtk_label_set_xalign(GTK_LABEL(ant), 0.5);<br>  sm = gtk_label_new(\"-----\");<br>  gtk_label_set_xalign(GTK_LABEL(sm), 0.5);<br><br>  hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);<br>  hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);<br>  vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);<br><br>  gtk_box_pack_start(GTK_BOX(hbox1), ant_label, TRUE, TRUE, 5);<br>  gtk_box_pack_start(GTK_BOX(hbox1), ant, TRUE, TRUE, 5);<br><br>  gtk_box_pack_start(GTK_BOX(hbox2), sm_label, TRUE, TRUE, 5);<br>  gtk_box_pack_start(GTK_BOX(hbox2), sm, TRUE, TRUE, 5);<br><br>  gtk_box_pack_start(GTK_BOX(vbox), hbox1, TRUE, TRUE, 5);<br>  gtk_box_pack_start(GTK_BOX(vbox), hbox2, TRUE, TRUE, 5);<br>  gtk_container_add(GTK_CONTAINER(window), vbox);<br>  <br>  if((fd = open(DEVICE, O_RDONLY)) &lt; 0) {<br>    fprintf(stderr, \"Can't open device.\\n\");<br>    exit(1);<br>  }<br>  set_tty();<br><br>  g_timeout_add(UPDATE, update_band_info, NULL);<br><br>  gtk_widget_show_all(window);<br>  gtk_main();<br><br>  close(fd);<br>  return 0;<br>}<br><\/pre>\n\n\n\n<p>Compile with (the above is in ant.c file): gcc ant.c -o ant -march=native `pkg-config &#8211;cflags &#8211;libs gtk+-3.0`<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There is no documentation on libqt6c -> decided to switch to gtk+. Cleaned up the code a bit too. #include &lt;stdio.h>#include &lt;string.h>#include &lt;stdlib.h>#include &lt;fcntl.h>#include &lt;unistd.h>#include &lt;termios.h>#include &lt;gtk\/gtk.h>\/\/ Define the antenna names#define ANTENNA1 &#8220;6m beam&#8221;#define ANTENNA2 &#8220;WARC beam&#8221;#define ANTENNA3 &#8220;20-15-10 stack&#8221;#define ANTENNA4 &#8220;40m beam&#8221;#define ANTENNA5 &#8220;No antenna&#8221;#define ANTENNA6 &#8220;No antenna&#8221;#define ANTENNA7 &#8220;No antenna&#8221;#define ANTENNA8 &#8220;No antenna&#8221;#define [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-109","post","type-post","status-publish","format-standard","hentry","category-ham_radio"],"_links":{"self":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/109","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=109"}],"version-history":[{"count":1,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/109\/revisions"}],"predecessor-version":[{"id":110,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=\/wp\/v2\/posts\/109\/revisions\/110"}],"wp:attachment":[{"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=109"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/aa6kj.hopto.org\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}