Showing posts with label hacking. Show all posts
Showing posts with label hacking. Show all posts

Tuesday, May 12, 2026

DIY: Cajon string replacement

Two years ago, my son's Meinl cajon (box drum) suffered a broken string.  The string in a cajon functions like the springs on a snare drum, so when broken the drum retains its boxy "thud" but the higher-pitched "snap" is lost or, worse, turns into a sloppy rattle.  So I decided to try to replace the string (which is essentially just a wound guitar string) myself.

I removed the face of the cajon easily enough — it is secured by just a handful of wood screws and some adhesive tape. But once inside, I decided I wasn't satisfied with the manufacturer's approach to securing the string.  Guitar strings have a "ball end" that secures that one end, but the other end was secured by putting a half knot in the string.  I wanted something less prone to tension changes and to inconsistencies in the precision of those knots across the two strings.

My solution was to hit the hardware store for a couple of T-nuts, which I installed on the underside of the top bit of framing on the cajon.  These would provide a way to secure the string with the tension of the T-nut's associated screw (much like the way electrical wiring its secured at switches).  I drilled an additional hole on each side for the string to pass through so that it would approach the T-nut from the side instead of from the top.  Then, I loosened the tension bar on the bottom of the drum and rewired it, pulling the strings taut with a single wrap around the T-nut screw before tightening that down.


Once the strings were secured, it was trivial business to screw the drum face back on and then use the drum's tension bar system to further tighten up the strings to my son's preferred tone.  

Why recall this two-year-old tale today?  Because during our church worship service this past week, my son suffered another broken cajon string.  But replacing it this time around was super easy, in part because of the hacks made to the drum the last time.  And who knows — maybe somebody else out there is looking for just this sort of improvement to their own cajon?  If so, consider our approach as one possibility that might work for you.

Wednesday, March 3, 2021

My custom Planning Center Services plan report. You're welcome.

To all my fellow Planning Center Services jockeys out there, I thought I'd share with you a custom Plan report that I created (using one of the built-in reports as a baseline, mind you) and have been using for years now. Now, before you assume that all I've done here is fiddle with colors and fonts and page layout, hear me out. There are some features of this template that I want you to know about.

First—and the primary reason that I love this template so much—is that it handles Plan and Item Note categories dynamically. Unlike the built-in templates, this report template doesn't have to be modified just because you use different Notes categories than the default Planning Center ones. In fact, it doesn't care what specific Notes categories you have at all. Rather, it examines the Plan and its Items and dynamically determines which Note types are actually employed. This means two things: that it doesn't waste any page/screen real estate on unused Note categories, and you can use the same template for entirely different Service Types with entirely different categories defined!

The second, admittedly minor, thing that I like about this template is that it adds a column for icons that appear next to certain Item types. So Songs show up with a music notes icon (♫) next to them; Media items show a "play" triangle (⏵). It's a little thing, but it draws the eye to certain Item types quickly.

Finally, the template itself allows some quick customization of the features it employs via a collection of boolean variables at the top.  This allows you to use the same basic template for multiple custom Reports, but with little easy tweaks to goven the feature set for each one.  So, for example, I have a Report that shows everything that I distribute to my A/V operators and band members.  But I have another one using this same template that I use for our Ushers and Greeters where the variables show_plan_people, show_plan_notes, and show_item_notes have been set to false.

So, without further ado, my template. Please feel free to copy, use, and modify this thing to better suit your church's needs!

<!-- QUICK CUSTOMIZATION: {% assign show_plan_notes = true %} {% assign show_plan_people = true %} {% assign show_item_times = true %} {% assign show_item_length = true %} {% assign show_item_description = true %} {% assign show_item_notes = true %} --> <html> <head> <title>{{ plan.ministry.name }}:: {{ plan.dates }}</title> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,600;1,400;1,600&display=swap" rel="stylesheet"> <style> body { padding: 0; margin: 5px; color: #222; font-family: Roboto, sans-serif; font-size: 14pt; } h1 { font-size: 1.5em; font-weight: normal; text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.9); } h2 { font-size: 1.1em; font-weight: normal; padding: 20px 35px; margin: 0; line-height: 1em; text-shadow: 0 1px 0 white; } h3 { font-size: 0.9em; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } pre { margin: 0; padding: 0; white-space: pre-wrap; font-size: .9em; } table#plan { width: 100%; border-collapse: collapse; margin-top:0px; margin-bottom: 15px; font-size: 10pt; } table#plan tr { border-bottom: 1px solid #ddd; } table#plan thead { display: table-header-group; } table#plan th { color: white; background: #444; font-weight: bold; padding: 0.5em 1em; vertical-align: middle; } table#plan td { text-align: center; padding: 0.5em 1em; } table#plan .icon { padding: 0; } table#plan .preservice .time, table#plan .preservice .length { color: #aaa; } table#plan .time { width: 3em; text-align: center; } table#plan .length { width: 3em; text-align: center; } table#plan .note_column { text-align: left; background: #f8f8f8; border-left: 2px solid white; } table#plan .header { text-align: left; font-weight: bold; color: black; background: #ddd; } table#plan .note { font-weight: bold; text-align: left; vertical-align: middle; min-width: 1in; } table#plan .element { min-width: 3in; text-align: left; } table#plan .element .description { font-size: 0.86em; color: #444; font-style: italic; } table#header { width: 100%; border: none; padding: 0; border-collapse: collapse; } table#header td { padding: 2px; vertical-align: top; } .artwork { float: left; margin-right: 15px; height: 40px; } .note_content { background: yellow; padding: 4px 8px 0; border-radius: 0.3em; } #header { padding: 5px 20px; background: #000; color: white; line-height: .8em; margin: 0; } #header img { float: right; height: 60px; margin-right: -15px; } #titles { color: #333; font-size: 1.1em; font-weight: normal; padding: 10px 20px; margin: 0; line-height: 1.1em; text-shadow: 0 1px 0 white; min-height: 45px; } #plan_title { font-style: normal; font-size: 0.8em; display: block; color: #666; margin: 4px 0 0 0; } #plan_notes, #plan_people { background: #f8f8f8; margin: 1em 0; border-left: 4px solid black; padding: 0.5em 0 1em 2em; } #plan_notes { font-size: 0.8em; background: #ffffd0; } #plan_notes .plan_note { margin-top: 1em; } #plan_notes .plan_note_category { font-weight: bold; } #plan_people { font-size: 0.9em; } #plan_people .person { font-size: 0.7em; margin-right: 15px; display: inline-block; } #plan_people .person_status_U { opacity: 0.5; } #plan_people .person_category { clear: both; padding-top: 1em; } #plan_people .person_category p { font-size: 0.8em; margin: 0; } #plan_people .person_position { font-style: italic; } #plan_people .position { font-weight: bold; } </style> </head> <body> <!-- If showing item notes, first build a list of item note categories in use. --> {% if show_item_notes %} {% assign used_item_note_categories = '' | split: '' %} {% for item in plan.items %} {% for note in item.notes %} {% assign category = note.category.name | split: '|' %} {% assign used_item_note_categories = used_item_note_categories | concat: category %} {% endfor %} {% endfor %} {% assign used_item_note_categories = used_item_note_categories | uniq | sort %} {% endif %} <div id="header"> <h1>{{ plan.ministry.name }} - {{ plan.dates }}</h1> </div> <div id="titles"> <img class="artwork" onerror="this.style.display='none'" src="{{ plan.series_artwork_url }}" /> <div id="series_title">{{ plan.series_title }}</div> <div id="plan_title">{{ plan.plan_title }}</div> </div> <!-- If showing plan notes, first build a list of plan note categories in use, then iterate. --> {% if show_plan_notes %} {% assign used_plan_note_categories = '' | split: '' %} {% for note in plan.notes %} {% assign category = note.category.name | split: '|' %} {% assign used_plan_note_categories = used_plan_note_categories | concat: category %} {% endfor %} {% assign used_plan_note_categories = used_plan_note_categories | uniq | sort %} {% unless used_plan_note_categories == empty %} <div id="plan_notes"> {% for plan_note_category in used_plan_note_categories %} <div class="plan_note"> <div class="plan_note_category">{{plan_note_category}}</div> {% for note in plan.notes %} {% if note.category_name == plan_note_category %} <div>{{note.note}}</div> {% endif %} {% endfor %} </div> {% endfor %} </div> {% endunless %} {% endif %} <table id="plan"> <thead> <tr> {% if show_item_times %} {% for time in plan.plan_times %} <th class="time">@{{ time.starts_at | date: '%I:%M' }}</th> <!-- {% increment colspan %} --> {% endfor %} {% endif %} <th class="icon"></th> <!-- {% increment colspan %} --> <th align="left">Element</th> <!-- {% increment colspan %} --> {% if show_item_notes %} {% for category in used_item_note_categories %} <th class="note">{{ category }}</th> <!-- {% increment colspan %} --> {% endfor %} {% endif %} {% if show_item_length %} <th class="length">Length</th> <!-- {% increment colspan %} --> {% endif %} </tr> </thead> <tbody> {% for item in plan.items %} {% case item.item_type %} {% when 'Header' %} <!-- Header rows --> <tr> <td colspan="{{ colspan }}" class="header">{{ item.title }}</td> </tr> {% else %} <!-- Non-header rows --> {% assign item_class = ''%} {% if item.is_preservice or item.is_postservice %} {% assign item_class = 'preservice ' %} {% endif %} <tr class="{{ item_class }}"> {% if show_item_times %} {% for time in plan.plan_times %} <td class="time item {{ item_class }} {{ item.item_type }}"> {% for item_time in item.times %} {% if item_time.time_id == time.id %} {{ item_time.actual_time | date: '%I:%M' | downcase }} {% endif %} {% endfor %} </td> {% endfor %} {% endif %} <td class="item icon"> {% if item.item_type == "Song" %}&#9835;{% endif %} {% if item.item_type == "Media" %}&#9205;{% endif %} </td> <td class="item element {{ item_class }} {{ item.item_type }}"> {{ item.title }}{% if item.song %} [{{ item.arrangement.music_key }}]{% endif %} {% if show_item_description %}<div class="description">{{ item.description }}</div>{% endif %} </td> {% if show_item_notes %} {% for category in used_item_note_categories %} <td class="item note note_column {{ item_class }} {{ item.item_type }}"> {% for note in item.notes %} {% if note.category_name == category %} {{ note.note }} {% endif %} {% endfor %} </td> {% endfor %} {% endif %} {% if show_item_length %} <td class="item length {{ item_class }} {{ item.item_type }}">{{ item.length }}</td> {% endif %} </tr> {% endcase %} {% endfor %} </tbody> </table> {% if show_plan_people %} <!-- If showing plan people, first build a list of person categories in use, then iterate. --> {% assign used_person_categories = '' | split: '' %} {% for plan_person in plan.plan_people_not_declined %} {% assign category = plan_person.category.name | split: '|' %} {% assign used_person_categories = used_person_categories | concat: category %} {% endfor %} {% assign used_person_categories = used_person_categories | uniq | sort %} {% unless used_person_categories == empty %} <div id="plan_people"> {% for plan_person_category in used_person_categories %} <div class="person_category"> <p><strong>{{ plan_person_category }}</strong></p> {% for plan_person in plan.plan_people_not_declined %} {% if plan_person.person.name != null %}{% if plan_person.category.name == plan_person_category %} <div class="person person_status_{{ plan_person.status }}"> &bull; <span class="person_name">{{ plan_person.person.name }}</span> <span class="person_position">({{ plan_person.position }})</span> </div> {% endif %}{% endif %} {% endfor %} </div> {% endfor %} </div> {% endunless %} {% endif %} </body> </html>

As of a few minutes ago, I've submitted this report template back to the fine folks that develop Planning Center Services so that they may consider integrating its enhancements into the core product itself.

Thursday, November 2, 2017

Custom countdown video generation (the ... complicated way)

At Providence Baptist Church, where I serve on staff as Worship Leader and Technology Coordinator, we occasionally employ countdown videos ahead of our worship service start times.  Now, a countdown video is little more than a video clip that shows a "minutes:seconds" countdown, usually overlaying some subtle motion graphic loop or still image.  You can buy these things from media sites (such as Worship House Media) for a nominal fee, often as part of a collection of videos which bear a common visual theme — a countdown video, a few motion loop videos without any overlaid text, etc.  But sometimes I want a countdown video that I can't purchase, perhaps because I don't like the font, font size, positioning, or background motion loop of what is available for purchase.

Now, I'm sure that expensive video editing software can generate these things willy-nilly with full customization.  But I'm not really an expensive-video-editing-software kind of guy.  I'm familiar with a handful of less-expensive (or free) options, though, and many of these offer the promise of a workaround for my itch to design custom countdown loops.  So tonight, I decided to see what I could work out using my current favorite of these editors, VideoPad.

Like many other timeline-based video editors, VideoPad allows you to create text objects which can overlay another video track.  I figured that if I could create a VideoPad project with a whole track full of 1-second text objects, each of which showed the countdown text I wanted ("4:59", "4:58", "4:57", and so on), I could use this as a template for various specific countdown timers in the future.  I'd simply swap out the still frame or motion loop video that served as the background, and re-render as needed.  But it only took me a few minutes to realize that I really didn't want to manually create and sequence 300 of these frames (5 minutes x 60 seconds per minute).

So I took a slight detour.  What if I could reverse-engineer the VideoPad project file format?  Was that possible?  It only took a few minutes to realize that VideoPad project files are simple text files with Unix line endings and URI-query-encoded lines of data.  Unfortunately, while I could make sense of much of what I saw in my sample project file, I failed to successfully edit it to affect a simple change — the addition of one more countdown text object properly sequenced.

I was about to give up hope, when I remembered that VideoPad also supported the use of PNG image overlays — with full alpha-channel support!  If I could generate 300 PNGs, each one a transparent rectangle with the countdown timer text rendered in the frame, then VideoPad would let me import those as an image sequence (much like the "create a slideshow" feature of other editors) and then I could layer that whole sequence atop the background still/video of my choosing!  Finally, I was in familiar territory.

After about a half-hour of hacking around with Python and Pillow (née the Python Imaging Library), I had something that worked for my purposes!  I quickly generated the 300 image files, successfully imported them into VideoPad, shoved another motion loop video underneath them, and rendered my first custom countdown video!  And since my wife and kids were away from home, I then spent another hour polishing up my script a bit more, allowing it to be driven with various options (controlling the size of the generated frames, the font specifications of the countdown text, and the rough positioning of the text), adding some error-checking and documentation, and published the result to Github as gen-countdown-frames.

To those with oddball interests and similar needs:  you're welcome!

Friday, June 3, 2011

Happy birthday, Gavin, from an old geek to a new one

My oldest son, Gavin, loves science and technology. His love is not just at a "video games are cool" or "I want an iPod because everyone else has one" level. It's much deeper, almost like just needing to know how the world works — by what rules does the Universe play, and how can we use our creativity to bend them? While his overactive imagination can sometimes be a source of conflict in the home (or in the restaurant, or church, or public restroom, or …), his mother and I do recognize the beauty of it.

Gavin turns eight years old in … well, in about two hours from now, technically. I was trying to think of something special I could do for him, when it occurred to me to use one of his favorite toys/tools — Lego® blocks — to create a portrait of him. At a design level, this isn't really so challenging. I mean, it only took moments to load up a recent photo of him into the GIMP, shrink it, posterize it, replace its palette with one based around primary colors, etc.

The result was a blocky image with Lego-ish colors whose aspect ratio had been adjusted to account for the fact that a 1x1 Lego block isn't square, but instead roughly 3:4:

No, the difficult part was in actually building the thing! Okay, not difficult so much as tedious. And of course, I failed to inventory my available blocks before starting, so it was on a wing and a prayer that the hours-long activity began. But in the end, every piece planned was in place (minus some black shadow blocks on the bottom I decided to omit), and the work was complete. I had a roughly 14" x 18" image of my son, made out of plastic, to present to him tomorrow as a surprise.

Friday, November 20, 2009

Revisionist History


Remember the Subversion 1.0 celebration T-shirts? Alas, there are no `svnadmin dump' and `svnadmin load' for cottonware.
As you might have read elsewhere, the Subversion project has recently been accepted in the Apache Software Foundation's Incubator. As part of the process, we'll be migrating all kinds of goodies off of Tigris.org — which has been the project's home (and a good one at that) for the past near-decade — and onto Apache-hosted servers. Last weekend we took the first of those steps by migrating our version control history. I managed the Subversion side of this migration, prepping the data for delivery into the ASF Infrastructure team's able hands.

But I wanted to do more than simply move our Subversion history from one place to another. See, when Subversion began, it was a bunch of source code living in a CVS repository. When the source code compiled into something trustworthy, we let Subversion hold it own source code. (For the record, we were never given cause to regret that decision.) But at the time we made that change, there was no reliable and simple way to convert CVS history into Subversion history like there is with cvs2svn today. So we just exported the latest snapshot of our main development line, imported that into Subversion, and dealt with the severed history. However, this repository migration — which was going to be disruptive anyway — presented an opportunity to stitch together our old CVS history and our Subversion history. So I did. Here's how:

  1. Using cvs2svn, I converted all CVS history to Subversion and deposited it into a temporary repository, svn-from-cvs.
  2. Now, the CVS repository data contained some trailing changes that were created after the switch to Subversion back in 2001. Most of those were commits to www/ (which we manually mirrored for a while based on our Subversion commits to trunk/www/). A couple of them were things like system-wide automated tweaks to www/robots.txt made by CollabNet folk. Also, we had real tags and branches in our CVS repos that we didn't bring with us into Subversion. So I dumped the first 3654 revisions from svn-from-cvs — the pre-switchover changes only — and loaded that into the stitch repository, svn-complete.
  3. To historically preserve the fact that apparently we didn't care too much about those old CVS tags and branches, I committed their deletion from svn-complete (but left the branches/ and tags/ top-level directories themselves).
  4. Since the first revision of our project's Subversion history (in the main svn repository) was a massive import into trunk, that would have clashed mightily being loaded atop already-existing files and directories in svn-complete. So instead checked I out svn-complete/trunk@HEAD, then exported svn/trunk@1 atop it. The local mods were the small delta between what we got outta CVS on August 31, 2001, and what we put into Subversion. They were mostly the result of $Date$ keyword formatting differences. I committed those local mods, which now brought svn-complete into sync with svn@1' except that svn-complete still had empty tags/ and branches/ directories (which were added in r532 and r1237, respectively).
  5. I dumped -r2:531 of svn, loading the result into svn-complete.
  6. I skipped r532 (the revision in which we created our tags/ directory) from svn, instead adding a no-op placeholder revision to svn-complete.
  7. I then dumped -r533:1236 of svn, loading the result into svn-complete.
  8. Once again, I skipped r1237 (the revision in which we created our branches/ directory) from svn, instead adding another no-op placeholder revision to svn-complete.
  9. Finally, I dumped the rest of the svn history (r1238:r40515), loading those revisions into svn-complete.

The result was a single repository (svn-complete) of 44170 revisions (3654 from CVS, 40515 from Subversion, and 1 cleanup revision) that contained all of the Subversion project's version control history, starting with the inception of the project. It's this data that I handed off to the ASF Infrastructure team.

The ASF Infrastructure team took this repository's data and loaded it into the ASF repository (with external commits disabled to prevent interleaved commit history). At the time that this history was loaded into the ASF repository, that repository already had 836419 revisions in it. The next 3655 revisions represent the Subversion CVS history (plus a fixup revision). This means that any historical references found in Subversion's source code, issue trackers, mailing lists, etc. that refer to pre-migration revisions (which are easy to spot, as they are all quite a bit smaller than 800000!) may be found in the ASF repository by adding 836419 + 3655 = 840074 to the revision number.

We as a project are still really finding all the places that this change of address (and revision numbers) will affect us. I can assure you that the last place I expected it to hit was my clothes closet, though!

Tuesday, November 3, 2009

SubConf/Munich 2009

As I compose this, I'm 38,000 feet above sea level and somewhere between Charlotte and Denver, en route (ultimately) to this year's U.S. Apachecon event. Seems it was just yesterday that I was flying home from my last conference event. Perhaps that's because it was just a few days ago that I passed through Customs in Charlotte after the lengthy flight home from Munich, Germany. I'd been in Munich for the third SubConf Subversion user conference. I've had the pleasure of attending the prior two years as well, and this year's event was again a enjoyable one.

After helping to coach Gavin's soccer team to an 8-1 victory last Saturday, I hurried home, grabbed my bags, and was off to the airport. My direct flight from Charlotte to Munich landed Sunday morning around 8:30am local time. A couple of train rides and a short walk later, I was checking into the B & B Hotel, where (I was told) "all the cool kids" — that is, some fellow Subversion developers — were staying. I spent Sunday afternoon getting settled into the hotel and resting, then met up with Hyrum Wright for the express purposes of reprising last year's trip to Berni's Nudelbrett and to plan how we'd invest our time on Monday.

Hyrum and I decided on a trip to Salzburg, Austria for our Monday outing. It was two hours away by train, so we caught the earliest one we could manage. While in Salzburg, we visited the Hohensalzburg Fortress (an 11th-century fortress that overlooks the city), the Residenz State Rooms, Mozart's birthplace, and some other spots in the city. Afterwards, we took a bus out to the town of Grönig, where we rode the Untersberg Cable Car up into the mountains. Wow! Suspended above the earth by a wire the thickness of my forearm, watching as rocky crags passed underneath, I was overwhelmed by the novelty of it all. This was not a normal day in the life of Mike Pilato, to say the least. Our train back to Munich got us in town in time for dinner, which we took at one of the city's many beerhouses. (Thankfully, the locals don't take it too personally when we tourists opts for Coke and Fanta.)

I returned to the hotel around 10:00pm, where two things were waiting for me: a group of Subversion developer colleagues, and a nasty surprise from the SubConf event organizers. It seems that an employee of Polarion who was slated to present an all-day Subversion workshop the following day (Tuesday) fell ill, and then made the poor decision to use email — nice, asynchronous, non-interrupting, unacknowledged email — as his medium for telling the event organizers that he had to bail on the workshop. By the time they realized that they had no one to present the workshop, the organizers had to scramble for assistance. They found me online and asked if I could present the workshop instead. I was completely unprepared to do so, but offered to help as best as I could.

Fortunately, things took a turn for the better when I then went down to the lobby to meet up with my colleagues. While I was bemoaning this situation and my lack of preparedness, the angel Michael — no, not that one; Michael Diers (from Elego) — offered to assist me for the first half of the day-long workshop and to take over entirely in the latter half. And as if that wasn't great enough, I was then presented with the finished results of the T-shirt I designed for the conference:

Tuesday's workshop went pretty well, actually, thanks largely to Michael's familiarity with the topic and ready-on-hand materials. After lunch I was finally able to meet up with several other Subversion developers in a room the conference organizers had arranged for us to occupy for hackathon purposes. We worked for a few hours before heading over to participate as a panel in a Developer Roundtable event — an hour and a half of face-to-face interaction with Subversion users and administrators interested in learning more about Subversion's upcoming features or in solving particular Subversion problems they were having. I love these events for the fact that they serve to remind me that at the other side of every bug report or feature request is another human being who isn't just trying to make work for us as developers but is merely trying to get stuff done.

Wednesday morning, I delivered my keynote presentation, The Subversion Legacy (So Far...). I took the attendees on a brief walk through Subversion's history, beginning with its instantiation as an open-source project by CollabNet and continuing through to the current day, and focusing on aspects of the community and of key events and decisions which have contributed to Subversion's success. Despite the language challenge (most of the attendees were more comfortable communicating in German), I got positive feedback. And with that completed, I was in the clear in terms of obligations to the conference, and joked that I was now able to return to my typical 40% stress level.

I spent the rest of Wednesday and most of Thursday working on Subversion itself in the hackathon room with the other developers. We had occasional breaks to take in presentations by our peers, but mostly spent the time trying to improve the very software that the conference existed to celebrate. Thursday night those of us that remained went to dinner in Munich, sharing a final last few minutes together. A casual onlooker would never guess that we live scattered across the globe, only occasionally seeing each other in person. (In fact, I met one of my CollabNet colleagues for the first time on this trip!)

I traveled to the airport Friday morning with Greg Stein, hopped onto my plane bound for Charlotte, and was fortunate to sleep two-thirds of the flight away. I was greeted in Charlotte on Friday afternoon by my incredible, amazing wife and children. The opening theme of my keynote was about gratitude, and gratitude for good friends and a loving family served as the bookend to that week-long trip to Germany.

Saturday, June 28, 2008

Aaron Spuler — making Mozilla smile

I've been using Aaron Spuler's Mozilla themes for a few years. I don't know the guy, but for some reason his graphical vision just leads him to places that put happy thoughts in my head. (This is rather in contrast with the default Firefox and Thunderbird themes, which are, shall we say … uninspired.) Anyway, peruse his (free) wares at http://www.spuler.us, and consider sending him a small donation if you dig it, too.

Friday, June 13, 2008

"Programmer Insecurity"

Ben Collins-Sussman seems to have found himself squarely in the middle of the "centralized vs. distributed version control" debate. It's not exactly what he intended to do, of course. Ben is just one of those folks who has years of experience in people-watching, and a keen ability to summarize the patterns he sees.

Ben's latest blog post (at the permalink-less URL http://blog.red-bean.com/sussman/?p=96) is more of that observational goodness. This time he tackles programmer insecurity. If you're a programmer, check it out — perhaps Ben is describing someone you know.

(Oh, and his post ventured into The Land of Version Control Tools, so if you wait a few days to read it, you'll almost certainly get bonus entertainment provided by a slew of comments aimed at furthering the which-kind-of-version-control-is-better debate.)

Monday, February 25, 2008

Subversion talk at Charlotte.PM this Wednesday

If you're in the Charlotte area and interested in an overview of Subversion plus the highlights of the upcoming 1.5 release thereof, consider attending the Charlotte Perl Mongers technical meeting this Wednesday, February 27th at 7pm in room IT4134 at CPCC, where I'll be giving just such a talk. And if time permits, we'll also get a demo of svk, another version control system build atop Subversion's core libraries — bonus! See the official meeting notice for details.

Friday, December 21, 2007

Hacking on the LG CU400

I recently bought (via eBay) a USB data cable for my LG CU400 cellphone. Sadly, I did so before realizing that LG apparently doesn't provide software besides basic Windows drivers for this phone. (For those interested, I used the drivers from a package named LGUSBModemDriver_Eng_Ver_4.5.exe, found with a Google site search.) At any rate, my investment thus far (which was only about $7, truth be told) was looking like a poor one.

But tonight I tried BitPim even though my phone model is not listed as explicitly supported. To my great surprise, BitPim recognized my phone as an "Other CDMA phone". That meant I got filesystem-explorer-type access, but none of the other GUI goodies. That's okay, though -- it only took a little bit of poking around to figure out where the phone keeps my photos, videos, mp3s, and so on.

And best of all, I was able to pull off the hack described here. So now I can use the mobile versions of Google Maps and Gmail on my phone without answering a network usage permission prompt for what seemed before like every packet transmitted!

12/21/2007 Followup: I forgot to note that I was doing all this on a Windows 2000 machine. But I'm pretty sure the drivers I got were rated for Windows XP, too. No clue about Vista support for any of this stuff.